diff --git a/configs/root-compilation.tsconfig.json b/configs/root-compilation.tsconfig.json index 1c1a3689aa..79ac5e5bdf 100644 --- a/configs/root-compilation.tsconfig.json +++ b/configs/root-compilation.tsconfig.json @@ -177,6 +177,9 @@ }, { "path": "../packages/rpc-core/compile.tsconfig.json" + }, + { + "path": "../plugins/fc3-plugin/compile.tsconfig.json" } ] } diff --git a/plugins/fc3-plugin/.eslintrc.js b/plugins/fc3-plugin/.eslintrc.js new file mode 100644 index 0000000000..8a85e18845 --- /dev/null +++ b/plugins/fc3-plugin/.eslintrc.js @@ -0,0 +1,10 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: [ + require.resolve('@malagu/component/configs/build.eslintrc.json') + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'compile.tsconfig.json' + } +}; \ No newline at end of file diff --git a/plugins/fc3-plugin/README.md b/plugins/fc3-plugin/README.md new file mode 100644 index 0000000000..6691fa3d03 --- /dev/null +++ b/plugins/fc3-plugin/README.md @@ -0,0 +1 @@ +# Malagu - FC3 Plugin Component diff --git a/plugins/fc3-plugin/compile.tsconfig.json b/plugins/fc3-plugin/compile.tsconfig.json new file mode 100644 index 0000000000..be92f8b566 --- /dev/null +++ b/plugins/fc3-plugin/compile.tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "@malagu/component/configs/base.tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../cloud-plugin/compile.tsconfig.json" + }, + { + "path": "../code-loader-plugin/compile.tsconfig.json" + }, + { + "path": "../../dev-packages/cli-common/compile.tsconfig.json" + } + ] +} diff --git a/plugins/fc3-plugin/malagu-http-mode.yml b/plugins/fc3-plugin/malagu-http-mode.yml new file mode 100644 index 0000000000..48bf3fd7af --- /dev/null +++ b/plugins/fc3-plugin/malagu-http-mode.yml @@ -0,0 +1,23 @@ +malagu: + cloud: + function: + runtime: custom + bootstrap: "${ startCommand ?: 'node backend/index.js'}" + trigger: + name: ${malagu.cloud.function.name}-${malagu.cloud.alias.name} + qualifier: ${malagu.cloud.alias.name} + triggerType: http + triggerConfig: + authType: anonymous + methods: [GET, POST, PUT, DELETE, HEAD, PATCH] + customDomain: + name: ${env.MALAGU_DOMAIN || 'auto'} + protocol: HTTP + certConfig: + # certName: xxx + privateKey: "${ env.SSL_KEY ?: 'ssl/domain.key'}" + certificate: "${ env.SSL_PEM ?: 'ssl/domain.pem'}" + routeConfig: + routes: + - path: '/*' + qualifier: ${malagu.cloud.alias.name} diff --git a/plugins/fc3-plugin/malagu-mns-topic-mode.yml b/plugins/fc3-plugin/malagu-mns-topic-mode.yml new file mode 100644 index 0000000000..1fa4608cd6 --- /dev/null +++ b/plugins/fc3-plugin/malagu-mns-topic-mode.yml @@ -0,0 +1,15 @@ +malagu: + cloud: + function: + runtime: nodejs14 + trigger: + name: ${malagu.cloud.function.name}-${malagu.cloud.alias.name}-mns_topic + qualifier: ${malagu.cloud.alias.name} + triggerType: mns_topic + # invocationRole: acs:ram::123456:role/app-mns-role + # sourceARN: acs:mns:cn-hangzhou:123456:/topics/test + triggerConfig: + # topicName: test-topic + notifyContentFormat: JSON + notifyStrategy: BACKOFF_RETRY + # filterTag: foo diff --git a/plugins/fc3-plugin/malagu-remote.yml b/plugins/fc3-plugin/malagu-remote.yml new file mode 100644 index 0000000000..34edbaafd1 --- /dev/null +++ b/plugins/fc3-plugin/malagu-remote.yml @@ -0,0 +1,9 @@ +mode: "${currentMode|toObjects[.item in [ 'http', 'api-gateway', 'sample-http', 'timer', 'mns-topic', 'event']]|suffix('-mode') || 'http-mode'}" +malagu: + cloud: + secure: true + internal: false + timeout: 600000 + function: + instanceConcurrency: 10 + withoutCodeLimit: true diff --git a/plugins/fc3-plugin/malagu-sample-http-mode.yml b/plugins/fc3-plugin/malagu-sample-http-mode.yml new file mode 100644 index 0000000000..83fba4edd3 --- /dev/null +++ b/plugins/fc3-plugin/malagu-sample-http-mode.yml @@ -0,0 +1,22 @@ +malagu: + cloud: + function: + runtime: nodejs14 + trigger: + name: ${malagu.cloud.function.name}-${malagu.cloud.alias.name} + qualifier: ${malagu.cloud.alias.name} + triggerType: http + triggerConfig: + authType: anonymous + methods: [GET, POST, PUT, DELETE, HEAD, PATCH] + customDomain: + name: ${env.MALAGU_DOMAIN || 'auto'} + protocol: HTTP + certConfig: + # certName: xxx + privateKey: "${ env.SSL_KEY ?: 'ssl/domain.key'}" + certificate: "${ env.SSL_PEM ?: 'ssl/domain.pem'}" + routeConfig: + routes: + - path: '/*' + functionName: ${malagu.cloud.function.name} diff --git a/plugins/fc3-plugin/malagu-timer-mode.yml b/plugins/fc3-plugin/malagu-timer-mode.yml new file mode 100644 index 0000000000..961835e939 --- /dev/null +++ b/plugins/fc3-plugin/malagu-timer-mode.yml @@ -0,0 +1,13 @@ +malagu: + cloud: + function: + runtime: nodejs14 + trigger: + name: ${malagu.cloud.function.name}-${malagu.cloud.alias.name}-timer + qualifier: ${malagu.cloud.alias.name} + triggerType: timer + triggerConfig: + payload: malagu-timer + cronExpression: 0 */1 * * * * # every minute + enable: true + \ No newline at end of file diff --git a/plugins/fc3-plugin/malagu.yml b/plugins/fc3-plugin/malagu.yml new file mode 100644 index 0000000000..19d518575b --- /dev/null +++ b/plugins/fc3-plugin/malagu.yml @@ -0,0 +1,20 @@ +malagu: + cloud: + name: alibaba cloud + profilePath: alibaba/profile.yml + regions: + - cn-qingdao + - cn-beijing + - cn-zhangjiakou + - cn-hangzhou + - cn-shanghai + - cn-shenzhen + - cn-huhehaote + - cn-hongkong + - ap-southeast-1 + - ap-southeast-2 + - ap-northeast-1 + - us-west-1 + - us-east-1 + - eu-central-1 + - ap-south-1 diff --git a/plugins/fc3-plugin/package.json b/plugins/fc3-plugin/package.json new file mode 100644 index 0000000000..846503eb13 --- /dev/null +++ b/plugins/fc3-plugin/package.json @@ -0,0 +1,54 @@ +{ + "name": "@malagu/fc3-plugin", + "version": "2.49.12", + "description": "", + "dependencies": { + "@alicloud/fc20230330": "^3.0.2", + "@alicloud/openapi-client": "^0.4.6", + "@alicloud/tea-util": "^1.4.7", + "@malagu/cli-common": "2.49.12", + "@malagu/cloud-plugin": "2.49.12", + "@malagu/code-loader-plugin": "2.49.12", + "axios": "^0.26.1", + "chalk": "^4.1.0", + "fs-extra": "^11.1.1", + "jszip": "^3.7.1" + }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "malagu-component", + "malagu-plugin" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/cellbang/malagu.git" + }, + "bugs": { + "url": "https://github.com/cellbang/malagu/issues" + }, + "homepage": "https://github.com/cellbang/malagu", + "files": [ + "lib", + "src", + "malagu.yml", + "malagu-remote.yml", + "malagu-http-mode.yml", + "malagu-sample-http-mode.yml", + "malagu-timer-mode.yml", + "malagu-mns-topic-mode.yml" + ], + "scripts": { + "lint": "malagu-component lint", + "build": "malagu-component build", + "watch": "malagu-component watch", + "clean": "malagu-component clean", + "test": "malagu-component test:js" + }, + "devDependencies": { + "@malagu/component": "2.49.12" + }, + "gitHead": "bbf636b21ea1a347affcc05a5f6f58b35bedef6d" +} diff --git a/plugins/fc3-plugin/src/hooks/api.ts b/plugins/fc3-plugin/src/hooks/api.ts new file mode 100644 index 0000000000..6dad5403a9 --- /dev/null +++ b/plugins/fc3-plugin/src/hooks/api.ts @@ -0,0 +1,49 @@ +import axios from 'axios'; +const DOMAIN = 'http://domain.devsapp.net'; + +export interface Params { + type: string; + user: string; + region: string; + function: string; + token?: string; +} + +function checkRs(rs: any) { + if (rs.Status !== 'Success') { + throw new Error(rs.Body); + } +} + +function parseParams(params: Params) { + const parsedParams = new URLSearchParams(); + parsedParams.append('type', params.type); + parsedParams.append('region', params.region); + parsedParams.append('user', params.user); + parsedParams.append('function', params.function); + if (params.token) { + parsedParams.append('token', params.token); + } + return parsedParams; +} + +function getCommonHeaders() { + return { 'Content-Type': 'application/x-www-form-urlencoded' }; +} + +export async function token(params: Params) { + const { data } = await axios.post(`${DOMAIN}/token`, parseParams(params), { headers: getCommonHeaders() }); + checkRs(data.Response); + return data.Response; +} + +export async function domain(params: Params) { + const { data } = await axios.post(`${DOMAIN}/domain`, parseParams(params), { headers: getCommonHeaders() }); + checkRs(data.Response); + return data.Response; +} + +export async function verify(params: Params) { + const { data } = await axios.post(`${DOMAIN}/verify`, parseParams(params), { headers: getCommonHeaders() }); + return data.Response; +} diff --git a/plugins/fc3-plugin/src/hooks/build.ts b/plugins/fc3-plugin/src/hooks/build.ts new file mode 100644 index 0000000000..51102364ad --- /dev/null +++ b/plugins/fc3-plugin/src/hooks/build.ts @@ -0,0 +1,19 @@ +import { BuildContext, PathUtil } from '@malagu/cli-common'; +import { join } from 'path'; +import { writeFile, ensureDir } from 'fs-extra'; +import { CloudUtils } from '@malagu/cloud-plugin'; +import { getCodeRootDir } from '@malagu/code-loader-plugin'; + +export default async (context: BuildContext) => { + const { cfg } = context; + const config = CloudUtils.getConfiguration(cfg); + const codeRootDir = getCodeRootDir(PathUtil.getProjectDistPath(), config.function.codeUri); + await ensureDir(codeRootDir); + if (config.function?.runtime === 'custom') { + const destDir = join(codeRootDir, 'bootstrap'); + const bootstrap = config.function.bootstrap; + await writeFile(destDir, `#!/bin/bash\n${bootstrap}`, { mode: 0o755 }); + } + delete config.function.bootstrap; + +}; diff --git a/plugins/fc3-plugin/src/hooks/deploy.ts b/plugins/fc3-plugin/src/hooks/deploy.ts new file mode 100644 index 0000000000..df7433e5d9 --- /dev/null +++ b/plugins/fc3-plugin/src/hooks/deploy.ts @@ -0,0 +1,438 @@ +import { DeployContext, PathUtil, ProjectUtil, SpinnerUtil } from '@malagu/cli-common'; +import { readFile, createWriteStream, remove } from 'fs-extra'; +import { join } from 'path'; +import * as JSZip from 'jszip'; +import { CloudUtils, DefaultProfileProvider } from '@malagu/cloud-plugin'; +import { DefaultCodeLoader } from '@malagu/code-loader-plugin'; +import { createFcClient, getAlias, getCustomDomain, getFunction, getLayer, getTrigger, parseDomain } from './utils'; +import * as fcAPI from './api'; +import { retry } from '@malagu/cli-common/lib/utils'; +import { tmpdir } from 'os'; +import * as chalk from 'chalk'; +import { CodeUri } from '@malagu/code-loader-plugin/lib/code-protocol'; +import { generateUUUID } from '@malagu/cli-common/lib/utils/uuid'; +import FC20230330, * as $fc from '@alicloud/fc20230330'; +import { RuntimeOptions } from '@alicloud/tea-util'; + +let fcClient: FC20230330; +let projectId: string; + +// TODO +export default async (context: DeployContext) => { + const { cfg, pkg } = context; + const cloudConfig = CloudUtils.getConfiguration(cfg); + const { layer, trigger, customDomain, alias, disableProjectId } = cloudConfig; + const functionMeta = cloudConfig.function; + + const profileProvider = new DefaultProfileProvider(); + const { region, account, credentials } = await profileProvider.provide(cloudConfig); + fcClient = await createFcClient(cloudConfig, region, credentials, account); + + console.log(`\nDeploying ${chalk.bold.yellow(pkg.pkg.name)} to the ${chalk.bold.blue(region)} region of ${cloudConfig.name}...`); + console.log(chalk`{bold.cyan - Profile: }`); + console.log(` - AccountId: ${account?.id}`); + console.log(` - Region: ${region}`); + + console.log(chalk`{bold.cyan - FC:}`); + + await publishLayerIfNeed(layer); + + delete functionMeta.callbackWaitsForEmptyEventLoop; + const functionName = await createOrUpdateFunction(functionMeta, disableProjectId); + + const { body: { versionId } } = await fcClient.publishFunctionVersion(functionName, new $fc.PublishFunctionVersionRequest({})); + + await createOrUpdateAlias(alias, functionName, versionId!); + + if (trigger?.triggerType === 'timer') { + await createOrUpdateTimerTrigger(trigger, functionMeta.name); + } else if (trigger?.triggerType === 'http') { + await createOrUpdateHttpTrigger(trigger, functionMeta.name, region, account.id); + } else if (trigger) { + await createOrUpdateTrigger(trigger, functionMeta.name); + } + + if (customDomain?.name) { + for (const route of customDomain.routeConfig.routes) { + route.functionName = route.functionName || functionMeta.name; + route.qualifier = route.qualifier || alias.name; + } + await createOrUpdateCustomDomain(customDomain, alias.name, { + type: 'fc', + user: account.id, + region: region.replace(/_/g, '-').toLocaleLowerCase(), + function: functionMeta.name.replace(/_/g, '-').toLocaleLowerCase() + }); + + } + + console.log('Deploy finished'); + console.log(); + +}; + +async function createOrUpdateHttpTrigger(trigger: any, functionName: string, region: string, accountId: string) { + const { triggerConfig } = trigger; + + const triggerInfo = await createOrUpdateTrigger(trigger, functionName); + const urlInternet: string = triggerInfo?.httpTrigger?.urlInternet || ''; + const urlIntranet: string = triggerInfo?.httpTrigger?.urlIntranet || ''; + + console.log(` - Methods: ${triggerConfig.methods}`); + console.log(chalk` - Url[Internet]: ${chalk.green.bold(urlInternet)}`); + console.log(chalk` - Url[Intranet]: ${chalk.green.bold(urlIntranet)}`); +} + +async function createOrUpdateTimerTrigger(trigger: any, functionName: string) { + const { triggerConfig } = trigger; + + await createOrUpdateTrigger(trigger, functionName); + + console.log(` - Cron: ${triggerConfig.cronExpression}`); + console.log(` - Enable: ${triggerConfig.enable}`); +} + +async function createOrUpdateTrigger(trigger: any, functionName: string) { + const opts = { ...trigger }; + opts.triggerName = opts.name; + delete opts.functionName; + delete opts.name; + + const triggerName = trigger.name; + + let triggerInfo = await getTrigger(fcClient, functionName, triggerName); + if (triggerInfo) { + await SpinnerUtil.start(`Update ${triggerName} trigger`, async () => { + try { + await fcClient.updateTrigger(functionName, triggerName, new $fc.UpdateTriggerRequest({ + body: new $fc.UpdateTriggerInput({ + ...opts, + triggerConfig: JSON.stringify(opts.triggerConfig || {}) + }) + })); + } catch (error) { + if (error.message?.includes('Updating trigger is not supported yet')) { + await fcClient.deleteTrigger(functionName, triggerName); + await fcClient.createTrigger(functionName, new $fc.CreateTriggerRequest({ + body: new $fc.CreateTriggerInput({ + ...opts, + triggerConfig: JSON.stringify(opts.triggerConfig || {}) + }) + })); + return; + } + throw error; + } + }); + } else { + await SpinnerUtil.start(`Create ${triggerName} trigger`, async () => { + const result = await fcClient.createTrigger(functionName, new $fc.CreateTriggerRequest({ + body: new $fc.CreateTriggerInput({ + ...opts, + triggerConfig: JSON.stringify(opts.triggerConfig || {}) + }) + })); + + triggerInfo = result.body; + }); + } + + return triggerInfo; +} + +async function tryCreateProjectId(functionName: string) { + projectId = await ProjectUtil.createProjectId(); + const functionInfo = await getFunction(fcClient, `${functionName}_${projectId}`); + if (functionInfo) { + await tryCreateProjectId(functionName); + } +} + +// TODO +async function parseCode(codeUri: CodeUri | string, withoutCodeLimit: boolean) { + const s3Uri = CloudUtils.parseS3Uri(codeUri); + let code: JSZip | undefined; + if (!s3Uri) { + const codeLoader = new DefaultCodeLoader(); + code = await codeLoader.load(PathUtil.getProjectDistPath(), codeUri); + } + + if (s3Uri) { + return { + ossBucketName: s3Uri.bucket, + ossObjectName: s3Uri.key + }; + } else { + if (withoutCodeLimit === true) { + const _tmpdir = tmpdir(); + const zipFile = join(_tmpdir, generateUUUID()); + return new Promise((resolve, reject) => + code!.generateNodeStream({ type: 'nodebuffer', platform: 'UNIX', compression: 'DEFLATE', streamFiles: true }) + .pipe(createWriteStream(zipFile)) + .on('finish', () => { + resolve({ + zipFile + }); + }) + .on('error', error => { + reject(error); + }) + ); + } + return { zipFile: await code!.generateAsync({ type: 'base64', platform: 'UNIX', compression: 'DEFLATE' }) }; + } +} + +// TODO +async function publishLayerIfNeed(layer: any = {}) { + if (!layer.name || !layer.codeUri) { + return; + } + const layerInfo = await getLayer(fcClient, layer.name); + if (!layerInfo || layer.sync) { + const opts = { ...layer }; + delete opts.codeUri; + delete opts.name; + delete layer.sync; + + await SpinnerUtil.start(`Publish ${layer.name} layer`, async () => { + const code: any = await parseCode(layer.codeUri, layer.withoutCodeLimit); + if (layer.withoutCodeLimit && (code).zipFile) { + await fcClient.createLayerVersion(layer.name, { + ...opts, + codeConfig: { + zipFilePath: (code).zipFile + } + }); + } else { + await fcClient.createLayerVersionWithOptions(layer.name, new $fc.CreateLayerVersionRequest({ + body: new $fc.CreateLayerVersionInput({ + ...opts, + code: new $fc.InputCodeLocation(code) + }) + }), {}, new RuntimeOptions({ readTimeout: 600000 })); + } + }); + } else { + await SpinnerUtil.start(`Skip ${layer.name} layer`, async () => { }); + } +} + +async function createOrUpdateFunction(functionMeta: any, disableProjectId: boolean): Promise { + const opts = { ...functionMeta }; + const sync = opts.sync; + opts.environmentVariables = opts.env; + delete opts.sync; + delete opts.name; + delete opts.env; + delete opts.codeUri; + + if (sync !== 'onlyUpdateCode' && opts.layers) { + const newLayers = []; + for (const layer of opts.layers) { + if (layer) { + if (layer.includes('#')) { + newLayers.push(layer); + } else { + const layerInfo = await getLayer(fcClient, layer); + newLayers.push(layerInfo?.layerVersionArn); + } + } + } + opts.layers = newLayers; + } + + projectId = await ProjectUtil.getProjectId(); + let functionInfo: any; + + if (disableProjectId) { + functionInfo = await getFunction(fcClient, functionMeta.name); + } else { + if (!projectId) { + await tryCreateProjectId(functionMeta.name); + await ProjectUtil.saveProjectId(projectId); + functionMeta.name = `${functionMeta.name}_${projectId}`; + } else { + functionMeta.name = `${functionMeta.name}_${projectId}`; + functionInfo = await getFunction(fcClient, functionMeta.name); + } + } + + const code: any = await parseCode(functionMeta.codeUri, functionMeta.withoutCodeLimit); + if (functionInfo) { + delete opts.runtime; + await SpinnerUtil.start(`Update ${functionMeta.name} function${sync === 'onlyUpdateCode' ? ' (only update code)' : ''}`, async () => { + await fcClient.updateFunction(functionMeta.name, new $fc.UpdateFunctionRequest({ + body: new $fc.UpdateFunctionInput({ + ...(sync === 'onlyUpdateCode' ? {} : opts), + code: new $fc.InputCodeLocation(code) + }) + })); + }); + } else { + opts.functionName = functionMeta.name; + await SpinnerUtil.start(`Create ${functionMeta.name} function`, async () => { + await fcClient.createFunction(new $fc.CreateFunctionRequest({ + body: new $fc.CreateFunctionInput({ + ...opts, + code: new $fc.InputCodeLocation(code) + }) + })); + }); + } + if (functionMeta.withoutCodeLimit && code.zipFile) { + remove(code.zipFile).catch(() => {}); + } + + return functionMeta.name; +} + +// TODO +async function createOrUpdateCustomDomain(customDomain: any, qualifier: string, params: fcAPI.Params) { + const { name, protocol, certConfig, routeConfig } = customDomain; + const domainName = name; + const opts: any = { + protocol + }; + + if (domainName === 'auto') { + await SpinnerUtil.start('Generated custom domain', async () => { + console.log('暂不支持domainName = auto'); + // domainName = await genDomain(params); + return; + }); + } + + if (certConfig?.certName) { + opts.certConfig = { ...certConfig }; + const privateKey = certConfig.privateKey; + const certificate = certConfig.certificate; + + if (privateKey?.endsWith('.key')) { + opts.certConfig.privateKey = await readFile(privateKey, 'utf-8'); + } + if (certificate?.endsWith('.pem')) { + opts.certConfig.certificate = await readFile(certificate, 'utf-8'); + } + } + + if (routeConfig) { + opts.routeConfig = routeConfig; + } + const customDomainInfo = await getCustomDomain(fcClient, domainName); + if (customDomainInfo) { + const { data } = customDomainInfo; + const routes: any[] = []; + if (data?.routeConfig?.routes) { + for (const route of data.routeConfig.routes) { + const target = opts.routeConfig.routes.find((r: any) => r.path === route.path); + if (target) { + routes.push({ ...route, ...target }); + opts.routeConfig.routes.splice(opts.routeConfig.routes.findIndex((r: any) => r.path === target.path), 1); + } else { + routes.push(route); + } + } + opts.routeConfig.routes = [...opts.routeConfig.routes, ...routes]; + } + await SpinnerUtil.start(`Update ${domainName} custom domain`, async () => { + await fcClient.updateCustomDomain(domainName, opts); + }); + } else { + opts.domainName = domainName; + await SpinnerUtil.start(`Create ${domainName} custom domain`, async () => { + retry(async () => { + await fcClient.createCustomDomain(new $fc.CreateCustomDomainRequest({ + body: new $fc.CreateCustomDomainInput(opts) + })); + }, 1000, 5); + }); + } + let path = ''; + if (opts.routeConfig?.routes?.length) { + for (const route of opts.routeConfig.routes) { + if (route.qualifier === qualifier) { + path = route.path?.split('*')[0] || ''; + } + } + } + console.log(chalk` - Url: ${chalk.green.bold( + `${protocol.includes('HTTPS') ? 'https' : 'http'}://${domainName}${path}`)}`); +} + +async function createOrUpdateAlias(alias: any, functionName: string, versionId: string) { + const aliasInfo = await getAlias(fcClient, alias.name, functionName); + if (aliasInfo) { + await SpinnerUtil.start(`Update ${alias.name} alias to version ${versionId}`, async () => { + await fcClient.updateAlias(functionName, alias.name, new $fc.UpdateAliasRequest({ + body: new $fc.UpdateAliasInput({ versionId }) + })); + }); + } else { + await SpinnerUtil.start(`Create ${alias.name} alias to version ${versionId}`, async () => { + await fcClient.createAlias(functionName, new $fc.CreateAliasRequest({ + body: new $fc.CreateAliasInput({ + aliasName: alias.name, + versionId: versionId, + }) + })); + }); + } +} + +// TODO +export async function genDomain(params: fcAPI.Params) { + const functionName = 'serverless-devs-domain'; + const triggerName = 'httpTrigger'; + + const { Body } = await fcAPI.token(params); + const token = Body.Token; + + const functionConfig: Omit<$fc.UpdateFunctionInput | $fc.CreateFunctionInput, 'toMap'> = { + functionName, + handler: 'index.handler', + runtime: 'nodejs8', + environmentVariables: { token }, + }; + + try { + await fcClient.updateFunction(functionName, new $fc.UpdateFunctionRequest({ + body: functionConfig + })); + } catch (ex) { + if (ex.code === 'FunctionNotFound') { + // function code is `exports.handler = (req, resp, context) => resp.send(process.env.token || '');`; + // eslint-disable-next-line max-len + const zipFile = 'UEsDBAoAAAAIABULiFLOAhlFSQAAAE0AAAAIAAAAaW5kZXguanMdyMEJwCAMBdBVclNBskCxuxT9UGiJNgnFg8MX+o4Pc3R14/OQdkOpUFQ8mRQ2MtUujumJyv4PG6TFob3CjCEve78gtBaFkLYPUEsBAh4DCgAAAAgAFQuIUs4CGUVJAAAATQAAAAgAAAAAAAAAAAAAALSBAAAAAGluZGV4LmpzUEsFBgAAAAABAAEANgAAAG8AAAAAAA=='; + functionConfig.code = new $fc.InputCodeLocation({ zipFile }); + await fcClient.createFunction(new $fc.CreateFunctionRequest({ + body: functionConfig + })); + } else { + throw ex; + } + } + + try { + await fcClient.createTrigger(functionName, new $fc.CreateTriggerRequest({ + body: new $fc.CreateTriggerInput({ + triggerName, + triggerType: 'http', + triggerConfig: { + AuthType: 'anonymous', + Methods: ['POST', 'GET'], + }, + }) + })); + } catch (ex) { + if (ex.code !== 'TriggerAlreadyExists') { + throw ex; + } + } + + await fcAPI.domain({ ...params, token }); + + await fcClient.deleteTrigger(functionName, triggerName); + await fcClient.deleteFunction(functionName); + return Body.Domain || parseDomain(params); +} diff --git a/plugins/fc3-plugin/src/hooks/info.ts b/plugins/fc3-plugin/src/hooks/info.ts new file mode 100644 index 0000000000..9ba0dc0e4d --- /dev/null +++ b/plugins/fc3-plugin/src/hooks/info.ts @@ -0,0 +1,53 @@ +import { InfoContext, ProjectUtil } from '@malagu/cli-common'; +import { CloudUtils, DefaultProfileProvider } from '@malagu/cloud-plugin'; +import { getAlias, getFunction, getCustomDomain, getTrigger, getLayer, createFcClient } from './utils'; +import * as chalk from 'chalk'; +import type FC20230330 from '@alicloud/fc20230330'; + +let fcClient: FC20230330; + +export default async (context: InfoContext) => { + const { cfg, pkg } = context; + const cloudConfig = CloudUtils.getConfiguration(cfg); + const { layer, trigger, alias, customDomain, disableProjectId } = cloudConfig; + + const profileProvider = new DefaultProfileProvider(); + const { region, account, credentials } = await profileProvider.provide(cloudConfig); + fcClient = await createFcClient(cloudConfig, region, credentials, account); + + console.log(`\nGetting ${chalk.bold.yellow(pkg.pkg.name)} from the ${chalk.bold.blue(region)} region of ${cloudConfig.name}...`); + console.log(chalk`{bold.cyan - Profile: }`); + console.log(` - AccountId: ${account?.id}`); + console.log(` - Region: ${region}`); + + const projectId = await ProjectUtil.getProjectId(); + if (!projectId && !disableProjectId) { + return; + } + const functionMeta = cloudConfig.function; + functionMeta.name = disableProjectId ? functionMeta.name : `${functionMeta.name}_${projectId}`; + const functionName = functionMeta.name; + + const functionInfo = await getFunction(fcClient, functionName, true); + context.output.functionInfo = functionInfo; + if (!functionInfo) { + return; + } + + context.output.layerInfo = await getLayer(fcClient, layer?.name, true); + + context.output.aliasInfo = await getAlias(fcClient, alias.name, functionName, true); + + if (trigger?.name) { + context.output.triggerInfo = await getTrigger(fcClient, functionName, trigger.name, region, account.id, true); + } + + if (customDomain?.name) { + context.output.groupInfo = await getCustomDomain(fcClient, customDomain.name, true, alias.name, { + type: 'fc', + user: account.id, + region: region.replace(/_/g, '-').toLocaleLowerCase(), + function: functionMeta.name.replace(/_/g, '-').toLocaleLowerCase() + }); + } +}; diff --git a/plugins/fc3-plugin/src/hooks/props.ts b/plugins/fc3-plugin/src/hooks/props.ts new file mode 100644 index 0000000000..aa79fd434a --- /dev/null +++ b/plugins/fc3-plugin/src/hooks/props.ts @@ -0,0 +1,19 @@ +import { PropsContext } from '@malagu/cli-common'; +import { BACKEND_TARGET } from '@malagu/cli-common/lib/constants'; +import { CloudUtils, DefaultProfileProvider } from '@malagu/cloud-plugin'; + +export async function before(context: PropsContext) { + const { props, cfg, target } = context; + if (target === BACKEND_TARGET && props.mode && props.mode.includes('remote')) { + context.spinner?.stop(); + const cloudConfig = CloudUtils.getConfiguration(cfg); + const profileProvider = new DefaultProfileProvider(); + const profile = await profileProvider.provide(cloudConfig, true); + if (!cloudConfig.account?.id) { + cloudConfig.account = profile?.account; + } + if (!cloudConfig.region) { + cloudConfig.region = profile?.region; + } + } +}; diff --git a/plugins/fc3-plugin/src/hooks/utils.ts b/plugins/fc3-plugin/src/hooks/utils.ts new file mode 100644 index 0000000000..7f53182812 --- /dev/null +++ b/plugins/fc3-plugin/src/hooks/utils.ts @@ -0,0 +1,159 @@ +import { Credentials, Account } from '@malagu/cloud-plugin'; +import FC20230330, * as $fc from '@alicloud/fc20230330'; +import * as openapi from '@alicloud/openapi-client'; +import * as chalk from 'chalk'; +import { Params } from './api'; + +// TODO +// parseDomain的问题 +// methods的问题 (控制台不支持methods) +export async function getCustomDomain(client: FC20230330, customDomainName: string, print = false, qualifier?: string, params?: Params) { + try { + if (customDomainName === 'auto') { + customDomainName = parseDomain(params!); + } + const result = await client.getCustomDomain(customDomainName); + if (print) { + console.log(chalk`{bold.cyan - CustomDomain: }`); + console.log(` - DomainName: ${result.body.domainName}`); + console.log(` - Protocol: ${result.body.protocol}`); + console.log(` - LastModifiedTime: ${result.body.lastModifiedTime}`); + const routeConfig = result.body.routeConfig; + let path = ''; + if (routeConfig?.routes?.length) { + console.log(' - RouteConfig: '); + for (const route of routeConfig.routes) { + console.log(` - Path: ${route.path}`); + console.log(` Methods: ${route.methods}`); + + if (route.qualifier === qualifier) { + path = route.path?.split('*')[0] || ''; + } + } + } + console.log(` - ApiUrl: ${result.body.protocol?.includes('HTTPS') ? 'https' : 'http'}://${customDomainName}${path}`); + } + return result; + } catch (ex) { + if (ex.code !== 'DomainNameNotFound') { + throw ex; + } + } +} + +export async function getAlias(client: FC20230330, aliasName: string, functionName: string, print = false) { + + try { + const result = await client.getAlias(functionName, aliasName); + if (print) { + console.log(chalk`{bold.cyan - Alias: }`); + console.log(` - AliasName: ${result.body.aliasName}`); + console.log(` - functionName: ${functionName}`); + console.log(` - VersionId: ${result.body.versionId}`); + console.log(` - LastModifiedTime: ${result.body.lastModifiedTime}`); + } + return result; + } catch (ex) { + if (ex.code !== 'AliasNotFound') { + throw ex; + } + } +} + +export async function getLayer(client: FC20230330, prefix?: string, print = false) { + if (!prefix) { + return; + } + const result = await client.listLayers(new $fc.ListLayersRequest({ prefix, limit: 1 })); + const layer = result.body.layers?.[0]; + if (layer && print) { + console.log(chalk`{bold.cyan - Layer: }`); + console.log(` - LayerName: ${layer.layerName}`); + console.log(` - Version: ${layer.version}`); + console.log(` - Description: ${layer.description}`); + console.log(` - CompatibleRuntime: ${layer.compatibleRuntime}`); + console.log(` - Arn: ${layer.layerVersionArn}`); + console.log(` - CreateTime: ${layer.createTime}`); + } + return layer; +} + +export async function getFunction(client: FC20230330, functionName: string, print = false): Promise<$fc.Function | undefined> { + try { + const result = await client.getFunction(functionName, new $fc.GetFunctionRequest({})); + if (print) { + console.log(chalk`{bold.cyan - Function: }`); + console.log(` - FunctionName: ${result.body.functionName}`); + console.log(` - Cpu: ${result.body.cpu}`); + console.log(` - MemorySize: ${result.body.memorySize}`); + console.log(` - DiskSize: ${result.body.diskSize}`); + console.log(` - Runtime: ${result.body.runtime}`); + console.log(` - Timeout: ${result.body.timeout}`); + console.log(` - Concurrency: ${result.body.instanceConcurrency}`); + console.log(` - CAPort: ${result.body.caPort}`); + console.log(` - LastModifiedTime: ${result.body.lastModifiedTime}`); + } + return result.body; + + } catch (ex) { + if (ex.code !== 'FunctionNotFound') { + throw ex; + } + } +} + +export async function getTrigger(client: FC20230330, functionName: string, triggerName: string, region?: string, accountId?: string, print = false) { + + try { + const result = await client.getTrigger(functionName, triggerName); + if (print) { + console.log(chalk`{bold.cyan - Trigger: }`); + console.log(` - TriggerName: ${result.body.triggerName}`); + console.log(` - TriggerType: ${result.body.triggerType}`); + console.log(` - Qualifier: ${result.body.qualifier}`); + console.log(` - InvocationRole: ${result.body.invocationRole}`); + console.log(` - SourceArn: ${result.body.sourceArn}`); + console.log(` - LastModifiedTime: ${result.body.lastModifiedTime}`); + let triggerConfig: Record = {}; + try { + if (result.body.triggerConfig) { + triggerConfig = JSON.parse(result.body.triggerConfig); + } + } catch (_) {} + if (result.body.triggerType === 'http') { + console.log(` - Methods: ${triggerConfig.methods}`); + if (region && accountId) { + console.log(` - Url[Internet]: ${result.body.httpTrigger?.urlInternet}`); + } + } else if (result.body.triggerType === 'timer') { + console.log(` - Cron: ${triggerConfig.cronExpression}`); + console.log(` - Enable: ${triggerConfig.enable}`); + } + } + return result.body; + + } catch (ex) { + if (ex.code !== 'TriggerNotFound') { + throw ex; + } + } + +} + +export async function createFcClient(cloudConfig: any, region: string, credentials: Credentials, account: Account) { + const fcClient = new FC20230330(new openapi.Config({ + accessKeyId: credentials.accessKeyId, + accessKeySecret: credentials.accessKeySecret, + securityToken: credentials.token, + regionId: region, + timeout: cloudConfig.timeout, + endpoint: `${account.id}.cn-zhangjiakou.fc.aliyuncs.com` + })); + + return fcClient; +} + +// TODO +export function parseDomain(params: Params) { + return `${params.function}.${params.user}.${params.region}.fc.devsapp.net`.toLocaleLowerCase(); +} diff --git a/plugins/fc3-plugin/src/package.spec.ts b/plugins/fc3-plugin/src/package.spec.ts new file mode 100644 index 0000000000..f1076d0ec3 --- /dev/null +++ b/plugins/fc3-plugin/src/package.spec.ts @@ -0,0 +1,4 @@ +describe('fc3 plugin package', () => { + + it('support code coverage statistics', () => {}); +}); diff --git a/tsconfig.json b/tsconfig.json index 11ccbaeea3..d04702d7db 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -277,6 +277,9 @@ ], "nest-app/*": [ "examples/nest-app/*" + ], + "@malagu/fc3-plugin/lib/*": [ + "plugins/fc3-plugin/src/*" ] } }, diff --git a/yarn.lock b/yarn.lock index 6f98599721..dd546615cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -264,6 +264,35 @@ dependencies: "@alicloud/pop-core" "^1.4.0" +"@alicloud/credentials@^2": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@alicloud/credentials/-/credentials-2.3.0.tgz#941233a07ba74cd2fdaa3f6a5d2a3cca5a10c184" + integrity sha512-x0vf/m1BzkqYXAj2Hkd22O35josx5P4VCzq/9EvTBjA7aGLX/P6JDz7QVp+gnhLjPJyvwAbErvJRYq4gIo4IMA== + dependencies: + "@alicloud/tea-typescript" "^1.5.3" + httpx "^2.2.0" + ini "^1.3.5" + kitx "^2.0.0" + +"@alicloud/endpoint-util@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@alicloud/endpoint-util/-/endpoint-util-0.0.1.tgz#b237f5e04e373abb54c42119377b30bd6afb1a7c" + integrity sha512-+pH7/KEXup84cHzIL6UJAaPqETvln4yXlD9JzlrqioyCSaWxbug5FUobsiI6fuUOpw5WwoB3fWAtGbFnJ1K3Yg== + dependencies: + "@alicloud/tea-typescript" "^1.5.1" + kitx "^2.0.0" + +"@alicloud/fc20230330@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@alicloud/fc20230330/-/fc20230330-3.0.2.tgz#bd861ece4ce8f0560a177a184df01445195cf269" + integrity sha512-DaLRPCZ2AfBnA8CtUBG2OIBj4rftfsRMqAENexITMY0DU4l3xCHPD/w0wCj0OitrmF2GzmN2QLIxZTcjPVwQ3A== + dependencies: + "@alicloud/endpoint-util" "^0.0.1" + "@alicloud/openapi-client" "^0.4.4" + "@alicloud/openapi-util" "^0.3.2" + "@alicloud/tea-typescript" "^1.7.1" + "@alicloud/tea-util" "^1.4.7" + "@alicloud/fc2@^2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@alicloud/fc2/-/fc2-2.6.0.tgz#f119b86a4a488c7c2c683cd9f196bf42582c14a6" @@ -277,6 +306,36 @@ kitx "^1.2.0" ws "^8.2.3" +"@alicloud/gateway-spi@^0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@alicloud/gateway-spi/-/gateway-spi-0.0.8.tgz#1d251986ed40d8b98690dcac8128fec0c56f0f53" + integrity sha512-KM7fu5asjxZPmrz9sJGHJeSU+cNQNOxW+SFmgmAIrITui5hXL2LB+KNRuzWmlwPjnuA2X3/keq9h6++S9jcV5g== + dependencies: + "@alicloud/credentials" "^2" + "@alicloud/tea-typescript" "^1.7.1" + +"@alicloud/openapi-client@^0.4.4", "@alicloud/openapi-client@^0.4.6": + version "0.4.6" + resolved "https://registry.yarnpkg.com/@alicloud/openapi-client/-/openapi-client-0.4.6.tgz#6e354cf94cddb56322f97569714a31fbbb12cb34" + integrity sha512-cyhUQOJehLRslHy2l+lsginiyXdzfV7yF7b9EJcxzGG7zHAEX0XF3OJvfo13n7WgiqCzt9suQBatJz7b5F+14A== + dependencies: + "@alicloud/credentials" "^2" + "@alicloud/gateway-spi" "^0.0.8" + "@alicloud/openapi-util" "^0.3.1" + "@alicloud/tea-typescript" "^1.7.1" + "@alicloud/tea-util" "^1.4.5" + "@alicloud/tea-xml" "0.0.2" + +"@alicloud/openapi-util@^0.3.1", "@alicloud/openapi-util@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@alicloud/openapi-util/-/openapi-util-0.3.2.tgz#d245e7e466d9fdf6945551c5b7c39c17a6596f1c" + integrity sha512-EC2JvxdcOgMlBAEG0+joOh2IB1um8CPz9EdYuRfTfd1uP8Yc9D8QRUWVGjP6scnj6fWSOaHFlit9H6PrJSyFow== + dependencies: + "@alicloud/tea-typescript" "^1.7.1" + "@alicloud/tea-util" "^1.3.0" + kitx "^2.1.0" + sm3 "^1.0.3" + "@alicloud/pop-core@^1.3.3", "@alicloud/pop-core@^1.4.0": version "1.7.12" resolved "https://registry.yarnpkg.com/@alicloud/pop-core/-/pop-core-1.7.12.tgz#007d21aacc926c231c02e4c903deb1b5a188f418" @@ -295,6 +354,31 @@ dependencies: "@alicloud/pop-core" "^1.3.3" +"@alicloud/tea-typescript@^1", "@alicloud/tea-typescript@^1.5.1", "@alicloud/tea-typescript@^1.5.3", "@alicloud/tea-typescript@^1.7.1": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@alicloud/tea-typescript/-/tea-typescript-1.8.0.tgz#aa9b04b6ee53e1b22aa51e224a950ea5bcd966e9" + integrity sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ== + dependencies: + "@types/node" "^12.0.2" + httpx "^2.2.6" + +"@alicloud/tea-util@^1.3.0", "@alicloud/tea-util@^1.4.5", "@alicloud/tea-util@^1.4.7": + version "1.4.7" + resolved "https://registry.yarnpkg.com/@alicloud/tea-util/-/tea-util-1.4.7.tgz#40748613c3751f5373ffa8e5a0e892602ef3f78c" + integrity sha512-Lrpfk9kxihHsit3oMoeIMjk783AxjOvzMhLAbZcIzazKiVg3Zk/209XDe9r1lXqxII59j3V4rhC9X14y6WGYyg== + dependencies: + "@alicloud/tea-typescript" "^1.5.1" + kitx "^2.0.0" + +"@alicloud/tea-xml@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@alicloud/tea-xml/-/tea-xml-0.0.2.tgz#7c97a38255d5e4f009c437facd3a2afc0ef17f45" + integrity sha512-Xs7v5y7YSNSDDYmiDWAC0/013VWPjS3dQU4KezSLva9VGiTVPaL3S7Nk4NrTmAYCG6MKcrRj/nGEDIWL5KRoPg== + dependencies: + "@alicloud/tea-typescript" "^1" + "@types/xml2js" "^0.4.5" + xml2js "^0.4.22" + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -3692,7 +3776,7 @@ resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.2.2.tgz#825bf5c214c3ab84d0b23fef2c8eb898f3ff8717" integrity sha512-MUSpfpW0yZbTgjekDbH0shMYBUD+X/uJJJMm9LXN1d5yjl5lCY1vN/eWKD6D1tOtjA6206K0zcIPnUaFMurdNA== -"@types/node@*", "@types/node@16", "@types/node@18", "@types/node@^14": +"@types/node@*", "@types/node@16", "@types/node@18", "@types/node@^12.0.2", "@types/node@^14", "@types/node@^20": version "18.17.12" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.12.tgz#c6bd7413a13e6ad9cfb7e97dd5c4e904c1821e50" integrity sha512-d6xjC9fJ/nSnfDeU0AMDsaJyb1iHsqCSOdi84w4u+SlN/UgQdY5tRhpMzaFYsI4mnpvgTivEaQd0yOUhAtOnEQ== @@ -4007,6 +4091,13 @@ dependencies: "@types/node" "*" +"@types/xml2js@^0.4.5": + version "0.4.14" + resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a" + integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -10348,6 +10439,14 @@ httpx@^2.1.1, httpx@^2.1.2: "@types/node" "^14" debug "^4.1.1" +httpx@^2.2.0, httpx@^2.2.6: + version "2.3.0" + resolved "https://registry.yarnpkg.com/httpx/-/httpx-2.3.0.tgz#2458ddc7b235899fa0f52882b0bb7df3e4d0b499" + integrity sha512-DdjwCJ+MauS+V/ta4/DFVGMolHytaS3oEy+Zh2Zzu14NVczqQ0lWexY5Elg52prEJxTzRboBNkSttkXOT/LDCw== + dependencies: + "@types/node" "^20" + debug "^4.1.1" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -10532,7 +10631,7 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: +ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -11760,6 +11859,13 @@ kitx@^1.2.0, kitx@^1.2.1: resolved "https://registry.yarnpkg.com/kitx/-/kitx-1.3.0.tgz#ab3ee7c598d2b1d629fd55568f868c4440c200ea" integrity sha512-fhBqFlXd0GkKTB+8ayLfpzPUw+LHxZlPAukPNBD1Om7JMeInT+/PxCAf1yLagvD+VKoyWhXtJR68xQkX/a0wOQ== +kitx@^2.0.0, kitx@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/kitx/-/kitx-2.1.0.tgz#fc7fbf78eb6ed7a5a3fd2d7afb3011e29d0e44c8" + integrity sha512-C/5v9MtIX7aHGOjwn5BmrrbNkJSf7i0R5mRzmh13GSAdRqQ7bYQo/Su2pTYNylFicqKNTVX3HML9k1u8k51+pQ== + dependencies: + "@types/node" "^12.0.2" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -17133,6 +17239,11 @@ sm-crypto@^0.3.7: dependencies: jsbn "^1.1.0" +sm3@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sm3/-/sm3-1.0.3.tgz#0051f0cc948c983944843136e7baa244eec5cd49" + integrity sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g== + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -19718,7 +19829,7 @@ xml2js@0.4.19: sax ">=0.6.0" xmlbuilder "~9.0.1" -xml2js@^0.4.16, xml2js@^0.4.17, xml2js@^0.4.19, xml2js@^0.4.23: +xml2js@^0.4.16, xml2js@^0.4.17, xml2js@^0.4.19, xml2js@^0.4.22, xml2js@^0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==