@softr/cli 0.0.1 → 0.1.0
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 +93 -81
- package/dist/cli.mjs +377 -255
- package/package.json +3 -4
package/dist/cli.mjs
CHANGED
|
@@ -1745,7 +1745,7 @@ var require_table = __commonJS({
|
|
|
1745
1745
|
var debug = require_debug();
|
|
1746
1746
|
var utils = require_utils();
|
|
1747
1747
|
var tableLayout = require_layout_manager();
|
|
1748
|
-
var
|
|
1748
|
+
var Table7 = class extends Array {
|
|
1749
1749
|
constructor(opts) {
|
|
1750
1750
|
super();
|
|
1751
1751
|
const options = utils.mergeOptions(opts);
|
|
@@ -1820,7 +1820,7 @@ var require_table = __commonJS({
|
|
|
1820
1820
|
return str[0].length;
|
|
1821
1821
|
}
|
|
1822
1822
|
};
|
|
1823
|
-
|
|
1823
|
+
Table7.reset = () => debug.reset();
|
|
1824
1824
|
function doDraw(row, lineNum, result) {
|
|
1825
1825
|
let line = [];
|
|
1826
1826
|
row.forEach(function(cell) {
|
|
@@ -1829,7 +1829,7 @@ var require_table = __commonJS({
|
|
|
1829
1829
|
let str = line.join("");
|
|
1830
1830
|
if (str.length) result.push(str);
|
|
1831
1831
|
}
|
|
1832
|
-
module.exports =
|
|
1832
|
+
module.exports = Table7;
|
|
1833
1833
|
}
|
|
1834
1834
|
});
|
|
1835
1835
|
|
|
@@ -5278,9 +5278,17 @@ function executionOrder(config) {
|
|
|
5278
5278
|
}
|
|
5279
5279
|
};
|
|
5280
5280
|
for (const trigger of config.triggers ?? []) {
|
|
5281
|
-
add({
|
|
5281
|
+
add({
|
|
5282
|
+
id: trigger.id,
|
|
5283
|
+
type: trigger.type,
|
|
5284
|
+
kind: "trigger",
|
|
5285
|
+
title: trigger.title,
|
|
5286
|
+
testable: true,
|
|
5287
|
+
loopPath: [],
|
|
5288
|
+
reachable: true
|
|
5289
|
+
});
|
|
5282
5290
|
}
|
|
5283
|
-
const collect = (actions,
|
|
5291
|
+
const collect = (actions, loopPath) => {
|
|
5284
5292
|
for (const action of actions ?? []) {
|
|
5285
5293
|
add({
|
|
5286
5294
|
id: action.id,
|
|
@@ -5288,17 +5296,18 @@ function executionOrder(config) {
|
|
|
5288
5296
|
kind: "action",
|
|
5289
5297
|
title: action.title,
|
|
5290
5298
|
testable: !NOT_TESTABLE_TYPES.has(action.type),
|
|
5291
|
-
loopId,
|
|
5299
|
+
loopId: loopPath.at(-1),
|
|
5300
|
+
loopPath,
|
|
5292
5301
|
reachable: false
|
|
5293
5302
|
});
|
|
5294
5303
|
if (action.type === LOOP_TYPE) {
|
|
5295
5304
|
const inputs = action.inputs;
|
|
5296
5305
|
pathSets.push(inputs?.paths ?? []);
|
|
5297
|
-
collect(inputs?.actions, action.id);
|
|
5306
|
+
collect(inputs?.actions, [...loopPath, action.id]);
|
|
5298
5307
|
}
|
|
5299
5308
|
}
|
|
5300
5309
|
};
|
|
5301
|
-
collect(config.actions);
|
|
5310
|
+
collect(config.actions, []);
|
|
5302
5311
|
const outgoing = new Map(appearance.map((id) => [id, []]));
|
|
5303
5312
|
const indegree = new Map(appearance.map((id) => [id, 0]));
|
|
5304
5313
|
for (const path13 of pathSets.flat()) {
|
|
@@ -5342,6 +5351,9 @@ function executionOrder(config) {
|
|
|
5342
5351
|
}
|
|
5343
5352
|
return [...ordered.filter((node) => node.reachable), ...ordered.filter((node) => !node.reachable)];
|
|
5344
5353
|
}
|
|
5354
|
+
function hierarchyPath(node, iteration = 0) {
|
|
5355
|
+
return [...node.loopPath.flatMap((loopId) => [loopId, String(iteration)]), node.id].join("/");
|
|
5356
|
+
}
|
|
5345
5357
|
|
|
5346
5358
|
// src/catalog/catalog.json
|
|
5347
5359
|
var catalog_default = [
|
|
@@ -5698,7 +5710,7 @@ var catalog_default = [
|
|
|
5698
5710
|
integrations: {
|
|
5699
5711
|
type: "array",
|
|
5700
5712
|
required: false,
|
|
5701
|
-
description: 'Workspace integrations the code may call through fetch: [{"alias": "crm", "integrationId": "<uuid>"}]. A call to a host of an attached integration carries its credentials (a REST_API integration only when the call names its alias); the alias ([A-Za-z_][A-Za-z0-9_]*, unique in the node) names one in fetch(url, { integration: "crm" }) when two share a host. Integration ids: `softr
|
|
5713
|
+
description: 'Workspace integrations the code may call through fetch: [{"alias": "crm", "integrationId": "<uuid>"}]. A call to a host of an attached integration carries its credentials (a REST_API integration only when the call names its alias); the alias ([A-Za-z_][A-Za-z0-9_]*, unique in the node) names one in fetch(url, { integration: "crm" }) when two share a host. Integration ids: `softr integrations` lists them, or the studio (Workspace settings \u2192 Integrations, or the URL of the integration).',
|
|
5702
5714
|
example: [
|
|
5703
5715
|
{
|
|
5704
5716
|
alias: "crm",
|
|
@@ -6088,7 +6100,7 @@ var SoftrApiError = class extends Error {
|
|
|
6088
6100
|
};
|
|
6089
6101
|
function hintFor(status) {
|
|
6090
6102
|
if (status === 401 || status === 403) {
|
|
6091
|
-
return "The token was not accepted: revoked, from another environment, missing access to this workflow, or its statements do not grant this action. Run: softr
|
|
6103
|
+
return "The token was not accepted: revoked, from another environment, missing access to this workflow, or its statements do not grant this action. Run: softr login";
|
|
6092
6104
|
}
|
|
6093
6105
|
if (status === 409) {
|
|
6094
6106
|
return "The workflow changed on the server since the last pull. Pull again with --force, then re-apply your edits.";
|
|
@@ -7588,7 +7600,7 @@ async function readConfig(dir) {
|
|
|
7588
7600
|
if (error.code === "ENOENT") {
|
|
7589
7601
|
throw new CliError(
|
|
7590
7602
|
`No ${CONFIG_FILE_NAME} in ${dir}.`,
|
|
7591
|
-
'Run "softr
|
|
7603
|
+
'Run "softr workflows pull <workflowId>" here, or pass --dir.'
|
|
7592
7604
|
);
|
|
7593
7605
|
}
|
|
7594
7606
|
throw error;
|
|
@@ -7619,7 +7631,7 @@ async function findWorkflowDir(cwd, explicitDir) {
|
|
|
7619
7631
|
return cwd;
|
|
7620
7632
|
}
|
|
7621
7633
|
const candidates = await listWorkflowDirs(cwd);
|
|
7622
|
-
const hint = candidates.length ? `Workflows found in: ${candidates.map((dir) => path2.relative(cwd, dir)).join(", ")}. Pass --dir <name> or cd into one.` : 'Run "softr
|
|
7634
|
+
const hint = candidates.length ? `Workflows found in: ${candidates.map((dir) => path2.relative(cwd, dir)).join(", ")}. Pass --dir <name> or cd into one.` : 'Run "softr workflows pull <workflowId>" to create one.';
|
|
7623
7635
|
throw new CliError(`No ${CONFIG_FILE_NAME} in ${cwd}.`, hint);
|
|
7624
7636
|
}
|
|
7625
7637
|
async function listWorkflowDirs(cwd) {
|
|
@@ -7641,6 +7653,79 @@ async function editConfig(dir, edits) {
|
|
|
7641
7653
|
`);
|
|
7642
7654
|
}
|
|
7643
7655
|
|
|
7656
|
+
// src/project/versions.ts
|
|
7657
|
+
function compareVersions(a, b) {
|
|
7658
|
+
const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
7659
|
+
const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
7660
|
+
for (let i2 = 0; i2 < Math.max(pa.length, pb.length); i2++) {
|
|
7661
|
+
const diff = (pa[i2] ?? 0) - (pb[i2] ?? 0);
|
|
7662
|
+
if (diff !== 0) {
|
|
7663
|
+
return diff;
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
return 0;
|
|
7667
|
+
}
|
|
7668
|
+
var CURRENT = new Map(CATALOG.map((entry) => [entry.type, entry.version]));
|
|
7669
|
+
function outdatedNodes(config) {
|
|
7670
|
+
const outdated = [];
|
|
7671
|
+
for (const node of [...config.triggers ?? [], ...allActions(config.actions)]) {
|
|
7672
|
+
const current2 = CURRENT.get(node.type);
|
|
7673
|
+
if (!current2 || !node.version || compareVersions(node.version, current2) >= 0) {
|
|
7674
|
+
continue;
|
|
7675
|
+
}
|
|
7676
|
+
outdated.push({
|
|
7677
|
+
id: node.id,
|
|
7678
|
+
type: node.type,
|
|
7679
|
+
version: node.version,
|
|
7680
|
+
current: current2,
|
|
7681
|
+
upgradable: upgradeOf(node) !== void 0
|
|
7682
|
+
});
|
|
7683
|
+
}
|
|
7684
|
+
return outdated;
|
|
7685
|
+
}
|
|
7686
|
+
function upgradeOf(node) {
|
|
7687
|
+
const current2 = CURRENT.get(node.type);
|
|
7688
|
+
if (!current2 || !node.version || compareVersions(node.version, current2) >= 0) {
|
|
7689
|
+
return void 0;
|
|
7690
|
+
}
|
|
7691
|
+
if (node.type === CUSTOM_CODE_TYPE) {
|
|
7692
|
+
const inputs = node.inputs ?? {};
|
|
7693
|
+
const ordered = {};
|
|
7694
|
+
for (const key of ["language", "inputData", "integrations", "code"]) {
|
|
7695
|
+
if (key === "integrations") {
|
|
7696
|
+
ordered[key] = inputs[key] ?? [];
|
|
7697
|
+
} else if (inputs[key] !== void 0) {
|
|
7698
|
+
ordered[key] = inputs[key];
|
|
7699
|
+
}
|
|
7700
|
+
}
|
|
7701
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
7702
|
+
if (!(key in ordered)) {
|
|
7703
|
+
ordered[key] = value;
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
return { ...node, version: current2, inputs: ordered };
|
|
7707
|
+
}
|
|
7708
|
+
return void 0;
|
|
7709
|
+
}
|
|
7710
|
+
function versionNote(config) {
|
|
7711
|
+
const outdated = outdatedNodes(config);
|
|
7712
|
+
if (!outdated.length) {
|
|
7713
|
+
return void 0;
|
|
7714
|
+
}
|
|
7715
|
+
const lines = outdated.map(
|
|
7716
|
+
(node) => ` ${node.id}: ${node.type} ${node.version} \u2192 ${node.current}${node.upgradable ? "" : ` (no automatic upgrade; see "spec ${node.type}")`}`
|
|
7717
|
+
);
|
|
7718
|
+
const hint = outdated.some((node) => node.upgradable) ? '\nRun "softr workflows upgrade" to move them to the current version.' : "";
|
|
7719
|
+
return `Nodes on an older version:
|
|
7720
|
+
${lines.join("\n")}${hint}`;
|
|
7721
|
+
}
|
|
7722
|
+
function emitWithVersionNote(options, config, human, json) {
|
|
7723
|
+
const note = versionNote(config);
|
|
7724
|
+
emit(options, note ? `${human}
|
|
7725
|
+
|
|
7726
|
+
${note}` : human, { ...json, versionNote: note ?? null });
|
|
7727
|
+
}
|
|
7728
|
+
|
|
7644
7729
|
// src/commands/edit.ts
|
|
7645
7730
|
import { access as access2, mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
|
|
7646
7731
|
import path5 from "node:path";
|
|
@@ -13591,6 +13676,36 @@ function renderPyType(type, depth = 0) {
|
|
|
13591
13676
|
}
|
|
13592
13677
|
|
|
13593
13678
|
// src/project/code-wrapper.ts
|
|
13679
|
+
function attachedIntegrations(inputs) {
|
|
13680
|
+
const value = inputs?.["integrations"];
|
|
13681
|
+
if (!Array.isArray(value)) {
|
|
13682
|
+
return [];
|
|
13683
|
+
}
|
|
13684
|
+
return value.flatMap(
|
|
13685
|
+
(entry) => entry && typeof entry === "object" && typeof entry.alias === "string" ? [
|
|
13686
|
+
{
|
|
13687
|
+
alias: entry.alias,
|
|
13688
|
+
integrationId: String(entry.integrationId ?? "")
|
|
13689
|
+
}
|
|
13690
|
+
] : []
|
|
13691
|
+
);
|
|
13692
|
+
}
|
|
13693
|
+
function integrationLines(header) {
|
|
13694
|
+
if (!hasFetch(header.version)) {
|
|
13695
|
+
return [];
|
|
13696
|
+
}
|
|
13697
|
+
const attached = header.integrations ?? [];
|
|
13698
|
+
if (!attached.length) {
|
|
13699
|
+
return [
|
|
13700
|
+
'No integrations attached: add { alias, integrationId } entries under inputs.integrations in softr-workflows.jsonc (ids from "softr integrations").'
|
|
13701
|
+
];
|
|
13702
|
+
}
|
|
13703
|
+
return [
|
|
13704
|
+
"Integrations attached to this node; fetch authenticates a call to their own API hosts by itself:",
|
|
13705
|
+
...attached.map((item) => ` ${item.alias}: ${item.integrationId}`),
|
|
13706
|
+
`Name one with fetch(url, { integration: '${attached[0].alias}' }) only for a REST API integration (it has no host of its own) or when two attached integrations share a host.`
|
|
13707
|
+
];
|
|
13708
|
+
}
|
|
13594
13709
|
var PLACEHOLDER_IN_CODE = /\{\{?[A-Za-z]+[^{}]*:::/;
|
|
13595
13710
|
function parseLanguage(value, nodeId) {
|
|
13596
13711
|
if (value === "JAVASCRIPT" || value === "PYTHON") {
|
|
@@ -13613,17 +13728,6 @@ var FETCH_SINCE = "1.2.0";
|
|
|
13613
13728
|
function hasFetch(version2) {
|
|
13614
13729
|
return compareVersions(version2, FETCH_SINCE) >= 0;
|
|
13615
13730
|
}
|
|
13616
|
-
function compareVersions(a, b) {
|
|
13617
|
-
const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
13618
|
-
const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
13619
|
-
for (let i2 = 0; i2 < Math.max(pa.length, pb.length); i2++) {
|
|
13620
|
-
const diff = (pa[i2] ?? 0) - (pb[i2] ?? 0);
|
|
13621
|
-
if (diff !== 0) {
|
|
13622
|
-
return diff;
|
|
13623
|
-
}
|
|
13624
|
-
}
|
|
13625
|
-
return 0;
|
|
13626
|
-
}
|
|
13627
13731
|
var JS_RULES = [
|
|
13628
13732
|
...RULES,
|
|
13629
13733
|
"HTTP calls: `await fetch(url, { method, headers, body, integration })`. An attached integration (inputs.integrations) authenticates calls to its own API hosts; a REST_API integration authenticates only a call that names its alias. No `import`/`require`. An undefined return is reported as null."
|
|
@@ -13650,8 +13754,8 @@ function rules(language, version2) {
|
|
|
13650
13754
|
`This node runs CUSTOM_CODE ${version2}: synchronous, no fetch, inputs.integrations is ignored. Set "version": "1.2.0" on this node in softr-workflows.jsonc to use them.`
|
|
13651
13755
|
];
|
|
13652
13756
|
}
|
|
13653
|
-
var HEADER_START = "softr
|
|
13654
|
-
var HEADER_END = "softr
|
|
13757
|
+
var HEADER_START = "softr: generated header - regenerated on pull, do not edit by hand";
|
|
13758
|
+
var HEADER_END = "softr: end of generated header";
|
|
13655
13759
|
var MAX_EXAMPLE = 60;
|
|
13656
13760
|
function describe(header) {
|
|
13657
13761
|
return `Softr Workflows custom code for node "${header.nodeId}"${header.title ? ` (${header.title})` : ""}.`;
|
|
@@ -13693,6 +13797,7 @@ function wrapCode(language, body, header) {
|
|
|
13693
13797
|
...rules("JAVASCRIPT", header.version).map((rule) => ` * ${rule}`),
|
|
13694
13798
|
" */",
|
|
13695
13799
|
...typedef,
|
|
13800
|
+
...integrationLines(header).map((line) => `// ${line}`),
|
|
13696
13801
|
`// ${HEADER_END}`,
|
|
13697
13802
|
`/** @param {${paramType}} inputData */`,
|
|
13698
13803
|
`export default ${hasFetch(header.version) ? "async " : ""}function (inputData) {`,
|
|
@@ -13712,6 +13817,7 @@ function wrapCode(language, body, header) {
|
|
|
13712
13817
|
...rules("PYTHON", header.version).map((rule) => `# ${rule}`),
|
|
13713
13818
|
"#",
|
|
13714
13819
|
...inputs,
|
|
13820
|
+
...integrationLines(header).map((line) => `# ${line}`),
|
|
13715
13821
|
`# ${HEADER_END}`,
|
|
13716
13822
|
"def main(inputData):",
|
|
13717
13823
|
...lines.length ? lines.map((line) => line === "" ? "" : ` ${line}`) : [" pass"],
|
|
@@ -13948,7 +14054,7 @@ function describePlaceholder(placeholder, raw, config, outputs, depth) {
|
|
|
13948
14054
|
}
|
|
13949
14055
|
}
|
|
13950
14056
|
function untested(nodeId) {
|
|
13951
|
-
return `node "${nodeId}" has no test output yet (run: softr
|
|
14057
|
+
return `node "${nodeId}" has no test output yet (run: softr workflows test ${nodeId})`;
|
|
13952
14058
|
}
|
|
13953
14059
|
function describeOutputRef(nodeId, path13, source, outputs) {
|
|
13954
14060
|
const segments = parsePath(path13);
|
|
@@ -14076,7 +14182,8 @@ async function externalizeCode(config, baseDir, outputs = {}) {
|
|
|
14076
14182
|
nodeId: node.id,
|
|
14077
14183
|
title: node.title,
|
|
14078
14184
|
version: nodeVersion(node),
|
|
14079
|
-
inputs: describeInputData(node, config, outputs)
|
|
14185
|
+
inputs: describeInputData(node, config, outputs),
|
|
14186
|
+
integrations: attachedIntegrations(node.inputs)
|
|
14080
14187
|
})
|
|
14081
14188
|
);
|
|
14082
14189
|
node.inputs["code"] = toFileRef(relative);
|
|
@@ -14131,7 +14238,8 @@ async function refreshCodeHeaders(config, baseDir, outputs) {
|
|
|
14131
14238
|
nodeId: node.id,
|
|
14132
14239
|
title: node.title,
|
|
14133
14240
|
version: nodeVersion(node),
|
|
14134
|
-
inputs: describeInputData(node, config, outputs)
|
|
14241
|
+
inputs: describeInputData(node, config, outputs),
|
|
14242
|
+
integrations: attachedIntegrations(node.inputs)
|
|
14135
14243
|
});
|
|
14136
14244
|
if (next !== source) {
|
|
14137
14245
|
await writeFile3(file, next);
|
|
@@ -14221,8 +14329,9 @@ function registerEdit(program3) {
|
|
|
14221
14329
|
entry.kind === "trigger" && (config.triggers?.length ?? 0) > 1 ? "The service accepts one trigger per published workflow for now." : "",
|
|
14222
14330
|
`Fill inputs.${Object.keys(entry.template)[0] ?? "\u2026"} etc. in softr-workflows.jsonc (see "spec ${entry.type}"), then push and test ${nodeId}.`
|
|
14223
14331
|
].filter(Boolean);
|
|
14224
|
-
|
|
14332
|
+
emitWithVersionNote(
|
|
14225
14333
|
options,
|
|
14334
|
+
await readConfig(dir),
|
|
14226
14335
|
`Replaced "${nodeId}" (${location.node.type}) with ${entry.type}; paths kept.
|
|
14227
14336
|
${notes.join("\n")}`,
|
|
14228
14337
|
{
|
|
@@ -14261,8 +14370,9 @@ ${notes.join("\n")}`,
|
|
|
14261
14370
|
codeFiles.length ? `Code files left in place: ${codeFiles.join(", ")}. Delete them when you no longer need them.` : "",
|
|
14262
14371
|
location.kind === "trigger" && (config.triggers?.length ?? 1) <= 1 ? "The workflow has no trigger now; add one before publishing." : ""
|
|
14263
14372
|
].filter(Boolean);
|
|
14264
|
-
|
|
14373
|
+
emitWithVersionNote(
|
|
14265
14374
|
options,
|
|
14375
|
+
await readConfig(dir),
|
|
14266
14376
|
`Removed ${removedNodes.map((id) => `"${id}"`).join(", ")} and ${doomed.length} path(s).${notes.length ? `
|
|
14267
14377
|
${notes.join("\n")}` : ""}`,
|
|
14268
14378
|
{
|
|
@@ -14291,9 +14401,8 @@ function listWorkflows(client, workspaceId) {
|
|
|
14291
14401
|
const query = new URLSearchParams({ workspaceId, limit: "500" });
|
|
14292
14402
|
return apiRequest(client, "GET", `/v1/workflows?${query}`);
|
|
14293
14403
|
}
|
|
14294
|
-
function testAction(client, workflowId,
|
|
14295
|
-
|
|
14296
|
-
return apiRequest(client, "POST", `/v1/workflows/${workflowId}/outputs/actions/${actionId}/${mode}${query}`, {});
|
|
14404
|
+
function testAction(client, workflowId, hierarchyPath2, mode) {
|
|
14405
|
+
return apiRequest(client, "POST", `/v1/workflows/${workflowId}/outputs/${mode}/actions/${hierarchyPath2}`, {});
|
|
14297
14406
|
}
|
|
14298
14407
|
function testTrigger(client, workflowId, triggerId) {
|
|
14299
14408
|
return apiRequest(client, "POST", `/v1/workflows/${workflowId}/outputs/triggers/${triggerId}/test`, {});
|
|
@@ -14320,18 +14429,8 @@ async function getEnabledVersion(client, workflowId) {
|
|
|
14320
14429
|
throw error;
|
|
14321
14430
|
}
|
|
14322
14431
|
}
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
return await apiRequest(client, "GET", `/v1/workflows/${workflowId}/readiness`);
|
|
14326
|
-
} catch (error) {
|
|
14327
|
-
if (error instanceof SoftrApiError && (error.status === 404 || isUnknownRouteAnswer(error))) {
|
|
14328
|
-
return null;
|
|
14329
|
-
}
|
|
14330
|
-
throw error;
|
|
14331
|
-
}
|
|
14332
|
-
}
|
|
14333
|
-
function isUnknownRouteAnswer(error) {
|
|
14334
|
-
return error.status === 500 && error.body.includes("ApiErrorMessages.INTERNAL_SERVER_ERROR");
|
|
14432
|
+
function getWorkflowReadiness(client, workflowId) {
|
|
14433
|
+
return apiRequest(client, "GET", `/v1/workflows/${workflowId}/readiness`);
|
|
14335
14434
|
}
|
|
14336
14435
|
|
|
14337
14436
|
// src/config/credentials.ts
|
|
@@ -14341,7 +14440,7 @@ import path6 from "node:path";
|
|
|
14341
14440
|
|
|
14342
14441
|
// src/api/oauth.ts
|
|
14343
14442
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
14344
|
-
var OAUTH_CLIENT_ID_ENV_VAR = "
|
|
14443
|
+
var OAUTH_CLIENT_ID_ENV_VAR = "SOFTR_OAUTH_CLIENT_ID";
|
|
14345
14444
|
var DEFAULT_OAUTH_CLIENT_ID = "74341326-0061-4f0e-965c-a5aa14a9a89a";
|
|
14346
14445
|
function oauthClientId() {
|
|
14347
14446
|
return process.env[OAUTH_CLIENT_ID_ENV_VAR] || DEFAULT_OAUTH_CLIENT_ID;
|
|
@@ -14379,7 +14478,7 @@ async function tokenRequest(target, form) {
|
|
|
14379
14478
|
`POST ${path13} failed with status ${response.status}${text ? `: ${text}` : ""}`,
|
|
14380
14479
|
response.status,
|
|
14381
14480
|
text,
|
|
14382
|
-
form["grant_type"] === "refresh_token" ? "The OAuth grant is gone (revoked or older than 30 days). Run: softr
|
|
14481
|
+
form["grant_type"] === "refresh_token" ? "The OAuth grant is gone (revoked or older than 30 days). Run: softr login" : void 0
|
|
14383
14482
|
);
|
|
14384
14483
|
}
|
|
14385
14484
|
const tokens = await response.json();
|
|
@@ -14403,8 +14502,8 @@ function refreshTokens(target, refreshToken) {
|
|
|
14403
14502
|
|
|
14404
14503
|
// src/config/target.ts
|
|
14405
14504
|
var API_URL_ENV_VAR = "SOFTR_WORKFLOWS_API_URL";
|
|
14406
|
-
var STUDIO_API_URL_ENV_VAR = "
|
|
14407
|
-
var STUDIO_URL_ENV_VAR = "
|
|
14505
|
+
var STUDIO_API_URL_ENV_VAR = "SOFTR_STUDIO_API_URL";
|
|
14506
|
+
var STUDIO_URL_ENV_VAR = "SOFTR_STUDIO_URL";
|
|
14408
14507
|
var DEFAULT_API_URL = "https://workflows-api.softr.io";
|
|
14409
14508
|
var DEFAULT_STUDIO_API_URL = "https://studio-api.softr.io";
|
|
14410
14509
|
var DEFAULT_STUDIO_URL = "https://studio.softr.io";
|
|
@@ -14429,7 +14528,7 @@ function resolveTarget(remembered) {
|
|
|
14429
14528
|
};
|
|
14430
14529
|
}
|
|
14431
14530
|
function targetHost(target) {
|
|
14432
|
-
return new URL(target.
|
|
14531
|
+
return new URL(target.studioApiUrl).host;
|
|
14433
14532
|
}
|
|
14434
14533
|
|
|
14435
14534
|
// src/config/tokens.ts
|
|
@@ -14442,12 +14541,12 @@ function tokenKind(token) {
|
|
|
14442
14541
|
}
|
|
14443
14542
|
throw new CliError(
|
|
14444
14543
|
"The token is neither a personal API token (pat_...) nor an OAuth access token (oaa_...).",
|
|
14445
|
-
'Create a personal API token in the studio and paste the whole value, or run "softr
|
|
14544
|
+
'Create a personal API token in the studio and paste the whole value, or run "softr login" without --token to log in through the browser.'
|
|
14446
14545
|
);
|
|
14447
14546
|
}
|
|
14448
14547
|
|
|
14449
14548
|
// src/config/credentials.ts
|
|
14450
|
-
var TOKEN_ENV_VAR = "
|
|
14549
|
+
var TOKEN_ENV_VAR = "SOFTR_TOKEN";
|
|
14451
14550
|
var REFRESH_MARGIN_MS = 5 * 60 * 1e3;
|
|
14452
14551
|
function credentialsPath() {
|
|
14453
14552
|
return path6.join(homedir(), ".softr", "credentials.json");
|
|
@@ -14518,7 +14617,7 @@ async function authenticatedClient(target) {
|
|
|
14518
14617
|
}
|
|
14519
14618
|
const stored = await storedToken(target);
|
|
14520
14619
|
if (!stored) {
|
|
14521
|
-
throw new CliError(`No token stored for ${targetHost(target)}.`, "Run: softr
|
|
14620
|
+
throw new CliError(`No token stored for ${targetHost(target)}.`, "Run: softr login");
|
|
14522
14621
|
}
|
|
14523
14622
|
const kind = stored.kind ?? tokenKind(stored.token);
|
|
14524
14623
|
const expiresAt = stored.expiresAt ? new Date(stored.expiresAt) : void 0;
|
|
@@ -14528,7 +14627,7 @@ async function authenticatedClient(target) {
|
|
|
14528
14627
|
if (expiresAt && expiresAt < /* @__PURE__ */ new Date()) {
|
|
14529
14628
|
throw new CliError(
|
|
14530
14629
|
`The stored token for ${targetHost(target)} expired at ${stored.expiresAt}.`,
|
|
14531
|
-
"Run: softr
|
|
14630
|
+
"Run: softr login"
|
|
14532
14631
|
);
|
|
14533
14632
|
}
|
|
14534
14633
|
return { target, token: stored.token, kind };
|
|
@@ -14545,7 +14644,7 @@ async function resolveTokenOwner(client) {
|
|
|
14545
14644
|
if (error instanceof SoftrApiError && error.status === 404) {
|
|
14546
14645
|
throw new CliError(
|
|
14547
14646
|
"The studio does not know this token: revoked, expired, or from another environment.",
|
|
14548
|
-
'Create a new personal API token in the studio, or run "softr
|
|
14647
|
+
'Create a new personal API token in the studio, or run "softr login" for the browser login.'
|
|
14549
14648
|
);
|
|
14550
14649
|
}
|
|
14551
14650
|
throw error;
|
|
@@ -14663,10 +14762,10 @@ function workflowTable(rows) {
|
|
|
14663
14762
|
}
|
|
14664
14763
|
return table.toString();
|
|
14665
14764
|
}
|
|
14666
|
-
function renderWorkflows({ workspaces, workflows, failures }) {
|
|
14765
|
+
function renderWorkflows({ workspaces, workflows: workflows2, failures }) {
|
|
14667
14766
|
const blocks = [];
|
|
14668
14767
|
for (const workspace of workspaces) {
|
|
14669
|
-
const rows =
|
|
14768
|
+
const rows = workflows2.filter((row) => row.workspaceId === workspace.id);
|
|
14670
14769
|
const heading = `Workspace: ${workspace.name} (${workspace.id})`;
|
|
14671
14770
|
blocks.push(rows.length ? `${heading}
|
|
14672
14771
|
${workflowTable(rows)}` : `${heading}
|
|
@@ -14689,11 +14788,11 @@ async function emitWorkflowList(options, client, workspaceId, human) {
|
|
|
14689
14788
|
emit(options, [human, renderWorkflows(listing)].filter(Boolean).join("\n\n"), { ...listing });
|
|
14690
14789
|
}
|
|
14691
14790
|
function registerList(program3) {
|
|
14692
|
-
program3.command("list").description("List the workflows you can access, grouped by workspace").option("--workspace <id>", "only this workspace").action(async (opts, command) => {
|
|
14791
|
+
program3.command("list").description("List the workflows you can access, grouped by workspace").option("--workspace <id>", "only this workspace (default: $SOFTR_WORKSPACE_ID when set)").action(async (opts, command) => {
|
|
14693
14792
|
const options = globalOptions(command);
|
|
14694
14793
|
const target = resolveTarget(await readState(process.cwd()));
|
|
14695
14794
|
const client = await authenticatedClient(target);
|
|
14696
|
-
await emitWorkflowList(options, client, opts.workspace);
|
|
14795
|
+
await emitWorkflowList(options, client, opts.workspace ?? process.env["SOFTR_WORKSPACE_ID"]);
|
|
14697
14796
|
});
|
|
14698
14797
|
}
|
|
14699
14798
|
|
|
@@ -14960,7 +15059,7 @@ function registerPull(program3) {
|
|
|
14960
15059
|
options,
|
|
14961
15060
|
client,
|
|
14962
15061
|
void 0,
|
|
14963
|
-
"Pick a workflow and run: softr
|
|
15062
|
+
"Pick a workflow and run: softr workflows pull <workflowId>"
|
|
14964
15063
|
);
|
|
14965
15064
|
return;
|
|
14966
15065
|
}
|
|
@@ -14991,8 +15090,9 @@ Sample outputs not available, inputData types are unknown: ${error.message}`;
|
|
|
14991
15090
|
outputsFile = await writeStateFile(dir, "outputs.json", rows);
|
|
14992
15091
|
}
|
|
14993
15092
|
const files = [configFile, ...codeFiles].map((file) => path8.relative(cwd, file));
|
|
14994
|
-
|
|
15093
|
+
emitWithVersionNote(
|
|
14995
15094
|
options,
|
|
15095
|
+
config,
|
|
14996
15096
|
`Pulled "${workflow.title}" (version ${workflow.version}, draft ${workflow.draft}, ${workflow.enabled ? "enabled" : "not enabled"}).
|
|
14997
15097
|
` + files.map((file) => ` ${file}`).join("\n") + outputsNote,
|
|
14998
15098
|
{
|
|
@@ -15079,10 +15179,11 @@ function registerAdd(program3) {
|
|
|
15079
15179
|
const notes = [
|
|
15080
15180
|
(config.triggers?.length ?? 0) >= 1 ? "The service accepts one trigger per published workflow for now; multi-trigger support is coming." : "",
|
|
15081
15181
|
opts.to ? "" : `Connect it with a path to the first action (or pass --to next time).`,
|
|
15082
|
-
`Then: softr
|
|
15182
|
+
`Then: softr workflows push, softr workflows test ${id}.`
|
|
15083
15183
|
].filter(Boolean);
|
|
15084
|
-
|
|
15184
|
+
emitWithVersionNote(
|
|
15085
15185
|
options,
|
|
15186
|
+
config,
|
|
15086
15187
|
`Added ${entry.type} trigger "${id}"${opts.to ? ` leading to "${opts.to}"` : ""}.
|
|
15087
15188
|
${notes.join("\n")}`,
|
|
15088
15189
|
{
|
|
@@ -15113,10 +15214,11 @@ ${notes.join("\n")}`,
|
|
|
15113
15214
|
const next = [
|
|
15114
15215
|
codeFile ? `Write the function in ${path9.relative(process.cwd(), codeFile)} and map its inputs under inputs.inputData in softr-workflows.jsonc.` : "",
|
|
15115
15216
|
entry.type === "BRANCH" || entry.type === "FILTER" ? `Add the condition(s) on the path(s) leaving it in softr-workflows.jsonc (see "spec ${entry.type}").` : "",
|
|
15116
|
-
`Then: softr
|
|
15217
|
+
`Then: softr workflows push, softr workflows test ${id}.`
|
|
15117
15218
|
].filter(Boolean);
|
|
15118
|
-
|
|
15219
|
+
emitWithVersionNote(
|
|
15119
15220
|
options,
|
|
15221
|
+
config,
|
|
15120
15222
|
`Added ${entry.type} "${id}" after "${after}"${opts.inLoop ? ` inside loop "${opts.inLoop}"` : ""}.
|
|
15121
15223
|
${next.join("\n")}`,
|
|
15122
15224
|
{
|
|
@@ -15158,7 +15260,10 @@ async function resolveWorkspace(client, requested) {
|
|
|
15158
15260
|
);
|
|
15159
15261
|
}
|
|
15160
15262
|
function registerInit(program3) {
|
|
15161
|
-
program3.command("init").description("Create a new workflow with one trigger, on the server and as a local directory").argument("<title>", "the workflow title").option(
|
|
15263
|
+
program3.command("init").description("Create a new workflow with one trigger, on the server and as a local directory").argument("<title>", "the workflow title").option(
|
|
15264
|
+
"--workspace <id>",
|
|
15265
|
+
"workspace to create it in (default: $SOFTR_WORKSPACE_ID, else your only or default workspace)"
|
|
15266
|
+
).option("--trigger <TYPE>", 'trigger type from "spec" (default WEBHOOK)', "WEBHOOK").option("--dir <path>", "directory to create (default: ./<title-slug>)").action(async (title, opts, command) => {
|
|
15162
15267
|
const options = globalOptions(command);
|
|
15163
15268
|
const cwd = process.cwd();
|
|
15164
15269
|
const dir = path10.resolve(cwd, opts.dir ?? slug(title));
|
|
@@ -15168,7 +15273,7 @@ function registerInit(program3) {
|
|
|
15168
15273
|
const trigger = findCatalogEntry(opts.trigger, "trigger");
|
|
15169
15274
|
const target = resolveTarget();
|
|
15170
15275
|
const client = await authenticatedClient(target);
|
|
15171
|
-
const workspaceId = await resolveWorkspace(client, opts.workspace);
|
|
15276
|
+
const workspaceId = await resolveWorkspace(client, opts.workspace ?? process.env["SOFTR_WORKSPACE_ID"]);
|
|
15172
15277
|
const created = await createWorkflow(client, {
|
|
15173
15278
|
workspaceId,
|
|
15174
15279
|
applicationId: null,
|
|
@@ -15198,7 +15303,7 @@ function registerInit(program3) {
|
|
|
15198
15303
|
`Created "${title}" (${created.id}) with a ${trigger.type} trigger "${TRIGGER_ID}" in workspace ${workspaceId}.
|
|
15199
15304
|
${path10.relative(cwd, configFile)}
|
|
15200
15305
|
${studioUrl}
|
|
15201
|
-
Next: softr
|
|
15306
|
+
Next: softr workflows add <TYPE> --dir ${path10.relative(cwd, dir) || "."} (see "spec" for the types), then push and test.`,
|
|
15202
15307
|
{
|
|
15203
15308
|
workflowId: created.id,
|
|
15204
15309
|
workspaceId,
|
|
@@ -15263,12 +15368,15 @@ async function workspaceFor(client, requested, workflowDir) {
|
|
|
15263
15368
|
return resolveWorkspace(client, void 0);
|
|
15264
15369
|
}
|
|
15265
15370
|
function registerIntegrations(program3) {
|
|
15266
|
-
program3.command("integrations").description("List the workspace's connected integrations, whose ids go into integrationId inputs").option(
|
|
15371
|
+
program3.command("integrations").description("List the workspace's connected integrations, whose ids go into integrationId inputs").option(
|
|
15372
|
+
"--workspace <id>",
|
|
15373
|
+
"workspace to list (default: $SOFTR_WORKSPACE_ID, else the workflow directory's, else your only or default workspace)"
|
|
15374
|
+
).option("--type <TYPE>", "only this integration type, e.g. SLACK or SOFTR_TABLES").option("--dir <path>", "workflow directory whose workspace to use (default: the current directory)").action(async (opts, command) => {
|
|
15267
15375
|
const options = globalOptions(command);
|
|
15268
15376
|
const workflowDir = await workflowDirOf(process.cwd(), opts.dir);
|
|
15269
15377
|
const target = resolveTarget(await readState(workflowDir ?? process.cwd()));
|
|
15270
15378
|
const client = await authenticatedClient(target);
|
|
15271
|
-
const workspaceId = await workspaceFor(client, opts.workspace, workflowDir);
|
|
15379
|
+
const workspaceId = await workspaceFor(client, opts.workspace ?? process.env["SOFTR_WORKSPACE_ID"], workflowDir);
|
|
15272
15380
|
const wanted = opts.type?.toUpperCase();
|
|
15273
15381
|
const rows = (await listWorkspaceIntegrations(client, workspaceId)).map(toRow2).filter((row) => !wanted || row.type === wanted).sort(byTypeThenName);
|
|
15274
15382
|
emit(options, `Workspace ${workspaceId}${wanted ? `, type ${wanted}` : ""}:
|
|
@@ -15322,7 +15430,7 @@ async function interactiveLogin(target) {
|
|
|
15322
15430
|
if (answer === "2") {
|
|
15323
15431
|
const token = (await rl.question("Personal API token: ")).trim();
|
|
15324
15432
|
if (!token) {
|
|
15325
|
-
throw new CliError("No token given.", 'Run "softr
|
|
15433
|
+
throw new CliError("No token given.", 'Run "softr login" again.');
|
|
15326
15434
|
}
|
|
15327
15435
|
return pastedToken(token);
|
|
15328
15436
|
}
|
|
@@ -15350,7 +15458,7 @@ function waitForCallback(expectedState) {
|
|
|
15350
15458
|
}
|
|
15351
15459
|
const finish = (status, text) => {
|
|
15352
15460
|
response.writeHead(status, { "Content-Type": "text/html; charset=utf-8", Connection: "close" });
|
|
15353
|
-
response.end(`<!doctype html><title>softr
|
|
15461
|
+
response.end(`<!doctype html><title>softr</title><p>${text} You can close this tab.</p>`);
|
|
15354
15462
|
server.close();
|
|
15355
15463
|
};
|
|
15356
15464
|
const error = url.searchParams.get("error");
|
|
@@ -15377,9 +15485,7 @@ function waitForCallback(expectedState) {
|
|
|
15377
15485
|
}
|
|
15378
15486
|
const timer = setTimeout(() => {
|
|
15379
15487
|
server.close();
|
|
15380
|
-
settle?.reject(
|
|
15381
|
-
new CliError("No answer from the browser within 5 minutes.", 'Run "softr-workflows login" again.')
|
|
15382
|
-
);
|
|
15488
|
+
settle?.reject(new CliError("No answer from the browser within 5 minutes.", 'Run "softr login" again.'));
|
|
15383
15489
|
}, CALLBACK_TIMEOUT_MS);
|
|
15384
15490
|
timer.unref();
|
|
15385
15491
|
resolveServer({ redirectUri: `http://127.0.0.1:${address.port}/callback`, result });
|
|
@@ -15484,51 +15590,6 @@ function outdatedReferences(row) {
|
|
|
15484
15590
|
const outdated = row?.sampleOutput?.outdatedDependencies;
|
|
15485
15591
|
return outdated && Object.keys(outdated).length ? outdated : {};
|
|
15486
15592
|
}
|
|
15487
|
-
function assessTests(config, rows, lastSavedAt) {
|
|
15488
|
-
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
15489
|
-
const issues = [];
|
|
15490
|
-
for (const node of executionOrder(config)) {
|
|
15491
|
-
if (!node.testable) {
|
|
15492
|
-
continue;
|
|
15493
|
-
}
|
|
15494
|
-
const row = byId.get(node.id);
|
|
15495
|
-
const result = row?.sampleOutput?.result;
|
|
15496
|
-
if (!result) {
|
|
15497
|
-
issues.push({ id: node.id, type: node.type, kind: "untested", detail: "not tested" });
|
|
15498
|
-
} else if (result.status === "FAILED") {
|
|
15499
|
-
issues.push({
|
|
15500
|
-
id: node.id,
|
|
15501
|
-
type: node.type,
|
|
15502
|
-
kind: "failed",
|
|
15503
|
-
detail: `last test failed${result.errorMessage ? `: ${result.errorMessage}` : ""}`
|
|
15504
|
-
});
|
|
15505
|
-
} else if (result.status !== "SUCCEEDED") {
|
|
15506
|
-
issues.push({
|
|
15507
|
-
id: node.id,
|
|
15508
|
-
type: node.type,
|
|
15509
|
-
kind: "pending",
|
|
15510
|
-
detail: `last test ended with ${result.status ?? "no status"}`
|
|
15511
|
-
});
|
|
15512
|
-
} else if (lastSavedAt && row?.sampleOutput?.createdAt && new Date(row.sampleOutput.createdAt) < new Date(lastSavedAt)) {
|
|
15513
|
-
issues.push({
|
|
15514
|
-
id: node.id,
|
|
15515
|
-
type: node.type,
|
|
15516
|
-
kind: "stale",
|
|
15517
|
-
detail: `tested ${row.sampleOutput.createdAt}, before the last save ${lastSavedAt}; test again to be sure`
|
|
15518
|
-
});
|
|
15519
|
-
}
|
|
15520
|
-
const outdated = Object.entries(outdatedReferences(row));
|
|
15521
|
-
if (outdated.length) {
|
|
15522
|
-
issues.push({
|
|
15523
|
-
id: node.id,
|
|
15524
|
-
type: node.type,
|
|
15525
|
-
kind: "outdated",
|
|
15526
|
-
detail: `tested against outdated data of ${outdated.map(([upstream, paths]) => `${upstream} (${paths.join(", ")})`).join("; ")}`
|
|
15527
|
-
});
|
|
15528
|
-
}
|
|
15529
|
-
}
|
|
15530
|
-
return issues;
|
|
15531
|
-
}
|
|
15532
15593
|
function renderIssues(issues) {
|
|
15533
15594
|
return issues.map((issue) => ` - ${issue.id} (${issue.type}): ${issue.detail}`).join("\n");
|
|
15534
15595
|
}
|
|
@@ -15618,7 +15679,7 @@ async function runOne(client, workflowId, node, opts) {
|
|
|
15618
15679
|
if (!node.testable) {
|
|
15619
15680
|
return { ...base, outcome: "skipped", detail: "branches, filters and loops have no output of their own" };
|
|
15620
15681
|
}
|
|
15621
|
-
const row = node.kind === "trigger" ? await runTrigger(client, workflowId, node.id, opts.sample ?? 0) : await testAction(client, workflowId, node.
|
|
15682
|
+
const row = node.kind === "trigger" ? await runTrigger(client, workflowId, node.id, opts.sample ?? 0) : await testAction(client, workflowId, hierarchyPath(node, opts.iteration), opts.mock ? "mock" : "test");
|
|
15622
15683
|
return { ...base, ...outcomeOf(row), row };
|
|
15623
15684
|
}
|
|
15624
15685
|
function mergeRows(existing, fresh) {
|
|
@@ -15688,7 +15749,7 @@ function registerTest(program3) {
|
|
|
15688
15749
|
"Run a node in the workflows service and save its sample output; --all runs every node in execution order"
|
|
15689
15750
|
).argument("[nodeId]", "the node to test (its id in softr-workflows.jsonc)").option("--all", "test the trigger and then every action, predecessors first, skipping branches, filters and loops").option("--mock", "mock run instead of a real one (actions only; custom code always runs for real)").option(
|
|
15690
15751
|
"--iteration <n>",
|
|
15691
|
-
"for a node inside a loop:
|
|
15752
|
+
"for a node inside a loop: the iteration of the enclosing loop(s) to use (default 0)",
|
|
15692
15753
|
(value) => Number.parseInt(value, 10)
|
|
15693
15754
|
).option(
|
|
15694
15755
|
"--sample <n>",
|
|
@@ -15744,7 +15805,7 @@ function registerTest(program3) {
|
|
|
15744
15805
|
].join("\n")
|
|
15745
15806
|
);
|
|
15746
15807
|
}
|
|
15747
|
-
|
|
15808
|
+
emitWithVersionNote(options, config, [renderResults(results), notes.join("\n")].filter(Boolean).join("\n\n"), {
|
|
15748
15809
|
workflowId: config.id,
|
|
15749
15810
|
results: results.map(({ row: _row, ...result }) => result),
|
|
15750
15811
|
headersRefreshed: headers.refreshed,
|
|
@@ -15831,7 +15892,7 @@ function registerOutputs(program3) {
|
|
|
15831
15892
|
const row = byId.get(nodeId);
|
|
15832
15893
|
const sample = row?.sampleOutput?.result?.output;
|
|
15833
15894
|
if (!row || sample === void 0 || sample === null) {
|
|
15834
|
-
throw new CliError(`No sample output for "${nodeId}" yet.`, `Run: softr
|
|
15895
|
+
throw new CliError(`No sample output for "${nodeId}" yet.`, `Run: softr workflows test ${nodeId}`);
|
|
15835
15896
|
}
|
|
15836
15897
|
const listing = collectVariables(nodeId, sample, row.outputMetadata ?? []);
|
|
15837
15898
|
emit(options, renderVariables(nodeId, listing.variables, listing.unverified), {
|
|
@@ -15869,7 +15930,7 @@ function registerOutputs(program3) {
|
|
|
15869
15930
|
refreshedHeaders.length ? `Headers refreshed: ${refreshedHeaders.join(", ")}` : "",
|
|
15870
15931
|
problems.length ? `Code files that do not parse (headers not refreshed):
|
|
15871
15932
|
${problems.map((problem) => ` ${problem}`).join("\n")}` : "",
|
|
15872
|
-
'Run "softr
|
|
15933
|
+
'Run "softr workflows outputs <nodeId>" for the variables of one node.'
|
|
15873
15934
|
].filter(Boolean);
|
|
15874
15935
|
emit(options, `${table.toString()}
|
|
15875
15936
|
|
|
@@ -15957,7 +16018,7 @@ function registerPlaceholder(program3) {
|
|
|
15957
16018
|
}
|
|
15958
16019
|
throw new CliError(
|
|
15959
16020
|
`${token} is not backed by test data: ${hint}.`,
|
|
15960
|
-
`Run "softr
|
|
16021
|
+
`Run "softr workflows outputs ${nodeId}" to list every referenceable path.`
|
|
15961
16022
|
);
|
|
15962
16023
|
}
|
|
15963
16024
|
const type = renderJsType(description.type);
|
|
@@ -15979,55 +16040,6 @@ type: ${type}${example3} (${status})`, {
|
|
|
15979
16040
|
import { stdin as stdin2, stdout } from "node:process";
|
|
15980
16041
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
15981
16042
|
|
|
15982
|
-
// src/project/placeholder-check.ts
|
|
15983
|
-
var TOKEN = /\{(?:outputs|loopActionGroup)\.[^:{}]+:::[^{}]*\}/g;
|
|
15984
|
-
function* stringsIn(value, path13) {
|
|
15985
|
-
if (typeof value === "string") {
|
|
15986
|
-
yield [path13, value];
|
|
15987
|
-
} else if (Array.isArray(value)) {
|
|
15988
|
-
for (const [index, item] of value.entries()) {
|
|
15989
|
-
yield* stringsIn(item, `${path13}[${index}]`);
|
|
15990
|
-
}
|
|
15991
|
-
} else if (value !== null && typeof value === "object") {
|
|
15992
|
-
for (const [key, item] of Object.entries(value)) {
|
|
15993
|
-
if (key === "actions" || key === "paths") {
|
|
15994
|
-
continue;
|
|
15995
|
-
}
|
|
15996
|
-
yield* stringsIn(item, path13 ? `${path13}.${key}` : key);
|
|
15997
|
-
}
|
|
15998
|
-
}
|
|
15999
|
-
}
|
|
16000
|
-
function checkPlaceholders(config, outputs) {
|
|
16001
|
-
const findings = [];
|
|
16002
|
-
const nodes = [...config.triggers ?? [], ...allActions(config.actions)];
|
|
16003
|
-
for (const node of nodes) {
|
|
16004
|
-
for (const [input, text] of stringsIn(node.inputs ?? {}, "")) {
|
|
16005
|
-
for (const token of new Set(text.match(TOKEN) ?? [])) {
|
|
16006
|
-
if (!parseWholePlaceholder(token)) {
|
|
16007
|
-
continue;
|
|
16008
|
-
}
|
|
16009
|
-
const [description] = describeInputData(
|
|
16010
|
-
{ id: node.id, type: node.type, inputs: { inputData: { probe: token } } },
|
|
16011
|
-
config,
|
|
16012
|
-
outputs
|
|
16013
|
-
);
|
|
16014
|
-
if (!description) {
|
|
16015
|
-
continue;
|
|
16016
|
-
}
|
|
16017
|
-
const status = description.verified ? "verified" : description.type.kind === "unknown" ? "unknown" : "declared";
|
|
16018
|
-
findings.push({
|
|
16019
|
-
nodeId: node.id,
|
|
16020
|
-
input,
|
|
16021
|
-
token,
|
|
16022
|
-
status,
|
|
16023
|
-
...description.note ? { note: description.note } : {}
|
|
16024
|
-
});
|
|
16025
|
-
}
|
|
16026
|
-
}
|
|
16027
|
-
}
|
|
16028
|
-
return findings;
|
|
16029
|
-
}
|
|
16030
|
-
|
|
16031
16043
|
// src/project/readiness.ts
|
|
16032
16044
|
var REASONS = {
|
|
16033
16045
|
REFERENCED_NODE_UNKNOWN: "no node with that id",
|
|
@@ -16074,31 +16086,15 @@ function fromReadiness(readiness) {
|
|
|
16074
16086
|
}
|
|
16075
16087
|
}
|
|
16076
16088
|
return {
|
|
16077
|
-
source: "service",
|
|
16078
16089
|
validationProblems: readiness.validationProblems,
|
|
16079
16090
|
testIssues,
|
|
16080
16091
|
unbacked,
|
|
16081
16092
|
checked: { version: readiness.version, draft: readiness.draft }
|
|
16082
16093
|
};
|
|
16083
16094
|
}
|
|
16084
|
-
function
|
|
16085
|
-
|
|
16086
|
-
return {
|
|
16087
|
-
source: "client",
|
|
16088
|
-
validationProblems: null,
|
|
16089
|
-
testIssues: assessTests(config, rows, lastUpdatedAt),
|
|
16090
|
-
unbacked: checkPlaceholders(config, outputs).filter((finding) => finding.status !== "verified").map((finding) => ({ nodeId: finding.nodeId, token: finding.token, reason: finding.note ?? finding.status })),
|
|
16091
|
-
checked: null
|
|
16092
|
-
};
|
|
16093
|
-
}
|
|
16094
|
-
async function assess(client, workflowId, packed, rows, lastUpdatedAt) {
|
|
16095
|
-
const readiness = await getWorkflowReadiness(client, workflowId);
|
|
16096
|
-
if (readiness) {
|
|
16097
|
-
return fromReadiness(readiness);
|
|
16098
|
-
}
|
|
16099
|
-
return fromClientChecks(packed ?? { actions: [], triggers: [] }, rows, lastUpdatedAt);
|
|
16095
|
+
async function assess(client, workflowId) {
|
|
16096
|
+
return fromReadiness(await getWorkflowReadiness(client, workflowId));
|
|
16100
16097
|
}
|
|
16101
|
-
var CLIENT_SIDE_NOTE = "Checked by this CLI: this deployment of the service has no readiness endpoint (it answered the request the way it answers an unknown route), so the publish validation is not part of this report.";
|
|
16102
16098
|
|
|
16103
16099
|
// src/commands/publish.ts
|
|
16104
16100
|
async function openWorkflow(dir) {
|
|
@@ -16134,15 +16130,15 @@ function registerPublish(program3) {
|
|
|
16134
16130
|
if (state?.syncedHash && definitionHash(packed) !== state.syncedHash) {
|
|
16135
16131
|
throw new CliError("Local edits are not pushed yet.", "Run push first, then publish.");
|
|
16136
16132
|
}
|
|
16137
|
-
const
|
|
16133
|
+
const remote = await getWorkflow(client, id);
|
|
16138
16134
|
if (state && (remote.version !== state.version || remote.draft !== state.draft) && !opts.force) {
|
|
16139
16135
|
throw new CliError(
|
|
16140
16136
|
`The server copy changed since your last sync: it is version ${remote.version}, draft ${remote.draft}; this directory knows version ${state.version}, draft ${state.draft}.`,
|
|
16141
16137
|
'Run "pull --force" to see what would go live, then publish; or "publish --force" to enable it unseen.'
|
|
16142
16138
|
);
|
|
16143
16139
|
}
|
|
16144
|
-
const assessment = await assess(client, id
|
|
16145
|
-
if (assessment.validationProblems
|
|
16140
|
+
const assessment = await assess(client, id);
|
|
16141
|
+
if (assessment.validationProblems.length) {
|
|
16146
16142
|
throw new CliError(
|
|
16147
16143
|
`The service would reject publishing:
|
|
16148
16144
|
${assessment.validationProblems.map((problem) => ` - ${problem}`).join("\n")}`,
|
|
@@ -16152,8 +16148,7 @@ ${assessment.validationProblems.map((problem) => ` - ${problem}`).join("\n")}`,
|
|
|
16152
16148
|
const issues = issuesOf(assessment, config);
|
|
16153
16149
|
if (issues.length && !opts.force) {
|
|
16154
16150
|
const text = `The workflow is not fully tested:
|
|
16155
|
-
${renderIssues(issues)}
|
|
16156
|
-
${CLIENT_SIDE_NOTE}` : ""}`;
|
|
16151
|
+
${renderIssues(issues)}`;
|
|
16157
16152
|
if (stdin2.isTTY && stdout.isTTY) {
|
|
16158
16153
|
const rl = createInterface2({ input: stdin2, output: stdout });
|
|
16159
16154
|
let answer;
|
|
@@ -16168,7 +16163,7 @@ Publish anyway? [y/N] `);
|
|
|
16168
16163
|
return;
|
|
16169
16164
|
}
|
|
16170
16165
|
} else {
|
|
16171
|
-
throw new CliError(text, 'Run "softr
|
|
16166
|
+
throw new CliError(text, 'Run "softr workflows test --all" first, or pass --force to publish anyway.');
|
|
16172
16167
|
}
|
|
16173
16168
|
}
|
|
16174
16169
|
await enableWorkflow(client, id);
|
|
@@ -16190,20 +16185,8 @@ ${target.studioUrl}/workflow/${id}`,
|
|
|
16190
16185
|
const options = globalOptions(command);
|
|
16191
16186
|
const dir = await findWorkflowDir(process.cwd(), opts.dir);
|
|
16192
16187
|
const { id, config, client, target, state } = await openWorkflow(dir);
|
|
16193
|
-
const [remote, live
|
|
16194
|
-
|
|
16195
|
-
getEnabledVersion(client, id),
|
|
16196
|
-
getWorkflowOutputs(client, id)
|
|
16197
|
-
]);
|
|
16198
|
-
let packedForStatus;
|
|
16199
|
-
try {
|
|
16200
|
-
packedForStatus = (await packWorkflow(config, dir)).packed;
|
|
16201
|
-
} catch (error) {
|
|
16202
|
-
if (!(error instanceof CliError)) {
|
|
16203
|
-
throw error;
|
|
16204
|
-
}
|
|
16205
|
-
}
|
|
16206
|
-
const assessment = await assess(client, id, packedForStatus, rows, remote.lastUpdatedAt);
|
|
16188
|
+
const [remote, live] = await Promise.all([getWorkflow(client, id), getEnabledVersion(client, id)]);
|
|
16189
|
+
const assessment = await assess(client, id);
|
|
16207
16190
|
const issues = issuesOf(assessment, config);
|
|
16208
16191
|
let local = "unknown";
|
|
16209
16192
|
let problem;
|
|
@@ -16224,15 +16207,12 @@ ${target.studioUrl}/workflow/${id}`,
|
|
|
16224
16207
|
`Local: ${local}${problem ? ` (${problem})` : ""}${state ? `; last sync version ${state.version}, draft ${state.draft}` : ""}`,
|
|
16225
16208
|
`Server: version ${remote.version}, draft ${remote.draft}${serverMoved ? " (moved since your last sync: pull --force to take it)" : ""}`,
|
|
16226
16209
|
`Live: ${live ? `version ${live.version}` : "not published"}`,
|
|
16227
|
-
|
|
16228
|
-
|
|
16229
|
-
${assessment.validationProblems.map((problem2) => ` - ${problem2}`).join("\n")}` : "the service accepts the workflow for publishing"}`
|
|
16230
|
-
],
|
|
16210
|
+
`Valid: ${assessment.validationProblems.length ? `${assessment.validationProblems.length} problem(s)
|
|
16211
|
+
${assessment.validationProblems.map((problem2) => ` - ${problem2}`).join("\n")}` : "the service accepts the workflow for publishing"}`,
|
|
16231
16212
|
`Tests: ${issues.length ? `${issues.length} issue(s)
|
|
16232
|
-
${renderIssues(issues)}` : "every node tested on current data"}
|
|
16233
|
-
...assessment.source === "client" ? [CLIENT_SIDE_NOTE] : []
|
|
16213
|
+
${renderIssues(issues)}` : "every node tested on current data"}`
|
|
16234
16214
|
];
|
|
16235
|
-
|
|
16215
|
+
emitWithVersionNote(options, config, lines.join("\n"), {
|
|
16236
16216
|
workflowId: id,
|
|
16237
16217
|
title: remote.title,
|
|
16238
16218
|
studioUrl: `${target.studioUrl}/workflow/${id}`,
|
|
@@ -16242,14 +16222,64 @@ ${renderIssues(issues)}` : "every node tested on current data"}`,
|
|
|
16242
16222
|
server: { version: remote.version, draft: remote.draft, moved: serverMoved },
|
|
16243
16223
|
liveVersion: live?.version ?? null,
|
|
16244
16224
|
testIssues: issues,
|
|
16245
|
-
validationProblems: assessment.validationProblems
|
|
16246
|
-
source: assessment.source
|
|
16225
|
+
validationProblems: assessment.validationProblems
|
|
16247
16226
|
});
|
|
16248
16227
|
});
|
|
16249
16228
|
}
|
|
16250
16229
|
|
|
16251
16230
|
// src/commands/push.ts
|
|
16252
16231
|
import path12 from "node:path";
|
|
16232
|
+
|
|
16233
|
+
// src/project/placeholder-check.ts
|
|
16234
|
+
var TOKEN = /\{(?:outputs|loopActionGroup)\.[^:{}]+:::[^{}]*\}/g;
|
|
16235
|
+
function* stringsIn(value, path13) {
|
|
16236
|
+
if (typeof value === "string") {
|
|
16237
|
+
yield [path13, value];
|
|
16238
|
+
} else if (Array.isArray(value)) {
|
|
16239
|
+
for (const [index, item] of value.entries()) {
|
|
16240
|
+
yield* stringsIn(item, `${path13}[${index}]`);
|
|
16241
|
+
}
|
|
16242
|
+
} else if (value !== null && typeof value === "object") {
|
|
16243
|
+
for (const [key, item] of Object.entries(value)) {
|
|
16244
|
+
if (key === "actions" || key === "paths") {
|
|
16245
|
+
continue;
|
|
16246
|
+
}
|
|
16247
|
+
yield* stringsIn(item, path13 ? `${path13}.${key}` : key);
|
|
16248
|
+
}
|
|
16249
|
+
}
|
|
16250
|
+
}
|
|
16251
|
+
function checkPlaceholders(config, outputs) {
|
|
16252
|
+
const findings = [];
|
|
16253
|
+
const nodes = [...config.triggers ?? [], ...allActions(config.actions)];
|
|
16254
|
+
for (const node of nodes) {
|
|
16255
|
+
for (const [input, text] of stringsIn(node.inputs ?? {}, "")) {
|
|
16256
|
+
for (const token of new Set(text.match(TOKEN) ?? [])) {
|
|
16257
|
+
if (!parseWholePlaceholder(token)) {
|
|
16258
|
+
continue;
|
|
16259
|
+
}
|
|
16260
|
+
const [description] = describeInputData(
|
|
16261
|
+
{ id: node.id, type: node.type, inputs: { inputData: { probe: token } } },
|
|
16262
|
+
config,
|
|
16263
|
+
outputs
|
|
16264
|
+
);
|
|
16265
|
+
if (!description) {
|
|
16266
|
+
continue;
|
|
16267
|
+
}
|
|
16268
|
+
const status = description.verified ? "verified" : description.type.kind === "unknown" ? "unknown" : "declared";
|
|
16269
|
+
findings.push({
|
|
16270
|
+
nodeId: node.id,
|
|
16271
|
+
input,
|
|
16272
|
+
token,
|
|
16273
|
+
status,
|
|
16274
|
+
...description.note ? { note: description.note } : {}
|
|
16275
|
+
});
|
|
16276
|
+
}
|
|
16277
|
+
}
|
|
16278
|
+
}
|
|
16279
|
+
return findings;
|
|
16280
|
+
}
|
|
16281
|
+
|
|
16282
|
+
// src/commands/push.ts
|
|
16253
16283
|
function registerPush(program3) {
|
|
16254
16284
|
program3.command("push").description("Inline @@ file references and code files, then save the workflow as a new draft").option("--dir <path>", "workflow directory (default: the current directory)").option("--dry-run", "print the request body instead of sending it").option("--force", "push even when nothing changed locally or the server copy changed since the last sync").action(async (opts, command) => {
|
|
16255
16285
|
const options = globalOptions(command);
|
|
@@ -16308,8 +16338,9 @@ function registerPush(program3) {
|
|
|
16308
16338
|
syncedHash: hash
|
|
16309
16339
|
});
|
|
16310
16340
|
const studioUrl = `${target.studioUrl}/workflow/${config.id}`;
|
|
16311
|
-
|
|
16341
|
+
emitWithVersionNote(
|
|
16312
16342
|
options,
|
|
16343
|
+
config,
|
|
16313
16344
|
`Pushed "${packed.title}": version ${result.version}, draft ${result.draft}` + (inlined.length ? ` (${inlined.length} file${inlined.length === 1 ? "" : "s"} inlined)` : "") + `.
|
|
16314
16345
|
${studioUrl}` + (unbacked.length ? `
|
|
16315
16346
|
Warning: ${unbacked.length} placeholder(s) not backed by test data:
|
|
@@ -16347,7 +16378,7 @@ function renderList() {
|
|
|
16347
16378
|
return [
|
|
16348
16379
|
table.toString(),
|
|
16349
16380
|
"This CLI offers only these node types on purpose: code-first workflows use custom code for everything else.",
|
|
16350
|
-
'Run "softr
|
|
16381
|
+
'Run "softr workflows spec <TYPE>" for the inputs, output and an example of one type.'
|
|
16351
16382
|
].join("\n");
|
|
16352
16383
|
}
|
|
16353
16384
|
function integrationIdFields(entry) {
|
|
@@ -16382,7 +16413,7 @@ ${table.toString()}`);
|
|
|
16382
16413
|
const idFields = integrationIdFields(entry);
|
|
16383
16414
|
if (idFields.length) {
|
|
16384
16415
|
blocks.push(
|
|
16385
|
-
`${idFields.join(", ")}: run "softr
|
|
16416
|
+
`${idFields.join(", ")}: run "softr integrations" for the ids of the workspace's connected integrations.`
|
|
16386
16417
|
);
|
|
16387
16418
|
}
|
|
16388
16419
|
} else {
|
|
@@ -16424,6 +16455,75 @@ function registerSpec(program3) {
|
|
|
16424
16455
|
});
|
|
16425
16456
|
}
|
|
16426
16457
|
|
|
16458
|
+
// src/commands/upgrade.ts
|
|
16459
|
+
function registerUpgrade(program3) {
|
|
16460
|
+
program3.command("upgrade").description(
|
|
16461
|
+
"Move nodes on an older version of their type to the current one: CUSTOM_CODE 1.0.0 and 1.1.0 become 1.2.0 (the integrations list is added, the code file keeps its body and gets the new header)"
|
|
16462
|
+
).argument("[nodeIds...]", "the nodes to upgrade (default: every node the CLI can upgrade)").option("--dir <path>", "workflow directory (default: the current directory)").action(async (nodeIds, opts, command) => {
|
|
16463
|
+
const options = globalOptions(command);
|
|
16464
|
+
const dir = await findWorkflowDir(process.cwd(), opts.dir);
|
|
16465
|
+
const config = await readConfig(dir);
|
|
16466
|
+
const outdated = outdatedNodes(config);
|
|
16467
|
+
for (const id of nodeIds) {
|
|
16468
|
+
if (!locateNode(config, id)) {
|
|
16469
|
+
throw new CliError(
|
|
16470
|
+
`No node "${id}" in this workflow.`,
|
|
16471
|
+
'Node ids are the "id" values in softr-workflows.jsonc.'
|
|
16472
|
+
);
|
|
16473
|
+
}
|
|
16474
|
+
}
|
|
16475
|
+
const wanted = nodeIds.length ? outdated.filter((node) => nodeIds.includes(node.id)) : outdated;
|
|
16476
|
+
const current2 = nodeIds.filter((id) => !outdated.some((node) => node.id === id));
|
|
16477
|
+
const upgraded = [];
|
|
16478
|
+
const manual = [];
|
|
16479
|
+
const edits = [];
|
|
16480
|
+
for (const node of wanted) {
|
|
16481
|
+
const location = locateNode(config, node.id);
|
|
16482
|
+
const next = location ? upgradeOf(location.node) : void 0;
|
|
16483
|
+
if (!location || !next) {
|
|
16484
|
+
manual.push(node);
|
|
16485
|
+
continue;
|
|
16486
|
+
}
|
|
16487
|
+
edits.push({ path: location.path, value: next });
|
|
16488
|
+
upgraded.push(node);
|
|
16489
|
+
}
|
|
16490
|
+
if (edits.length) {
|
|
16491
|
+
await editConfig(dir, edits);
|
|
16492
|
+
}
|
|
16493
|
+
const headers = edits.length ? await refreshCodeHeaders(
|
|
16494
|
+
await readConfig(dir),
|
|
16495
|
+
dir,
|
|
16496
|
+
indexOutputs(await readStateFile(dir, OUTPUTS_FILE) ?? [])
|
|
16497
|
+
) : { refreshed: [], problems: [] };
|
|
16498
|
+
if (!wanted.length && manual.length === 0 && current2.length === 0) {
|
|
16499
|
+
emit(options, "Every node is on the current version of its type.", { upgraded: [], manual: [], current: [] });
|
|
16500
|
+
return;
|
|
16501
|
+
}
|
|
16502
|
+
if (headers.problems.length) {
|
|
16503
|
+
throw new CliError(
|
|
16504
|
+
`Upgraded, but a code file could not get its new header:
|
|
16505
|
+
${headers.problems.map((problem) => ` ${problem}`).join("\n")}`,
|
|
16506
|
+
'Fix the file, then run "pull" to regenerate its header.'
|
|
16507
|
+
);
|
|
16508
|
+
}
|
|
16509
|
+
const lines = [
|
|
16510
|
+
...upgraded.map((node) => `Upgraded ${node.id}: ${node.type} ${node.version} \u2192 ${node.current}.`),
|
|
16511
|
+
...manual.map(
|
|
16512
|
+
(node) => `No automatic upgrade for ${node.id}: ${node.type} ${node.version} \u2192 ${node.current}; compare its inputs with "spec ${node.type}" and set the version by hand.`
|
|
16513
|
+
),
|
|
16514
|
+
...current2.map((id) => `${id} is already on the current version.`),
|
|
16515
|
+
headers.refreshed.length ? `Headers refreshed: ${headers.refreshed.join(", ")}` : "",
|
|
16516
|
+
upgraded.length ? "Then: softr workflows push, softr workflows test --all." : ""
|
|
16517
|
+
].filter(Boolean);
|
|
16518
|
+
emit(options, lines.join("\n"), {
|
|
16519
|
+
upgraded: upgraded.map((node) => ({ id: node.id, type: node.type, from: node.version, to: node.current })),
|
|
16520
|
+
manual: manual.map((node) => ({ id: node.id, type: node.type, from: node.version, to: node.current })),
|
|
16521
|
+
current: current2,
|
|
16522
|
+
headersRefreshed: headers.refreshed
|
|
16523
|
+
});
|
|
16524
|
+
});
|
|
16525
|
+
}
|
|
16526
|
+
|
|
16427
16527
|
// src/commands/verify.ts
|
|
16428
16528
|
function section(title, lines, okText) {
|
|
16429
16529
|
return { title, ok: lines.length === 0, lines: lines.length ? lines : [okText] };
|
|
@@ -16479,7 +16579,7 @@ function registerVerify(program3) {
|
|
|
16479
16579
|
`in sync with the server (version ${remote.version}, draft ${remote.draft})`
|
|
16480
16580
|
);
|
|
16481
16581
|
let rows = await getWorkflowOutputs(client, config.id);
|
|
16482
|
-
let assessment = await assess(client, config.id
|
|
16582
|
+
let assessment = await assess(client, config.id);
|
|
16483
16583
|
let ran = [];
|
|
16484
16584
|
if (opts.testMissing && sync.ok && files.ok) {
|
|
16485
16585
|
const wanted = new Set(
|
|
@@ -16489,23 +16589,19 @@ function registerVerify(program3) {
|
|
|
16489
16589
|
if (nodes.length) {
|
|
16490
16590
|
ran = await runNodeTests(client, config.id, nodes, {}, true);
|
|
16491
16591
|
rows = await getWorkflowOutputs(client, config.id);
|
|
16492
|
-
assessment = await assess(client, config.id
|
|
16592
|
+
assessment = await assess(client, config.id);
|
|
16493
16593
|
}
|
|
16494
16594
|
}
|
|
16495
16595
|
await writeStateFile(dir, OUTPUTS_FILE, mergeRows([], rows));
|
|
16496
16596
|
const headers = await refreshCodeHeaders(config, dir, indexOutputs(rows));
|
|
16497
|
-
if (assessment.checked
|
|
16597
|
+
if (assessment.checked.version !== remote.version || assessment.checked.draft !== remote.draft) {
|
|
16498
16598
|
syncLines.push(
|
|
16499
16599
|
`the service assessed version ${assessment.checked.version}, draft ${assessment.checked.draft}, but the workflow is already at version ${remote.version}, draft ${remote.draft} (run verify again)`
|
|
16500
16600
|
);
|
|
16501
16601
|
}
|
|
16502
16602
|
const sections = [files, sync];
|
|
16503
|
-
if (assessment.validationProblems !== null) {
|
|
16504
|
-
sections.push(
|
|
16505
|
-
section("Validation", assessment.validationProblems, "the service accepts the workflow for publishing")
|
|
16506
|
-
);
|
|
16507
|
-
}
|
|
16508
16603
|
sections.push(
|
|
16604
|
+
section("Validation", assessment.validationProblems, "the service accepts the workflow for publishing"),
|
|
16509
16605
|
section(
|
|
16510
16606
|
"Tests",
|
|
16511
16607
|
assessment.testIssues.map((issue) => `${issue.id} (${issue.type}): ${issue.detail}`),
|
|
@@ -16525,17 +16621,15 @@ function registerVerify(program3) {
|
|
|
16525
16621
|
(item) => `${item.ok ? "OK " : "NOT"} ${item.title}
|
|
16526
16622
|
${item.lines.map((line) => ` ${line}`).join("\n")}`
|
|
16527
16623
|
),
|
|
16528
|
-
assessment.source === "client" ? CLIENT_SIDE_NOTE : "",
|
|
16529
16624
|
headers.refreshed.length ? `Headers refreshed: ${headers.refreshed.join(", ")}` : "",
|
|
16530
16625
|
ready ? "Ready to publish." : `Not ready to publish: ${sections.filter((item) => !item.ok).length} section(s) need attention.`
|
|
16531
16626
|
].filter(Boolean).join("\n\n");
|
|
16532
|
-
|
|
16627
|
+
emitWithVersionNote(options, config, human, {
|
|
16533
16628
|
workflowId: config.id,
|
|
16534
16629
|
ready,
|
|
16535
|
-
source: assessment.source,
|
|
16536
16630
|
files: { ok: files.ok, problems: fileProblems },
|
|
16537
16631
|
sync: { ok: sync.ok, problems: syncLines, server: { version: remote.version, draft: remote.draft } },
|
|
16538
|
-
validation:
|
|
16632
|
+
validation: { ok: assessment.validationProblems.length === 0, problems: assessment.validationProblems },
|
|
16539
16633
|
tests: {
|
|
16540
16634
|
ok: assessment.testIssues.length === 0,
|
|
16541
16635
|
issues: assessment.testIssues,
|
|
@@ -16549,10 +16643,36 @@ ${item.lines.map((line) => ` ${line}`).join("\n")}`
|
|
|
16549
16643
|
});
|
|
16550
16644
|
}
|
|
16551
16645
|
|
|
16646
|
+
// src/commands/workspaces.ts
|
|
16647
|
+
var import_cli_table36 = __toESM(require_cli_table3(), 1);
|
|
16648
|
+
function registerWorkspaces(program3) {
|
|
16649
|
+
program3.command("workspaces").description("The workspaces the stored token reaches, with your role in each").action(async (_opts, command) => {
|
|
16650
|
+
const options = globalOptions(command);
|
|
16651
|
+
const target = resolveTarget();
|
|
16652
|
+
const client = await authenticatedClient(target);
|
|
16653
|
+
const workspaces = await listWorkspaces(client);
|
|
16654
|
+
const table = new import_cli_table36.default({
|
|
16655
|
+
head: ["ID", "Name", "Role", "Default"],
|
|
16656
|
+
colWidths: [38, 34, 14, 9],
|
|
16657
|
+
wordWrap: true,
|
|
16658
|
+
style: { head: [], border: [], "padding-left": 1, "padding-right": 1 }
|
|
16659
|
+
});
|
|
16660
|
+
for (const workspace of workspaces) {
|
|
16661
|
+
table.push([workspace.id, workspace.name, workspace.userRole ?? "", workspace.isDefault ? "yes" : ""]);
|
|
16662
|
+
}
|
|
16663
|
+
emit(
|
|
16664
|
+
options,
|
|
16665
|
+
workspaces.length ? `${table.toString()}
|
|
16666
|
+
Pass --workspace <id> to a command, or set SOFTR_WORKSPACE_ID, to work in one of them.` : "The token reaches no workspace.",
|
|
16667
|
+
{ workspaces }
|
|
16668
|
+
);
|
|
16669
|
+
});
|
|
16670
|
+
}
|
|
16671
|
+
|
|
16552
16672
|
// package.json
|
|
16553
16673
|
var package_default = {
|
|
16554
16674
|
name: "@softr/cli",
|
|
16555
|
-
version: "0.0
|
|
16675
|
+
version: "0.1.0",
|
|
16556
16676
|
description: "The Softr command line: build, test and publish Softr workflows from local files; more Softr APIs to come",
|
|
16557
16677
|
keywords: [
|
|
16558
16678
|
"softr",
|
|
@@ -16562,8 +16682,7 @@ var package_default = {
|
|
|
16562
16682
|
license: "UNLICENSED",
|
|
16563
16683
|
type: "module",
|
|
16564
16684
|
bin: {
|
|
16565
|
-
softr: "dist/cli.mjs"
|
|
16566
|
-
"softr-workflows": "dist/cli.mjs"
|
|
16685
|
+
softr: "dist/cli.mjs"
|
|
16567
16686
|
},
|
|
16568
16687
|
files: [
|
|
16569
16688
|
"dist"
|
|
@@ -16580,7 +16699,7 @@ var package_default = {
|
|
|
16580
16699
|
format: "prettier --write .",
|
|
16581
16700
|
"format:check": "prettier --check .",
|
|
16582
16701
|
check: "npm run typecheck && npm run format:check && npm test && npm run build",
|
|
16583
|
-
"check:skill": "npx -y skills-ref@0.1.5 validate skills/softr-workflows"
|
|
16702
|
+
"check:skill": "npx -y skills-ref@0.1.5 validate skills/softr-cli && npx -y skills-ref@0.1.5 validate skills/softr-workflows"
|
|
16584
16703
|
},
|
|
16585
16704
|
publishConfig: {
|
|
16586
16705
|
access: "public"
|
|
@@ -16603,20 +16722,23 @@ var package_default = {
|
|
|
16603
16722
|
};
|
|
16604
16723
|
|
|
16605
16724
|
// src/cli.ts
|
|
16606
|
-
var program2 = new Command().name("softr").description("
|
|
16725
|
+
var program2 = new Command().name("softr").description("The Softr command line: workflows today, more of Softr to come").version(package_default.version).option("--json", "print results as JSON");
|
|
16607
16726
|
registerLogin(program2);
|
|
16608
|
-
|
|
16727
|
+
registerWorkspaces(program2);
|
|
16609
16728
|
registerIntegrations(program2);
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16729
|
+
var workflows = program2.command("workflows").description("Build, test and publish Softr Workflows from local files (one directory per workflow)");
|
|
16730
|
+
registerList(workflows);
|
|
16731
|
+
registerPull(workflows);
|
|
16732
|
+
registerPush(workflows);
|
|
16733
|
+
registerTest(workflows);
|
|
16734
|
+
registerVerify(workflows);
|
|
16735
|
+
registerOutputs(workflows);
|
|
16736
|
+
registerPlaceholder(workflows);
|
|
16737
|
+
registerInit(workflows);
|
|
16738
|
+
registerAdd(workflows);
|
|
16739
|
+
registerEdit(workflows);
|
|
16740
|
+
registerUpgrade(workflows);
|
|
16741
|
+
registerPublish(workflows);
|
|
16742
|
+
registerSpec(workflows);
|
|
16743
|
+
registerSchema(workflows);
|
|
16622
16744
|
program2.parseAsync(process.argv).catch(reportError);
|