@softr/cli 0.1.6 → 0.1.7-se22117.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/cli.mjs +131 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -188,9 +188,13 @@ synced. That is how `push` knows there is nothing to send, how it notices that t
|
|
|
188
188
|
|
|
189
189
|
#### The node types on offer
|
|
190
190
|
|
|
191
|
-
`spec` lists
|
|
191
|
+
`spec` lists the curated set, small on purpose: the generic triggers (webhook, schedules, Softr Tables and Softr Apps
|
|
192
192
|
events, incoming email), the control nodes (branch, filter, loop, wait), `RESPONDED_TO_WEBHOOK`, `CALL_API`, and
|
|
193
|
-
`CUSTOM_CODE` for
|
|
193
|
+
`CUSTOM_CODE` for code. Each entry points to its public docs page and comes with a template and an example.
|
|
194
|
+
|
|
195
|
+
Any other node type the workflows service knows works in the file as well; the CLI just has no specification for it.
|
|
196
|
+
`spec <TYPE>` says where to get one (the Softr MCP server or the studio), `add <TYPE> --node-version <v>` writes the node
|
|
197
|
+
with empty inputs, and the service validates it on publish, as for every node.
|
|
194
198
|
|
|
195
199
|
#### A typical session
|
|
196
200
|
|
package/dist/cli.mjs
CHANGED
|
@@ -6070,18 +6070,74 @@ var CATALOG = catalog_default;
|
|
|
6070
6070
|
function catalogEntries(kind) {
|
|
6071
6071
|
return CATALOG.filter((entry) => !kind || entry.kind === kind);
|
|
6072
6072
|
}
|
|
6073
|
-
function
|
|
6073
|
+
function catalogEntryOrNull(type, kind) {
|
|
6074
6074
|
const wanted = type.trim().toUpperCase();
|
|
6075
|
-
|
|
6075
|
+
return CATALOG.find((candidate) => candidate.type === wanted && (!kind || candidate.kind === kind));
|
|
6076
|
+
}
|
|
6077
|
+
function findCatalogEntry(type, kind) {
|
|
6078
|
+
const entry = catalogEntryOrNull(type, kind);
|
|
6076
6079
|
if (!entry) {
|
|
6077
6080
|
const known = catalogEntries(kind).map((candidate) => candidate.type).join(", ");
|
|
6078
6081
|
throw new CliError(
|
|
6079
|
-
`"${type}" is not
|
|
6080
|
-
`
|
|
6082
|
+
`"${type.trim().toUpperCase()}" is not in the CLI's curated set${kind ? ` of ${kind}s` : ""}.`,
|
|
6083
|
+
`Curated: ${known}.
|
|
6084
|
+
${uncuratedGuidance(type)}`
|
|
6081
6085
|
);
|
|
6082
6086
|
}
|
|
6083
6087
|
return entry;
|
|
6084
6088
|
}
|
|
6089
|
+
function uncuratedGuidance(type) {
|
|
6090
|
+
const wanted = type.trim().toUpperCase();
|
|
6091
|
+
return [
|
|
6092
|
+
`${wanted} is not in the curated set, so the CLI has no template or specification for it; the workflows service`,
|
|
6093
|
+
"still runs it. Take its specification (inputs, the required ones, fixed options, version) from the Softr MCP server",
|
|
6094
|
+
"(get_node_specifications; workflow_get_node_specifications in an agent runtime) or from the studio. Then write the",
|
|
6095
|
+
"node with its type, version and inputs, or scaffold it: softr workflows add <TYPE> --node-version <v> [--kind trigger].",
|
|
6096
|
+
"Push and test it as any node: the service validates it on publish and names what is wrong."
|
|
6097
|
+
].join("\n");
|
|
6098
|
+
}
|
|
6099
|
+
function resolveNodeType(type, choice) {
|
|
6100
|
+
const entry = catalogEntryOrNull(type, choice.kind);
|
|
6101
|
+
if (entry) {
|
|
6102
|
+
if (choice.version) {
|
|
6103
|
+
throw new CliError(
|
|
6104
|
+
`${entry.type} is curated: the CLI pins its version (${entry.version}).`,
|
|
6105
|
+
'Drop --node-version; "softr workflows upgrade" moves nodes to the current one.'
|
|
6106
|
+
);
|
|
6107
|
+
}
|
|
6108
|
+
const { type: curatedType, kind, label, version: version2, template } = entry;
|
|
6109
|
+
return { type: curatedType, kind, label, version: version2, template: structuredClone(template), curated: true };
|
|
6110
|
+
}
|
|
6111
|
+
const otherKind = catalogEntryOrNull(type);
|
|
6112
|
+
if (otherKind) {
|
|
6113
|
+
throw new CliError(
|
|
6114
|
+
`${otherKind.type} is curated as ${article(otherKind.kind)}, not ${article(choice.kind ?? "action")}.`,
|
|
6115
|
+
`Curated ${choice.kind ?? "action"}s: ${catalogEntries(choice.kind ?? "action").map((candidate) => candidate.type).join(", ")}.`
|
|
6116
|
+
);
|
|
6117
|
+
}
|
|
6118
|
+
const wanted = type.trim().toUpperCase();
|
|
6119
|
+
if (!choice.version) {
|
|
6120
|
+
throw new CliError(
|
|
6121
|
+
`"${wanted}" is not in the CLI's curated set, so pass --node-version <v>: the node version the service runs.`,
|
|
6122
|
+
uncuratedGuidance(type)
|
|
6123
|
+
);
|
|
6124
|
+
}
|
|
6125
|
+
return {
|
|
6126
|
+
type: wanted,
|
|
6127
|
+
kind: choice.kind ?? "action",
|
|
6128
|
+
label: labelOf(wanted),
|
|
6129
|
+
version: choice.version,
|
|
6130
|
+
template: {},
|
|
6131
|
+
curated: false
|
|
6132
|
+
};
|
|
6133
|
+
}
|
|
6134
|
+
function article(kind) {
|
|
6135
|
+
return kind === "action" ? "an action" : "a trigger";
|
|
6136
|
+
}
|
|
6137
|
+
function labelOf(type) {
|
|
6138
|
+
const words = type.toLowerCase().split("_");
|
|
6139
|
+
return [words[0].charAt(0).toUpperCase() + words[0].slice(1), ...words.slice(1)].join(" ");
|
|
6140
|
+
}
|
|
6085
6141
|
function isTestableType(type) {
|
|
6086
6142
|
return !NOT_TESTABLE_TYPES.has(type);
|
|
6087
6143
|
}
|
|
@@ -14346,42 +14402,48 @@ function pathsAt(config, pathsPath) {
|
|
|
14346
14402
|
return Array.isArray(current2) ? current2 : [];
|
|
14347
14403
|
}
|
|
14348
14404
|
function registerEdit(program3) {
|
|
14349
|
-
program3.command("replace").description("Give a node another type: keeps its id, title and paths; inputs start from the new type's template").argument("<nodeId>", "the trigger or action to change").argument(
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14405
|
+
program3.command("replace").description("Give a node another type: keeps its id, title and paths; inputs start from the new type's template").argument("<nodeId>", "the trigger or action to change").argument(
|
|
14406
|
+
"<type>",
|
|
14407
|
+
'the new type from "spec" (same kind: trigger or action), or any type the service knows (with --node-version)'
|
|
14408
|
+
).option("--lang <language>", "for CUSTOM_CODE: JAVASCRIPT or PYTHON (default JAVASCRIPT)").option("--node-version <version>", "for a type outside the curated set: the node version the service runs").option("--dir <path>", "workflow directory (default: the current directory)").action(
|
|
14409
|
+
async (nodeId, type, opts, command) => {
|
|
14410
|
+
const options = globalOptions(command);
|
|
14411
|
+
const dir = await findWorkflowDir(process.cwd(), opts.dir);
|
|
14412
|
+
const config = await readConfig(dir);
|
|
14413
|
+
const location = locateNode(config, nodeId);
|
|
14414
|
+
if (!location) {
|
|
14415
|
+
throw new CliError(`No node "${nodeId}" in softr-workflows.jsonc.`);
|
|
14416
|
+
}
|
|
14417
|
+
const entry = resolveNodeType(type, { kind: location.kind, version: opts.nodeVersion });
|
|
14418
|
+
if (location.node.type === LOOP_TYPE && entry.type !== LOOP_TYPE) {
|
|
14419
|
+
throw new CliError(`"${nodeId}" is a loop with a body; remove it instead of replacing it.`);
|
|
14420
|
+
}
|
|
14421
|
+
const title = location.node.title ?? entry.label;
|
|
14422
|
+
const { inputs, codeFile } = await newNodeInputs(entry, nodeId, title, opts.lang, dir);
|
|
14423
|
+
const node = { ...location.node, type: entry.type, version: entry.version, inputs };
|
|
14424
|
+
delete node["outputSchema"];
|
|
14425
|
+
await editConfig(dir, [{ path: location.path, value: node }]);
|
|
14426
|
+
const oldCode = location.node.type === CUSTOM_CODE_TYPE && isFileRef(location.node.inputs?.["code"]) ? location.node.inputs?.["code"] : void 0;
|
|
14427
|
+
const notes = [
|
|
14428
|
+
oldCode && oldCode !== inputs["code"] ? `The previous code file (${oldCode}) is left in place; delete it when you no longer need it.` : "",
|
|
14429
|
+
entry.kind === "trigger" && (config.triggers?.length ?? 0) > 1 ? "The service accepts one trigger per published workflow for now." : "",
|
|
14430
|
+
entry.curated ? `Fill inputs.${Object.keys(entry.template)[0] ?? "\u2026"} etc. in softr-workflows.jsonc (see "spec ${entry.type}"), then push and test ${nodeId}.` : `Fill its inputs in softr-workflows.jsonc from its specification ("spec ${entry.type}" says where to find it), then push and test ${nodeId}.`
|
|
14431
|
+
].filter(Boolean);
|
|
14432
|
+
emitWithVersionNote(
|
|
14433
|
+
options,
|
|
14434
|
+
await readConfig(dir),
|
|
14435
|
+
`Replaced "${nodeId}" (${location.node.type}) with ${entry.type}; paths kept.
|
|
14376
14436
|
${notes.join("\n")}`,
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14437
|
+
{
|
|
14438
|
+
id: nodeId,
|
|
14439
|
+
from: location.node.type,
|
|
14440
|
+
to: entry.type,
|
|
14441
|
+
curated: entry.curated,
|
|
14442
|
+
codeFile: codeFile ? path5.relative(process.cwd(), codeFile) : null
|
|
14443
|
+
}
|
|
14444
|
+
);
|
|
14445
|
+
}
|
|
14446
|
+
);
|
|
14385
14447
|
program3.command("remove").description("Remove a node and every path that leads to or from it (a loop goes with its body)").argument("<nodeId>", "the trigger or action to remove").option("--dir <path>", "workflow directory (default: the current directory)").action(async (nodeId, opts, command) => {
|
|
14386
14448
|
const options = globalOptions(command);
|
|
14387
14449
|
const dir = await findWorkflowDir(process.cwd(), opts.dir);
|
|
@@ -14430,6 +14492,15 @@ function slug(title, fallback = "workflow") {
|
|
|
14430
14492
|
}
|
|
14431
14493
|
|
|
14432
14494
|
// src/commands/add.ts
|
|
14495
|
+
function parseKind(kind) {
|
|
14496
|
+
if (kind === void 0) {
|
|
14497
|
+
return void 0;
|
|
14498
|
+
}
|
|
14499
|
+
if (kind === "trigger" || kind === "action") {
|
|
14500
|
+
return kind;
|
|
14501
|
+
}
|
|
14502
|
+
throw new CliError(`--kind takes trigger or action, not "${kind}".`);
|
|
14503
|
+
}
|
|
14433
14504
|
function uniqueId(base, taken) {
|
|
14434
14505
|
if (!taken.has(base)) {
|
|
14435
14506
|
return base;
|
|
@@ -14465,11 +14536,14 @@ function defaultAfter(config, loopId) {
|
|
|
14465
14536
|
);
|
|
14466
14537
|
}
|
|
14467
14538
|
function registerAdd(program3) {
|
|
14468
|
-
program3.command("add").description("Add an action after an existing node, or a trigger; custom code gets its own file under actions/").argument(
|
|
14539
|
+
program3.command("add").description("Add an action after an existing node, or a trigger; custom code gets its own file under actions/").argument(
|
|
14540
|
+
"<type>",
|
|
14541
|
+
'node type from "spec", e.g. CUSTOM_CODE or WEBHOOK, or any type the service knows (with --node-version)'
|
|
14542
|
+
).option("--id <id>", "node id, readable and unique (default: derived from the type)").option("--title <title>", "node title shown in the studio (default: the type label)").option("--after <nodeId>", "actions: the node the new one follows (default: the only node without a successor)").option("--to <nodeId>", "triggers: an action the new trigger leads to").option("--in-loop <loopId>", "put the node inside this loop body").option("--lang <language>", "for CUSTOM_CODE: JAVASCRIPT or PYTHON (default JAVASCRIPT)").option("--node-version <version>", "for a type outside the curated set: the node version the service runs").option("--kind <kind>", "for a type outside the curated set: trigger or action (default action)").option("--dir <path>", "workflow directory (default: the current directory)").action(async (type, opts, command) => {
|
|
14469
14543
|
const options = globalOptions(command);
|
|
14470
14544
|
const dir = await findWorkflowDir(process.cwd(), opts.dir);
|
|
14471
14545
|
const config = await readConfig(dir);
|
|
14472
|
-
const entry =
|
|
14546
|
+
const entry = resolveNodeType(type, { kind: parseKind(opts.kind), version: opts.nodeVersion });
|
|
14473
14547
|
const nodes = [...config.triggers ?? [], ...allActions(config.actions)];
|
|
14474
14548
|
const taken = new Set(nodes.map((node2) => node2.id));
|
|
14475
14549
|
const id = opts.id ?? uniqueId(entry.kind === "trigger" ? "trigger" : slug(entry.label), taken);
|
|
@@ -14509,6 +14583,7 @@ ${notes.join("\n")}`,
|
|
|
14509
14583
|
id,
|
|
14510
14584
|
kind: "trigger",
|
|
14511
14585
|
type: entry.type,
|
|
14586
|
+
curated: entry.curated,
|
|
14512
14587
|
to: opts.to ?? null
|
|
14513
14588
|
}
|
|
14514
14589
|
);
|
|
@@ -14533,6 +14608,7 @@ ${notes.join("\n")}`,
|
|
|
14533
14608
|
const next = [
|
|
14534
14609
|
codeFile ? `Write the function in ${path6.relative(process.cwd(), codeFile)} and map its inputs under inputs.inputData in softr-workflows.jsonc.` : "",
|
|
14535
14610
|
entry.type === "BRANCH" || entry.type === "FILTER" ? `Add the condition(s) on the path(s) leaving it in softr-workflows.jsonc (see "spec ${entry.type}").` : "",
|
|
14611
|
+
entry.curated ? "" : `Fill its inputs in softr-workflows.jsonc from its specification ("spec ${entry.type}" says where to find it).`,
|
|
14536
14612
|
`Then: softr workflows push, softr workflows test ${id}.`
|
|
14537
14613
|
].filter(Boolean);
|
|
14538
14614
|
emitWithVersionNote(
|
|
@@ -14544,6 +14620,7 @@ ${next.join("\n")}`,
|
|
|
14544
14620
|
id,
|
|
14545
14621
|
kind: "action",
|
|
14546
14622
|
type: entry.type,
|
|
14623
|
+
curated: entry.curated,
|
|
14547
14624
|
after,
|
|
14548
14625
|
loopId: opts.inLoop ?? null,
|
|
14549
14626
|
codeFile: codeFile ? path6.relative(process.cwd(), codeFile) : null
|
|
@@ -17697,7 +17774,8 @@ function renderList() {
|
|
|
17697
17774
|
}
|
|
17698
17775
|
return [
|
|
17699
17776
|
table.toString(),
|
|
17700
|
-
"
|
|
17777
|
+
"These are the curated types: documented here, with a template and an example each. Any other node type the",
|
|
17778
|
+
'workflows service knows can be written into softr-workflows.jsonc as well; "softr workflows spec <TYPE>" says how.',
|
|
17701
17779
|
'Run "softr workflows spec <TYPE>" for the inputs, output and an example of one type.'
|
|
17702
17780
|
].join("\n");
|
|
17703
17781
|
}
|
|
@@ -17756,7 +17834,9 @@ ${JSON.stringify(extra.json, null, 2)}`);
|
|
|
17756
17834
|
return blocks.join("\n\n");
|
|
17757
17835
|
}
|
|
17758
17836
|
function registerSpec(program3) {
|
|
17759
|
-
program3.command("spec").description(
|
|
17837
|
+
program3.command("spec").description(
|
|
17838
|
+
"List the curated node types, or show the inputs, output and an example of one; any other type: how to add it"
|
|
17839
|
+
).argument("[type]", "a node type such as CUSTOM_CODE").action((type, _opts, command) => {
|
|
17760
17840
|
const options = globalOptions(command);
|
|
17761
17841
|
if (!type) {
|
|
17762
17842
|
emit(options, renderList(), {
|
|
@@ -17770,8 +17850,13 @@ function registerSpec(program3) {
|
|
|
17770
17850
|
});
|
|
17771
17851
|
return;
|
|
17772
17852
|
}
|
|
17773
|
-
const entry =
|
|
17774
|
-
|
|
17853
|
+
const entry = catalogEntryOrNull(type);
|
|
17854
|
+
if (!entry) {
|
|
17855
|
+
const guidance = uncuratedGuidance(type);
|
|
17856
|
+
emit(options, guidance, { type: type.trim().toUpperCase(), curated: false, guidance });
|
|
17857
|
+
return;
|
|
17858
|
+
}
|
|
17859
|
+
emit(options, renderEntry(entry), { ...entry, curated: true, testable: isTestableType(entry.type) });
|
|
17775
17860
|
});
|
|
17776
17861
|
}
|
|
17777
17862
|
|
|
@@ -17998,7 +18083,7 @@ Pass --workspace <id> to a command, or set SOFTR_WORKSPACE_ID, to work in one of
|
|
|
17998
18083
|
// package.json
|
|
17999
18084
|
var package_default = {
|
|
18000
18085
|
name: "@softr/cli",
|
|
18001
|
-
version: "0.1.
|
|
18086
|
+
version: "0.1.7-se22117.20",
|
|
18002
18087
|
description: "The Softr command line: build, test and publish Softr workflows from local files, manage Softr Databases; more Softr APIs to come",
|
|
18003
18088
|
keywords: [
|
|
18004
18089
|
"softr",
|
package/package.json
CHANGED