diff --git a/packages/engine/package.json b/packages/engine/package.json index 1ba5def..2c742b5 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -1,6 +1,6 @@ { "name": "@serverless-devs/engine", - "version": "0.1.6-beta.3", + "version": "0.1.6-beta.13", "description": "a engine lib for serverless-devs", "main": "lib/index.js", "scripts": { diff --git a/packages/engine/src/actions/index.ts b/packages/engine/src/actions/index.ts index 37bf68c..2e8938c 100644 --- a/packages/engine/src/actions/index.ts +++ b/packages/engine/src/actions/index.ts @@ -1,5 +1,5 @@ import { IAction, IActionLevel, IActionType, IAllowFailure, IComponentAction, IHookType, IPluginAction, IRunAction, getInputs } from '@serverless-devs/parse-spec'; -import { isEmpty, filter, includes, set, get } from 'lodash'; +import { isEmpty, filter, includes, set, get, cloneDeep } from 'lodash'; import * as utils from '@serverless-devs/utils'; import { DevsError, ETrackerType } from '@serverless-devs/utils'; import fs from 'fs-extra'; @@ -27,7 +27,8 @@ interface IRecord { interface IOptions { hookLevel: `${IActionLevel}`; - projectName?: string; + projectName?: string; // 资源名称(resources下的key) + appName?: string; // 项目名称(s.yaml的name字段) logger: ILoggerInstance; skipActions?: boolean; } @@ -246,8 +247,29 @@ You can still use them now, but we suggest to modify them.`) const instance = await loadComponent(hook.value, { logger: this.logger }); // Determine the inputs for the plugin based on the record's pluginOutput. const inputs = isEmpty(this.record.pluginOutput) ? this.inputs : this.record.pluginOutput; + // 添加hook上下文信息 + // level: 'resource' 表示资源级别hook, 'project' 表示项目级别hook(全局) + const isResourceLevel = this.option.hookLevel === IActionLevel.PROJECT; + const command = this.record.command || ''; + const hookType = hook.hookType || ''; + const inputsWithHookContext = { + ...inputs, + hookContext: { + hookType: hookType, // 例如: 'pre', 'fail', 'success', 'complete' + hookName: command ? `${hookType}-${command}` : hookType, // 例如: 'fail-deploy', 'complete-deploy' + command: command, // 例如: 'deploy', 'remove' + actionType: hook.actionType || 'plugin', // 'plugin' + level: isResourceLevel ? 'resource' : 'project', // 'resource'=资源级别, 'project'=项目级别(全局) + projectName: this.option.appName || undefined, // 项目名称(s.yaml的name字段) + resourceName: isResourceLevel ? (this.option.projectName || undefined) : undefined, // 资源名称(仅resource级别有值) + }, + }; // Execute the plugin with the determined inputs and provided arguments. - this.record.pluginOutput = await instance(inputs, hook.args, this.logger); + this.record.pluginOutput = await instance(inputsWithHookContext, hook.args, this.logger); + // If prop 'replace_output' is true, replace the record's step output with the plugin output. + if (hook.replace_output) { + this.record.step.output = cloneDeep(this.record.pluginOutput); + } } catch (e) { const error = e as Error; // Check if the failure is allowed based on the record's allowFailure setting. diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts index 39d3486..7569284 100644 --- a/packages/engine/src/index.ts +++ b/packages/engine/src/index.ts @@ -121,6 +121,7 @@ class Engine { // 初始化全局的 action this.globalActionInstance = new Actions(yaml.actions, { hookLevel: IActionLevel.GLOBAL, + appName: get(this.spec, 'yaml.appName'), // 项目名称 logger: this.logger, skipActions: this.spec.skipActions, }); @@ -393,19 +394,51 @@ class Engine { const projectInfo = get(this.info, item.projectName); if (!projectInfo || isEmpty(projectInfo) || isNil(projectInfo)) { this.logger.write(`${chalk.gray(`execute info command of [${item.projectName}]...`)}`); + let args = cloneDeep(this.options.args); + if (args) { + args[0] = 'info'; + } + // 20250520: support action for ${resources.xx.info.xx} + const newParseSpecInstance = new ParseSpec(get(this.spec, 'yaml.path'), { + argv: args, + logger: this.logger, + }); + newParseSpecInstance.start(); + const newAction = newParseSpecInstance.parseActions(item.actions, IActionLevel.PROJECT); + const newActionInstance = new Actions(newAction, { + hookLevel: IActionLevel.PROJECT, + projectName: item.projectName, // 资源名称 + appName: get(this.spec, 'yaml.appName'), // 项目名称 + logger: item.logger, + skipActions: this.spec.skipActions, + }); + newActionInstance.setValue('magic', await this.getFilterContext(item)); + newActionInstance.setValue('step', item); + newActionInstance.setValue('command', 'info'); + const newInputs = await this.getProps(item); + newActionInstance.setValue('componentProps', newInputs); + const pluginResult = await this.actionInstance?.start(IHookType.PRE, newInputs) || {}; const spec = cloneDeep(this.spec); spec['command'] = 'info'; // 20240912 when -f, don't throw error - const { f, force } = parseArgv(this.options.args); - let res = {} - if (f || force) { - try { - res = await this.doSrc(item, {}, spec); - } catch(e) { - this.logger.warn(get(e, 'data')); + // 20250521: remove previous logic + let res: any = {} + try { + res = await this.doSrc(item, pluginResult, spec); + set(newInputs, 'output', res); + const pluginSuccessResult = await newActionInstance?.start(IHookType.SUCCESS, newInputs); + if (!isEmpty(pluginSuccessResult)) { + res = get(pluginSuccessResult, 'step.output'); } - } else { - res = await this.doSrc(item, {}, spec); + } catch (e) { + this.logger.warn(get(e, 'data')); + // 将error信息添加到inputs中传递给fail hook + set(newInputs, 'errorContext', e); + res = get(await newActionInstance?.start(IHookType.FAIL, newInputs), 'step.output') || {}; + } + const pluginCompleteResult = await newActionInstance?.start(IHookType.COMPLETE, newInputs); + if (!isEmpty(pluginCompleteResult)) { + res = get(pluginCompleteResult, 'step.output'); } set(this.info, item.projectName, res); this.logger.write(`${chalk.gray(`[${item.projectName}] info command executed.`)}`); @@ -501,7 +534,12 @@ class Engine { } catch (error) { // On error, attempt to trigger the project's fail hook and update the recorded context. try { - const res = await this.actionInstance?.start(IHookType.FAIL, this.record.componentProps); + // 将error信息添加到inputs中传递给fail hook + const failInputs = { + ...this.record.componentProps, + errorContext: error, + }; + const res = await this.actionInstance?.start(IHookType.FAIL, failInputs); this.recordContext(item, get(res, 'pluginOutput', {})); } catch (error) { this.record.status = STEP_STATUS.FAILURE; @@ -529,10 +567,13 @@ class Engine { // Attempt to trigger the project's complete hook regardless of status. try { - const res = await this.actionInstance?.start(IHookType.COMPLETE, { + // complete hook需要包含error信息(如果有的话) + const completeInputs = { ...this.record.componentProps, output: get(item, 'output', {}), - }); + errorContext: get(item, 'error'), + }; + const res = await this.actionInstance?.start(IHookType.COMPLETE, completeInputs); this.recordContext(item, get(res, 'pluginOutput', {})); } catch (error) { this.record.status = STEP_STATUS.FAILURE; @@ -578,7 +619,8 @@ class Engine { debug(`project actions: ${JSON.stringify(newAction)}`); this.actionInstance = new Actions(newAction, { hookLevel: IActionLevel.PROJECT, - projectName: item.projectName, + projectName: item.projectName, // 资源名称 + appName: get(this.spec, 'yaml.appName'), // 项目名称 logger: item.logger, skipActions: this.spec.skipActions, }); diff --git a/packages/load-application/package.json b/packages/load-application/package.json index 56b12b2..44435f2 100644 --- a/packages/load-application/package.json +++ b/packages/load-application/package.json @@ -1,6 +1,6 @@ { "name": "@serverless-devs/load-application", - "version": "0.0.16-beta.1", + "version": "0.0.16-beta.3", "description": "load application for serverless-devs", "main": "lib/index.js", "scripts": { @@ -18,7 +18,7 @@ }, "dependencies": { "@serverless-cd/debug": "^4.3.4", - "@serverless-devs/art-template": "^4.13.16-beta.24", + "@serverless-devs/art-template": "^4.13.16-beta.39", "@serverless-devs/credential": "workspace:^", "@serverless-devs/downloads": "workspace:^", "@serverless-devs/secret": "workspace:^", diff --git a/packages/load-application/src/utils/index.ts b/packages/load-application/src/utils/index.ts index 3e98c10..4544c5e 100644 --- a/packages/load-application/src/utils/index.ts +++ b/packages/load-application/src/utils/index.ts @@ -34,6 +34,11 @@ export const getDefaultValue = (value: any) => { return replace(value, RANDOM_PATTERN, randomId()); }; +export const getNumberDefaultValue = (value: any) => { + if (typeof value !== 'number') return; + return value; +}; + export const getSecretManager = () => { const secretManager = SecretManager.getInstance(); return secretManager; diff --git a/packages/load-application/src/v3.ts b/packages/load-application/src/v3.ts index d151c3d..24e18f8 100644 --- a/packages/load-application/src/v3.ts +++ b/packages/load-application/src/v3.ts @@ -8,7 +8,7 @@ import { isEmpty, includes, split, get, has, set, sortBy, map, concat, keys, sta import axios from 'axios'; import parse from './parse'; import { IOptions } from './types'; -import { getInputs, getUrlWithLatest, getUrlWithVersion, getAllCredential, getDefaultValue, getSecretManager } from './utils'; +import { getInputs, getUrlWithLatest, getUrlWithVersion, getAllCredential, getDefaultValue, getSecretManager, getNumberDefaultValue } from './utils'; import YAML from 'yaml'; import inquirer from 'inquirer'; import chalk from 'chalk'; @@ -338,6 +338,16 @@ class LoadApplication { default: getDefaultValue(item.default), validate, }); + } else if (item.type === 'number') { + // number类型 + promptList.push({ + type: 'input', + message: item.title, + name, + prefix, + default: getNumberDefaultValue(item.default), + validate, + }); } } return { promptList, tmpResult }; diff --git a/packages/load-component/package.json b/packages/load-component/package.json index 2b8bdae..cbe731f 100644 --- a/packages/load-component/package.json +++ b/packages/load-component/package.json @@ -1,6 +1,6 @@ { "name": "@serverless-devs/load-component", - "version": "0.0.9", + "version": "0.0.10-beta.1", "description": "request for serverless-devs", "main": "lib/index.js", "scripts": { diff --git a/packages/load-component/src/utils/index.ts b/packages/load-component/src/utils/index.ts index 5e234de..2c99bc4 100644 --- a/packages/load-component/src/utils/index.ts +++ b/packages/load-component/src/utils/index.ts @@ -18,30 +18,37 @@ export function readJsonFile(filePath: string) { } } -const getEntryFile = async (componentPath: string) => { - const fsStat = await fs.stat(componentPath); - if (fsStat.isFile() || !fsStat.isDirectory()) return componentPath; - const packageInfo: any = readJsonFile(path.resolve(componentPath, 'package.json')); - // First look for main under the package json file - let entry = get(packageInfo, 'main'); - if (entry) return path.resolve(componentPath, entry); - // Second check the out dir under the tsconfig json file - const tsconfigPath = path.resolve(componentPath, 'tsconfig.json'); - const tsconfigInfo = readJsonFile(tsconfigPath); - entry = get(tsconfigInfo, 'compilerOptions.outDir'); - if (entry) return path.resolve(componentPath, entry); - // Third look for src index js - const srcIndexPath = path.resolve(componentPath, './src/index.js'); - if (fs.existsSync(srcIndexPath)) return srcIndexPath; - const indexPath = path.resolve(componentPath, './index.js'); - if (fs.existsSync(indexPath)) return indexPath; - throw new Error( - 'The component cannot be required. Please check whether the setting of the component entry file is correct. In the current directory, first look for main under the package json file, secondly look for compiler options out dir under the tsconfig json file, thirdly look for src index js, and finally look for index js', - ); +const getEntryFile = async (componentPath: string, logger: any) => { + try { + const fsStat = await fs.stat(componentPath); + if (fsStat.isFile() || !fsStat.isDirectory()) return componentPath; + const packageInfo: any = readJsonFile(path.resolve(componentPath, 'package.json')); + // First look for main under the package json file + let entry = get(packageInfo, 'main'); + if (entry) return path.resolve(componentPath, entry); + // Second check the out dir under the tsconfig json file + const tsconfigPath = path.resolve(componentPath, 'tsconfig.json'); + const tsconfigInfo = readJsonFile(tsconfigPath); + entry = get(tsconfigInfo, 'compilerOptions.outDir'); + if (entry) return path.resolve(componentPath, entry); + // Third look for src index js + const srcIndexPath = path.resolve(componentPath, './src/index.js'); + if (fs.existsSync(srcIndexPath)) return srcIndexPath; + const indexPath = path.resolve(componentPath, './index.js'); + if (fs.existsSync(indexPath)) return indexPath; + // No valid entry file found + throw new Error('No valid entry file found'); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + logger.debug(errorMessage); + throw new Error( + 'The component cannot be required. Please check whether the setting of the component entry file is correct. In the current directory, first look for main under the package json file, secondly look for compiler options out dir under the tsconfig json file, thirdly look for src index js, and finally look for index js', + ); + } }; export const buildComponentInstance = async (componentPath: string, params?: any, cleanCache: boolean = false) => { - const requirePath = await getEntryFile(componentPath); + const requirePath = await getEntryFile(componentPath, params.logger || console); // bug: `- component: fc invoke` timeout. Delete require cache if (cleanCache && require.cache[requirePath]) { try { diff --git a/packages/parse-spec/package.json b/packages/parse-spec/package.json index c935ccd..f715ad0 100644 --- a/packages/parse-spec/package.json +++ b/packages/parse-spec/package.json @@ -1,6 +1,6 @@ { "name": "@serverless-devs/parse-spec", - "version": "0.0.29", + "version": "0.0.30-beta.3", "description": "a parse yaml spec lib for serverless-devs", "main": "lib/index.js", "scripts": { @@ -22,7 +22,7 @@ }, "dependencies": { "@serverless-cd/debug": "^4.3.4", - "@serverless-devs/art-template": "^4.13.16-beta.24", + "@serverless-devs/art-template": "^4.13.16-beta.39", "@serverless-devs/credential": "workspace:^", "@serverless-devs/utils": "workspace:^", "chalk": "^4.1.2", diff --git a/packages/parse-spec/src/index.ts b/packages/parse-spec/src/index.ts index 98ff0e0..02e4cb4 100644 --- a/packages/parse-spec/src/index.ts +++ b/packages/parse-spec/src/index.ts @@ -62,11 +62,11 @@ class ParseSpec { const code = get(projects, `${i}.props.code`); if (code && isString(code)) { const codePath = utils.getAbsolutePath(get(projects, `${i}.props.code`, '')); - expand(dotenv.config({ path: path.join(codePath, '.env') })); + expand(dotenv.config({ path: path.join(codePath, '.env'), override: true })); } } - expand(dotenv.config({ path: path.join(path.dirname(this.yaml.path), '.env') })); + expand(dotenv.config({ path: path.join(path.dirname(this.yaml.path), '.env'), override: true })); } private async doExtend() { // this.yaml = { path: '' } diff --git a/packages/parse-spec/src/types.ts b/packages/parse-spec/src/types.ts index cde15a7..73ee006 100644 --- a/packages/parse-spec/src/types.ts +++ b/packages/parse-spec/src/types.ts @@ -41,6 +41,7 @@ export interface IPluginAction { level: `${IActionLevel}`; projectName: string; allow_failure?: boolean | IAllowFailure; + replace_output?: boolean; } export interface IComponentAction { hookType: `${IHookType}`; diff --git a/packages/registry/package.json b/packages/registry/package.json index 0dbcaf8..df95751 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -1,6 +1,6 @@ { "name": "@serverless-devs/registry", - "version": "0.0.12-beta.5", + "version": "0.0.12-beta.7", "description": "request for serverless-devs", "main": "lib/index.js", "scripts": { @@ -23,7 +23,7 @@ "dependencies": { "@serverless-devs/utils": "workspace:^", "@serverless-devs/zip": "workspace:^", - "@serverless-devs/art-template": "4.13.16-beta.38", + "@serverless-devs/art-template": "4.13.16-beta.39", "ajv": "^8.12.0", "chalk": "^5.3.0", "js-yaml": "^4.1.0", diff --git a/packages/registry/src/actions/constant.ts b/packages/registry/src/actions/constant.ts index 05f5262..cb7356e 100644 --- a/packages/registry/src/actions/constant.ts +++ b/packages/registry/src/actions/constant.ts @@ -34,24 +34,6 @@ export const publishSchema = { "type": "array", "items": { "type": "string", - "enum": [ - "阿里云", - "腾讯云", - "华为云", - "百度智能云", - "AWS", - "Azure", - "Google Cloud Platform", - "专有云", - "其它", - "火山引擎", - "Alibaba Cloud", - "Tencent Cloud", - "Huawei Cloud", - "Baidu Cloud", - "Private Cloud", - "Others" - ] } }, "Version": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8dfbe7..80b1f96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,8 +221,8 @@ importers: specifier: ^4.3.4 version: 4.3.4 '@serverless-devs/art-template': - specifier: ^4.13.16-beta.24 - version: 4.13.16-beta.24 + specifier: ^4.13.16-beta.39 + version: 4.13.16-beta.39 '@serverless-devs/credential': specifier: workspace:^ version: link:../credential @@ -344,8 +344,8 @@ importers: specifier: ^4.3.4 version: 4.3.4 '@serverless-devs/art-template': - specifier: ^4.13.16-beta.24 - version: 4.13.16-beta.24 + specifier: ^4.13.16-beta.39 + version: 4.13.16-beta.39 '@serverless-devs/credential': specifier: workspace:^ version: link:../credential @@ -394,8 +394,8 @@ importers: packages/registry: dependencies: '@serverless-devs/art-template': - specifier: 4.13.16-beta.38 - version: 4.13.16-beta.38 + specifier: 4.13.16-beta.39 + version: 4.13.16-beta.39 '@serverless-devs/utils': specifier: workspace:^ version: link:../utils @@ -851,12 +851,8 @@ packages: supports-color: optional: true - '@serverless-devs/art-template@4.13.16-beta.24': - resolution: {integrity: sha512-TKK3WBpa4shDLO9wJXtipdobnAv27Hdm3DhPgpSaSrz+rYXV3f1mY2Ln3ZrSoD/OY/YXAY3VYLKd8EMhIBswXQ==} - engines: {node: '>= 1.0.0'} - - '@serverless-devs/art-template@4.13.16-beta.38': - resolution: {integrity: sha512-4Sjc8UwsVhI2tDKunjS+2HUDCdyy9WU3uGc+0Wrl6gxHzjCy5o8FONzeKndj+BgySQj1Kp2VQBE1nfYTGw3z7A==} + '@serverless-devs/art-template@4.13.16-beta.39': + resolution: {integrity: sha512-mKPvRUi2jAlmC1xG0efeosaP6ltPK5XMdlhwJxZfxVIg2o+REQMqBU3yUVzESq5fHaQKxElX8mtI2HaRv8NdKw==} engines: {node: '>= 1.0.0'} '@serverless-devs/credentials@2.2.6': @@ -3565,23 +3561,7 @@ snapshots: dependencies: ms: 2.1.2 - '@serverless-devs/art-template@4.13.16-beta.24': - dependencies: - '@serverless-devs/secret': 0.0.2-beta.4 - '@serverless-devs/utils': 0.0.14 - acorn: 5.7.4 - escodegen: 1.14.3 - estraverse: 4.3.0 - fs-extra: 11.1.1 - html-minifier: 3.5.21 - is-keyword-js: 1.0.3 - js-tokens: 3.0.2 - js-yaml: 4.1.0 - lodash: 4.17.21 - merge-source-map: 1.1.0 - source-map: 0.5.7 - - '@serverless-devs/art-template@4.13.16-beta.38': + '@serverless-devs/art-template@4.13.16-beta.39': dependencies: '@serverless-devs/secret': 0.0.2-beta.4 '@serverless-devs/utils': 0.0.14