@softr/cli 0.1.0 → 0.1.1-se21216.10
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 +3 -3
- package/dist/cli.mjs +86 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ ones. A workflow directory remembers the target it was pulled from.
|
|
|
49
49
|
| `workspaces` | The workspaces the stored token reaches, with your role in each and the default one. Commands that take `--workspace` default to `SOFTR_WORKSPACE_ID` when it is set, else to your only or default workspace. |
|
|
50
50
|
| `integrations [--workspace <id>] [--type <TYPE>]` | List the workspace's connected integrations (id, name, type, account, auth kind) from the studio API; the id goes into a node's `integrationId` input. Inside a workflow directory the workspace is the workflow's; `--type` filters. Credentials are never shown. |
|
|
51
51
|
|
|
52
|
-
Every command accepts `--json`. `SOFTR_TOKEN` overrides the stored token; `SOFTR_WORKSPACE_ID` is the default workspace.
|
|
52
|
+
Every command accepts `--json`. `SOFTR_TOKEN` overrides the stored token; `SOFTR_WORKSPACE_ID` is the default workspace; `SOFTR_APP_ID` pins the workflows `init` creates to that application.
|
|
53
53
|
|
|
54
54
|
## Workflows
|
|
55
55
|
|
|
@@ -133,10 +133,10 @@ node still on 1.0.0 or 1.1.0 keeps its synchronous runtime without `fetch`, and
|
|
|
133
133
|
|
|
134
134
|
| Command | What it does |
|
|
135
135
|
| -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
|
-
| `workflows list [--workspace <id>]`
|
|
136
|
+
| `workflows list [--workspace <id>] [--app <id>]` | List the workflows you can access, one table per workspace; `--app` keeps only those linked to that application. A workspace whose listing fails is reported at the end and skipped. |
|
|
137
137
|
| `workflows pull [<workflowId>] [--dir <path>] [--force]` | Download a workflow into a directory (new `./<title-slug>/` by default). Without an id, list what you can pull. |
|
|
138
138
|
| `workflows push [--dir <path>] [--dry-run] [--force]` | Inline `@@` references and code files, save as a new draft. Sends nothing when the files match the last sync, and refuses when the server copy changed since then (`--force` overrides both). Warns about placeholders that no saved test data backs. |
|
|
139
|
-
| `workflows init <title> [--workspace <id>] [--trigger <TYPE>] [--dir <path>]`
|
|
139
|
+
| `workflows init <title> [--workspace <id>] [--app <id>] [--trigger <TYPE>] [--dir <path>]` | Create a workflow on the server with one trigger (default `WEBHOOK`) and the local directory for it; `--app` pins it to an application, so the app lists it. |
|
|
140
140
|
| `workflows upgrade [<nodeId>...] [--dir <path>]` | 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). Every command that touches a workflow lists such nodes in a note. |
|
|
141
141
|
| `workflows add <TYPE> [--id <id>] [--after <nodeId>] [--in-loop <loopId>] [--to <actionId>] [--lang JAVASCRIPT\|PYTHON] [--title <t>]` | Append an action after a node (default: the only node without a successor) and the path to it, or append a trigger (`--to` connects it). Custom code gets `actions/<id>.js\|py` with an empty function. Comments in the config survive. |
|
|
142
142
|
| `workflows replace <nodeId> <TYPE>` | Give a node another type of the same kind: id, title and paths stay, inputs start from the new template. |
|
package/dist/cli.mjs
CHANGED
|
@@ -6098,9 +6098,9 @@ var SoftrApiError = class extends Error {
|
|
|
6098
6098
|
body;
|
|
6099
6099
|
hint;
|
|
6100
6100
|
};
|
|
6101
|
-
function hintFor(status) {
|
|
6101
|
+
function hintFor(status, client) {
|
|
6102
6102
|
if (status === 401 || status === 403) {
|
|
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";
|
|
6103
|
+
return client.kind === "proxy" ? "The target refused the request: no access to this workflow, or its credential is gone." : "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";
|
|
6104
6104
|
}
|
|
6105
6105
|
if (status === 409) {
|
|
6106
6106
|
return "The workflow changed on the server since the last pull. Pull again with --force, then re-apply your edits.";
|
|
@@ -6108,7 +6108,7 @@ function hintFor(status) {
|
|
|
6108
6108
|
return void 0;
|
|
6109
6109
|
}
|
|
6110
6110
|
function authHeaders(client) {
|
|
6111
|
-
return { Authorization: `Bearer ${client.token}` };
|
|
6111
|
+
return client.kind === "proxy" ? {} : { Authorization: `Bearer ${client.token}` };
|
|
6112
6112
|
}
|
|
6113
6113
|
async function send(client, service, method, path13, body) {
|
|
6114
6114
|
const init = {
|
|
@@ -6129,7 +6129,7 @@ async function send(client, service, method, path13, body) {
|
|
|
6129
6129
|
`${method} ${path13} failed with status ${response.status}${text ? `: ${text}` : ""}`,
|
|
6130
6130
|
response.status,
|
|
6131
6131
|
text,
|
|
6132
|
-
hintFor(response.status)
|
|
6132
|
+
hintFor(response.status, client)
|
|
6133
6133
|
);
|
|
6134
6134
|
}
|
|
6135
6135
|
async function apiRequest(client, method, path13, body) {
|
|
@@ -14397,8 +14397,8 @@ function updateWorkflow(client, workflowId, body) {
|
|
|
14397
14397
|
function getWorkflowOutputs(client, workflowId) {
|
|
14398
14398
|
return apiRequest(client, "GET", `/v1/workflows/${workflowId}/outputs`);
|
|
14399
14399
|
}
|
|
14400
|
-
function listWorkflows(client, workspaceId) {
|
|
14401
|
-
const query = new URLSearchParams({ workspaceId, limit: "500" });
|
|
14400
|
+
function listWorkflows(client, workspaceId, applicationId) {
|
|
14401
|
+
const query = new URLSearchParams({ workspaceId, ...applicationId ? { applicationId } : {}, limit: "500" });
|
|
14402
14402
|
return apiRequest(client, "GET", `/v1/workflows?${query}`);
|
|
14403
14403
|
}
|
|
14404
14404
|
function testAction(client, workflowId, hierarchyPath2, mode) {
|
|
@@ -14514,7 +14514,7 @@ function normalizeUrl(value, source) {
|
|
|
14514
14514
|
} catch {
|
|
14515
14515
|
throw new CliError(`${source} is not a valid URL: ${value}`);
|
|
14516
14516
|
}
|
|
14517
|
-
return url.origin
|
|
14517
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
14518
14518
|
}
|
|
14519
14519
|
function resolveTarget(remembered) {
|
|
14520
14520
|
const pick = (envVar, rememberedValue, fallback) => {
|
|
@@ -14547,6 +14547,8 @@ function tokenKind(token) {
|
|
|
14547
14547
|
|
|
14548
14548
|
// src/config/credentials.ts
|
|
14549
14549
|
var TOKEN_ENV_VAR = "SOFTR_TOKEN";
|
|
14550
|
+
var AUTH_ENV_VAR = "SOFTR_AUTH";
|
|
14551
|
+
var PROXY_AUTH = "proxy";
|
|
14550
14552
|
var REFRESH_MARGIN_MS = 5 * 60 * 1e3;
|
|
14551
14553
|
function credentialsPath() {
|
|
14552
14554
|
return path6.join(homedir(), ".softr", "credentials.json");
|
|
@@ -14611,6 +14613,9 @@ async function refreshedAccessToken(target, stored) {
|
|
|
14611
14613
|
}
|
|
14612
14614
|
}
|
|
14613
14615
|
async function authenticatedClient(target) {
|
|
14616
|
+
if (process.env[AUTH_ENV_VAR] === PROXY_AUTH) {
|
|
14617
|
+
return { target, kind: "proxy" };
|
|
14618
|
+
}
|
|
14614
14619
|
const fromEnv = process.env[TOKEN_ENV_VAR];
|
|
14615
14620
|
if (fromEnv) {
|
|
14616
14621
|
return { target, token: fromEnv, kind: tokenKind(fromEnv) };
|
|
@@ -14720,7 +14725,7 @@ function toRow(workflow, workspace) {
|
|
|
14720
14725
|
updatedAt: workflow.updatedAt ?? null
|
|
14721
14726
|
};
|
|
14722
14727
|
}
|
|
14723
|
-
async function collectWorkflows(client, workspaceId) {
|
|
14728
|
+
async function collectWorkflows(client, workspaceId, applicationId) {
|
|
14724
14729
|
const workspaces = await listWorkspaces(client);
|
|
14725
14730
|
const selected = workspaceId ? workspaces.filter((workspace) => workspace.id === workspaceId) : workspaces;
|
|
14726
14731
|
if (workspaceId && !selected.length) {
|
|
@@ -14732,7 +14737,7 @@ async function collectWorkflows(client, workspaceId) {
|
|
|
14732
14737
|
const listing = { workspaces: [], workflows: [], failures: [] };
|
|
14733
14738
|
for (const workspace of selected) {
|
|
14734
14739
|
try {
|
|
14735
|
-
const page = await listWorkflows(client, workspace.id);
|
|
14740
|
+
const page = await listWorkflows(client, workspace.id, applicationId);
|
|
14736
14741
|
listing.workspaces.push({ id: workspace.id, name: workspace.name });
|
|
14737
14742
|
listing.workflows.push(...page.items.map((workflow) => toRow(workflow, workspace)));
|
|
14738
14743
|
} catch (error) {
|
|
@@ -14783,16 +14788,16 @@ ${workflowTable(rows)}` : `${heading}
|
|
|
14783
14788
|
}
|
|
14784
14789
|
return blocks.join("\n\n");
|
|
14785
14790
|
}
|
|
14786
|
-
async function emitWorkflowList(options, client, workspaceId, human) {
|
|
14787
|
-
const listing = await collectWorkflows(client, workspaceId);
|
|
14791
|
+
async function emitWorkflowList(options, client, workspaceId, human, applicationId) {
|
|
14792
|
+
const listing = await collectWorkflows(client, workspaceId, applicationId);
|
|
14788
14793
|
emit(options, [human, renderWorkflows(listing)].filter(Boolean).join("\n\n"), { ...listing });
|
|
14789
14794
|
}
|
|
14790
14795
|
function registerList(program3) {
|
|
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) => {
|
|
14796
|
+
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)").option("--app <id>", "only the workflows linked to this application").action(async (opts, command) => {
|
|
14792
14797
|
const options = globalOptions(command);
|
|
14793
14798
|
const target = resolveTarget(await readState(process.cwd()));
|
|
14794
14799
|
const client = await authenticatedClient(target);
|
|
14795
|
-
await emitWorkflowList(options, client, opts.workspace ?? process.env["SOFTR_WORKSPACE_ID"]);
|
|
14800
|
+
await emitWorkflowList(options, client, opts.workspace ?? process.env["SOFTR_WORKSPACE_ID"], void 0, opts.app);
|
|
14796
14801
|
});
|
|
14797
14802
|
}
|
|
14798
14803
|
|
|
@@ -15263,58 +15268,63 @@ function registerInit(program3) {
|
|
|
15263
15268
|
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
15269
|
"--workspace <id>",
|
|
15265
15270
|
"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(
|
|
15267
|
-
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15271
|
+
).option("--app <id>", "application to link the workflow to (default: $SOFTR_APP_ID when set)").option("--trigger <TYPE>", 'trigger type from "spec" (default WEBHOOK)', "WEBHOOK").option("--dir <path>", "directory to create (default: ./<title-slug>)").action(
|
|
15272
|
+
async (title, opts, command) => {
|
|
15273
|
+
const options = globalOptions(command);
|
|
15274
|
+
const cwd = process.cwd();
|
|
15275
|
+
const dir = path10.resolve(cwd, opts.dir ?? slug(title));
|
|
15276
|
+
if (await configExists(dir)) {
|
|
15277
|
+
throw new CliError(`${path10.join(dir, CONFIG_FILE_NAME)} already exists.`, "Pick another --dir or title.");
|
|
15278
|
+
}
|
|
15279
|
+
const trigger = findCatalogEntry(opts.trigger, "trigger");
|
|
15280
|
+
const target = resolveTarget();
|
|
15281
|
+
const client = await authenticatedClient(target);
|
|
15282
|
+
const workspaceId = await resolveWorkspace(client, opts.workspace ?? process.env["SOFTR_WORKSPACE_ID"]);
|
|
15283
|
+
const pinToApplicationId = opts.app ?? process.env["SOFTR_APP_ID"];
|
|
15284
|
+
const created = await createWorkflow(client, {
|
|
15285
|
+
workspaceId,
|
|
15286
|
+
applicationId: null,
|
|
15287
|
+
...pinToApplicationId ? { pinToApplicationId } : {},
|
|
15288
|
+
title,
|
|
15289
|
+
thumbnail: null,
|
|
15290
|
+
triggers: [{ id: TRIGGER_ID, type: trigger.type, version: trigger.version, inputs: trigger.template }],
|
|
15291
|
+
actions: [],
|
|
15292
|
+
paths: [],
|
|
15293
|
+
// Like the studio: the author's time zone, so schedules and waits mean what they read.
|
|
15294
|
+
configuration: { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC" }
|
|
15295
|
+
});
|
|
15296
|
+
const workflow = await getWorkflow(client, created.id);
|
|
15297
|
+
const config = { $schema: SCHEMA_POINTER, ...toLocalConfig(workflow) };
|
|
15298
|
+
const configFile = await writeConfig(dir, config);
|
|
15299
|
+
await writeSchemaFile(dir);
|
|
15300
|
+
const syncedHash = definitionHash((await packWorkflow(config, dir)).packed);
|
|
15301
|
+
await writeState(dir, {
|
|
15302
|
+
...target,
|
|
15303
|
+
workflowId: created.id,
|
|
15304
|
+
version: workflow.version,
|
|
15305
|
+
draft: workflow.draft,
|
|
15306
|
+
syncedHash
|
|
15307
|
+
});
|
|
15308
|
+
const studioUrl = `${target.studioUrl}/workflow/${created.id}`;
|
|
15309
|
+
emit(
|
|
15310
|
+
options,
|
|
15311
|
+
`Created "${title}" (${created.id}) with a ${trigger.type} trigger "${TRIGGER_ID}" in workspace ${workspaceId}${pinToApplicationId ? `, linked to application ${pinToApplicationId}` : ""}.
|
|
15304
15312
|
${path10.relative(cwd, configFile)}
|
|
15305
15313
|
${studioUrl}
|
|
15306
15314
|
Next: softr workflows add <TYPE> --dir ${path10.relative(cwd, dir) || "."} (see "spec" for the types), then push and test.`,
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15315
|
+
{
|
|
15316
|
+
workflowId: created.id,
|
|
15317
|
+
workspaceId,
|
|
15318
|
+
pinnedToApplicationId: pinToApplicationId ?? null,
|
|
15319
|
+
title,
|
|
15320
|
+
triggerId: TRIGGER_ID,
|
|
15321
|
+
triggerType: trigger.type,
|
|
15322
|
+
dir,
|
|
15323
|
+
studioUrl
|
|
15324
|
+
}
|
|
15325
|
+
);
|
|
15326
|
+
}
|
|
15327
|
+
);
|
|
15318
15328
|
}
|
|
15319
15329
|
|
|
15320
15330
|
// src/commands/integrations.ts
|
|
@@ -15542,6 +15552,11 @@ function registerLogin(program3) {
|
|
|
15542
15552
|
).option("--no-browser", "browser login, but print the URL instead of opening it (for remote shells and agents)").action(async (opts, command) => {
|
|
15543
15553
|
const options = globalOptions(command);
|
|
15544
15554
|
const target = resolveTarget();
|
|
15555
|
+
if (process.env[AUTH_ENV_VAR] === "proxy") {
|
|
15556
|
+
throw new CliError(
|
|
15557
|
+
`Login is not needed: the target authenticates the requests itself ($${AUTH_ENV_VAR}=proxy).`
|
|
15558
|
+
);
|
|
15559
|
+
}
|
|
15545
15560
|
const token = (opts.token ?? process.env[TOKEN_ENV_VAR] ?? "").trim();
|
|
15546
15561
|
const saved = token ? pastedToken(token) : opts.browser ? await interactiveLogin(target) : await browserLogin(target, true);
|
|
15547
15562
|
const identity = await identify({ target, token: saved.token, kind: saved.kind });
|
|
@@ -15561,6 +15576,14 @@ function registerLogin(program3) {
|
|
|
15561
15576
|
const options = globalOptions(command);
|
|
15562
15577
|
const target = resolveTarget();
|
|
15563
15578
|
const client = await authenticatedClient(target);
|
|
15579
|
+
if (client.kind === "proxy") {
|
|
15580
|
+
emit(
|
|
15581
|
+
options,
|
|
15582
|
+
`Requests to ${targetHost(target)} carry no token: the target authenticates them itself ($${AUTH_ENV_VAR}=proxy).`,
|
|
15583
|
+
{ kind: "proxy", host: targetHost(target) }
|
|
15584
|
+
);
|
|
15585
|
+
return;
|
|
15586
|
+
}
|
|
15564
15587
|
const identity = await identify(client);
|
|
15565
15588
|
const stored = process.env[TOKEN_ENV_VAR] ? void 0 : await storedToken(target);
|
|
15566
15589
|
const expiry = stored?.expiresAt ? ` Expires ${stored.expiresAt.slice(0, 16).replace("T", " ")} UTC.` : "";
|
|
@@ -16672,7 +16695,7 @@ Pass --workspace <id> to a command, or set SOFTR_WORKSPACE_ID, to work in one of
|
|
|
16672
16695
|
// package.json
|
|
16673
16696
|
var package_default = {
|
|
16674
16697
|
name: "@softr/cli",
|
|
16675
|
-
version: "0.1.
|
|
16698
|
+
version: "0.1.1-se21216.10",
|
|
16676
16699
|
description: "The Softr command line: build, test and publish Softr workflows from local files; more Softr APIs to come",
|
|
16677
16700
|
keywords: [
|
|
16678
16701
|
"softr",
|