feat(cli): support --dependencies for script actions

- action add/update: new --dependencies <json> option
- parseDependencies accepts JSON, name@version shorthand, or bare names
- update is incremental; --dependencies replaces the whole array
- docs: deployment/systemd.md + skill SKILL/action-types/development
  document the npm-deps feature and the service-PATH requirement
  (spawn npm ENOENT without nvm bin dir on PATH)
This commit is contained in:
yeuimu
2026-08-11 05:51:27 +00:00
parent aff4d70b71
commit cf25c8ff93
5 changed files with 213 additions and 35 deletions
+54 -2
View File
@@ -349,6 +349,7 @@ function actionCommand() {
.option('--code <js>', 'Script JS code (script)')
.option('--code-file <path>', 'Read script code from a file (script)')
.option('--timeout <ms>', 'Script timeout ms (script, default 60000)')
.option('--dependencies <json>', 'npm packages JSON array for script, e.g. \'[{"name":"axios","version":"1.6.0"}]\' (script)')
.option('--to <email>', 'Recipient (sendEmail)')
.option('--subject <s>', 'Subject (sendEmail)')
.option('--body <html>', 'HTML body (sendEmail)')
@@ -410,6 +411,8 @@ function actionCommand() {
.option('--config <json>', 'Full replacement config JSON')
.option('--code <js>', 'Script JS code (script)')
.option('--code-file <path>', 'Read script code from a file (script)')
.option('--timeout <ms>', 'Script timeout ms (script)')
.option('--dependencies <json>', 'npm packages JSON array for script, e.g. \'[{"name":"axios","version":"1.6.0"}]\' (script; replaces existing deps)')
.option('--name <n>', 'New node display name')
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, args, o) => {
@@ -425,8 +428,17 @@ function actionCommand() {
let config = node.config || {};
if (opts.config)
config = JSON.parse(opts.config);
else if (opts.code !== undefined || opts['code-file'] !== undefined) {
config = { ...config, code: opts['code-file'] ? readFile(opts['code-file']) : opts.code };
else {
// Granular update: merge only the flags the user actually passed.
config = { ...config };
if (opts.code !== undefined || opts['code-file'] !== undefined) {
config.code = opts['code-file'] ? readFile(opts['code-file']) : opts.code;
}
if (opts.timeout !== undefined)
config.timeout = Number(opts.timeout);
if (opts.dependencies !== undefined) {
config.dependencies = parseDependencies(opts.dependencies);
}
}
nodes[idx] = { ...node, config, name: opts.name ?? node.name };
const updated = await ctx.client.replaceNodes(baseId, workflowId, {
@@ -463,6 +475,44 @@ function readFile(p) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('node:fs').readFileSync(p, 'utf8');
}
/**
* Parse `--dependencies` into the ScriptDependency[] shape the backend expects.
* Accepts:
* - full JSON: '[{"name":"axios","version":"1.6.0"}]'
* - name list: 'axios,lodash' (version defaults to 'latest')
* - name@ver: 'axios@1.6.0,lodash@4.17.21'
* - empty '[]' or '' to clear deps
*/
function parseDependencies(raw) {
const trimmed = raw.trim();
if (!trimmed || trimmed === '[]')
return [];
// JSON form
if (trimmed[0] === '[') {
const parsed = JSON.parse(trimmed);
if (!Array.isArray(parsed))
throw new Error('--dependencies JSON must be an array');
return parsed.map((d) => {
if (typeof d === 'string')
return { name: d };
if (d && typeof d === 'object' && typeof d.name === 'string') {
return { name: d.name, ...(d.version ? { version: String(d.version) } : {}) };
}
throw new Error('--dependencies entries must be strings or {name,version?} objects');
});
}
// Comma list form: axios@1.6.0,lodash
return trimmed
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map((spec) => {
const at = spec.lastIndexOf('@');
if (at > 0)
return { name: spec.slice(0, at), version: spec.slice(at + 1) };
return { name: spec };
});
}
/** Build action config from granular flags. */
function buildActionConfig(opts) {
const type = opts.type;
@@ -479,6 +529,8 @@ function buildActionConfig(opts) {
config.code = opts.code;
if (opts.timeout)
config.timeout = Number(opts.timeout);
if (opts.dependencies)
config.dependencies = parseDependencies(opts.dependencies);
break;
}
case 'sendEmail': {