@solidactions/cli 1.19.0 → 1.21.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/dist/commands/deploy.js +73 -19
- package/dist/commands/dev-shim.mjs +194 -0
- package/dist/commands/dev.js +165 -7
- package/dist/commands/env-set.js +15 -0
- package/dist/commands/init.js +3 -2
- package/dist/commands/project-logs.js +26 -3
- package/dist/commands/run-list.js +41 -19
- package/dist/commands/webhook-secret.js +74 -0
- package/dist/index.js +22 -4
- package/package.json +2 -2
package/dist/commands/deploy.js
CHANGED
|
@@ -3,9 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cacheBusterEntry = cacheBusterEntry;
|
|
7
|
+
exports.shouldPrintWorkspaceMismatch = shouldPrintWorkspaceMismatch;
|
|
6
8
|
exports.deploy = deploy;
|
|
9
|
+
exports.shouldPrintWebhookSecretNotice = shouldPrintWebhookSecretNotice;
|
|
7
10
|
const fs_1 = __importDefault(require("fs"));
|
|
8
11
|
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const crypto_1 = require("crypto");
|
|
9
13
|
const archiver_1 = __importDefault(require("archiver"));
|
|
10
14
|
const axios_1 = __importDefault(require("axios"));
|
|
11
15
|
const form_data_1 = __importDefault(require("form-data"));
|
|
@@ -87,6 +91,21 @@ function validateProject(sourceDir) {
|
|
|
87
91
|
}
|
|
88
92
|
return { valid: errors.length === 0, errors, warnings };
|
|
89
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* When `noCache` is true, returns an archive entry with a unique name and
|
|
96
|
+
* random content that busts the build-layer content hash (Blaxel/Daytona S3
|
|
97
|
+
* MD5 and BuildKit COPY . layer). Returns null when noCache is false/undefined.
|
|
98
|
+
*/
|
|
99
|
+
function cacheBusterEntry(noCache) {
|
|
100
|
+
if (!noCache) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const uuid = (0, crypto_1.randomUUID)();
|
|
104
|
+
return {
|
|
105
|
+
name: `tenantcode/sa-nocache-${uuid}`,
|
|
106
|
+
content: `force-rebuild ${uuid} ${Date.now()}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
90
109
|
/**
|
|
91
110
|
* Push YAML env declarations to the project.
|
|
92
111
|
* This registers all YAML-declared vars and their mappings.
|
|
@@ -113,6 +132,15 @@ async function pushYamlDeclarations(config, projectSlug, yamlConfig) {
|
|
|
113
132
|
console.error(chalk_1.default.yellow('Warning: Failed to sync YAML declarations:'), error.response?.data?.message || error.message);
|
|
114
133
|
}
|
|
115
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Returns true only when a 404 on the environment-specific project lookup
|
|
137
|
+
* will cause the deploy command to abort (non-production environment and
|
|
138
|
+
* --create was not passed). On these paths the workspace-mismatch warning
|
|
139
|
+
* should print. On all success/create paths it must be suppressed.
|
|
140
|
+
*/
|
|
141
|
+
function shouldPrintWorkspaceMismatch(environment, willCreate) {
|
|
142
|
+
return environment !== 'production' && !willCreate;
|
|
143
|
+
}
|
|
116
144
|
async function deploy(projectName, sourcePath, options = {}) {
|
|
117
145
|
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
118
146
|
let workspaceMismatchPrinted = false;
|
|
@@ -135,9 +163,10 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
135
163
|
process.exit(1);
|
|
136
164
|
}
|
|
137
165
|
// -------------------------------------------------------------------------
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
166
|
+
// First-deploy check — must happen BEFORE we apply any env default so that
|
|
167
|
+
// first-time deploys without -e get a helpful error prompting a deliberate
|
|
168
|
+
// environment choice, instead of silently defaulting. (Any environment can
|
|
169
|
+
// stand alone — production is not required to exist first.)
|
|
141
170
|
// -------------------------------------------------------------------------
|
|
142
171
|
let productionExists = null; // null = unknown (error path)
|
|
143
172
|
let productionSlug = null;
|
|
@@ -151,11 +180,8 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
151
180
|
catch (error) {
|
|
152
181
|
if (error.response?.status === 404) {
|
|
153
182
|
productionExists = false;
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
// hint. Surface that augmented message so the user sees the hint before falling through to
|
|
157
|
-
// the first-deploy flow.
|
|
158
|
-
printWorkspaceMismatchOnce(error);
|
|
183
|
+
// Project doesn't exist yet — this is the normal first-deploy path.
|
|
184
|
+
// Do NOT print a warning here; the deploy proceeds to create/deploy successfully.
|
|
159
185
|
}
|
|
160
186
|
else {
|
|
161
187
|
// 5xx, network error, auth failure, etc. — fail conservatively rather
|
|
@@ -164,14 +190,15 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
164
190
|
process.exit(1);
|
|
165
191
|
}
|
|
166
192
|
}
|
|
167
|
-
// Decision: if
|
|
168
|
-
//
|
|
193
|
+
// Decision: if this is the first deploy (project doesn't exist yet) and no -e
|
|
194
|
+
// flag was given, prompt the user to choose an environment explicitly so the
|
|
195
|
+
// first deploy is deliberate. Any environment can be the first one.
|
|
169
196
|
if (productionExists === false && explicitEnv === undefined) {
|
|
170
197
|
console.error(chalk_1.default.red(`\nThis is the first deploy of "${projectName}" — please pick an environment explicitly:\n`));
|
|
171
198
|
console.error(` solidactions project deploy ${projectName} <path> -e production # most projects start here`);
|
|
172
|
-
console.error(` solidactions project deploy ${projectName} <path> -e dev # dev
|
|
173
|
-
console.error(` solidactions project deploy ${projectName} <path> -e staging # staging
|
|
174
|
-
console.error(chalk_1.default.gray('\nTip:
|
|
199
|
+
console.error(` solidactions project deploy ${projectName} <path> -e dev # start in dev (no production required first)`);
|
|
200
|
+
console.error(` solidactions project deploy ${projectName} <path> -e staging # start in staging`);
|
|
201
|
+
console.error(chalk_1.default.gray('\nTip: production is the usual default, but any environment can stand alone — pick whichever you want this project to start with.'));
|
|
175
202
|
process.exit(1);
|
|
176
203
|
}
|
|
177
204
|
// Apply default: if production already exists and the user didn't pass -e,
|
|
@@ -229,12 +256,11 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
229
256
|
}
|
|
230
257
|
catch (error) {
|
|
231
258
|
if (error.response?.status === 404) {
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (environment !== 'production' && !options.create) {
|
|
259
|
+
// For non-production environments, require --create or give a clear hint.
|
|
260
|
+
// Only print the workspace-mismatch warning on this abort path — not on
|
|
261
|
+
// the success/create path (shouldPrintWorkspaceMismatch guards this).
|
|
262
|
+
if (shouldPrintWorkspaceMismatch(environment, options.create ?? false)) {
|
|
263
|
+
printWorkspaceMismatchOnce(error);
|
|
238
264
|
console.error(chalk_1.default.red(`\nProject "${projectName}" doesn't have a ${environment} environment.\n`));
|
|
239
265
|
console.error(` If production is the intended target:`);
|
|
240
266
|
console.error(` solidactions project deploy ${projectName} <path> -e production`);
|
|
@@ -363,6 +389,12 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
363
389
|
if (yamlConfig) {
|
|
364
390
|
await pushYamlDeclarations(config, projectSlug, yamlConfig);
|
|
365
391
|
}
|
|
392
|
+
if (yamlConfig && shouldPrintWebhookSecretNotice(yamlConfig.workflows ?? [])) {
|
|
393
|
+
const envFlag = environment !== 'dev' ? ` -e ${environment}` : '';
|
|
394
|
+
console.log('');
|
|
395
|
+
console.log(chalk_1.default.blue(`ℹ Webhook secret: run \`solidactions webhook secret ${projectName}${envFlag}\` to retrieve the generated secret.`));
|
|
396
|
+
console.log(chalk_1.default.gray(` Set the same value in your sender (e.g. Telegram setWebhook secret_token).`));
|
|
397
|
+
}
|
|
366
398
|
fs_1.default.unlinkSync(archivePath);
|
|
367
399
|
process.exit(0);
|
|
368
400
|
}
|
|
@@ -427,5 +459,27 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
427
459
|
'RUN npm run build',
|
|
428
460
|
].join('\n') + '\n';
|
|
429
461
|
archive.append(universalDockerfile, { name: 'Dockerfile' });
|
|
462
|
+
// --no-cache / --force-rebuild: inject a unique random-content file so all
|
|
463
|
+
// cache layers (Blaxel content hash, S3 context.tar MD5, BuildKit COPY .)
|
|
464
|
+
// see a new directory fingerprint and are forced to rebuild from scratch.
|
|
465
|
+
const buster = cacheBusterEntry(options.noCache ?? false);
|
|
466
|
+
if (buster) {
|
|
467
|
+
console.log(chalk_1.default.yellow('🔄 --no-cache: injecting cache-buster, forcing a fresh build'));
|
|
468
|
+
archive.append(buster.content, { name: buster.name });
|
|
469
|
+
}
|
|
430
470
|
await archive.finalize();
|
|
431
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Returns true if any workflow in the project has a webhook trigger that
|
|
474
|
+
* uses HMAC or header authentication (i.e. requires a shared secret).
|
|
475
|
+
* Used to gate the post-deploy notice pointing authors to `webhook secret`.
|
|
476
|
+
*/
|
|
477
|
+
function shouldPrintWebhookSecretNotice(workflows) {
|
|
478
|
+
return workflows.some(wf => {
|
|
479
|
+
if (wf.trigger !== 'webhook') {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
const auth = wf.webhook?.auth ?? 'hmac';
|
|
483
|
+
return auth === 'hmac' || auth === 'header';
|
|
484
|
+
});
|
|
485
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev-shim.mjs — ESM subprocess entrypoint for `solidactions dev`.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE IS .mjs (NOT .ts):
|
|
5
|
+
* The CLI tsconfig compiles "module": "commonjs". tsc rewrites await import(x)
|
|
6
|
+
* to Promise.resolve(x).then(s => require(s)) in CJS output (confirmed in
|
|
7
|
+
* dist/commands/dev.js:217). A .ts shim compiled by this tsconfig would also
|
|
8
|
+
* produce require(), which runs under tsx's CJS hook — the CJS hook does NOT
|
|
9
|
+
* remap .js→.ts. A hand-written .mjs file bypasses tsc entirely, so its
|
|
10
|
+
* await import() stays a real ESM dynamic import. tsx loads .mjs files under
|
|
11
|
+
* the ESM loader, which DOES remap .js→.ts for all transitive imports.
|
|
12
|
+
*
|
|
13
|
+
* HOW IT IS SPAWNED:
|
|
14
|
+
* npx tsx dist/commands/dev-shim.mjs <userEntry.ts>
|
|
15
|
+
*
|
|
16
|
+
* CONTEXT (from parent, via env var — no secrets to disk):
|
|
17
|
+
* SOLIDACTIONS_DEV_SHIM_CONTEXT: JSON-encoded DevShimContext object (see below).
|
|
18
|
+
*
|
|
19
|
+
* RESULT (written to temp file, path in context):
|
|
20
|
+
* The result file path is passed in context.resultPath. A private temp file
|
|
21
|
+
* (mode 0o600, random UUID name) is written by the parent before spawn; the
|
|
22
|
+
* shim writes its result JSON there; the parent reads it and unlinks it.
|
|
23
|
+
*
|
|
24
|
+
* This file is copied to dist/commands/dev-shim.mjs by the build script.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import path from 'path';
|
|
28
|
+
import fs from 'fs';
|
|
29
|
+
import { createRequire } from 'module';
|
|
30
|
+
import { randomUUID } from 'crypto';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Serialize an unknown error value to a plain JSON-serialisable object.
|
|
34
|
+
* JSON.stringify(new Error('x')) produces {} — this ensures the message
|
|
35
|
+
* is preserved so the CLI's r.error?.message reads correctly.
|
|
36
|
+
*
|
|
37
|
+
* @param {unknown} e
|
|
38
|
+
* @returns {{ message: string; name: string; stack?: string }}
|
|
39
|
+
*/
|
|
40
|
+
function serializeError(e) {
|
|
41
|
+
if (e instanceof Error) {
|
|
42
|
+
return { message: e.message, name: e.name, stack: e.stack };
|
|
43
|
+
}
|
|
44
|
+
return { message: String(e), name: 'Error' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
const contextJson = process.env['SOLIDACTIONS_DEV_SHIM_CONTEXT'];
|
|
49
|
+
|
|
50
|
+
if (!contextJson) {
|
|
51
|
+
process.stderr.write('dev-shim: missing SOLIDACTIONS_DEV_SHIM_CONTEXT env var\n');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let ctx;
|
|
56
|
+
try {
|
|
57
|
+
ctx = JSON.parse(contextJson);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
process.stderr.write(`dev-shim: failed to parse context JSON: ${e?.message ?? e}\n`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { entryPath, input, vars, mockBaseUrl, mockApiKey, runUuid, workerSessionId, resultPath } = ctx;
|
|
64
|
+
|
|
65
|
+
// Resolve SDK from the user entry's directory — ensures we hit the same SDK
|
|
66
|
+
// registry instance the workflow populated, not the CLI's own linked copy.
|
|
67
|
+
const _require = createRequire(entryPath);
|
|
68
|
+
|
|
69
|
+
let shimResult;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Import the user entry file. Under tsx's ESM loader (active because this
|
|
73
|
+
// file is .mjs), dynamic import() correctly remaps .js-extension imports
|
|
74
|
+
// to .ts files for all transitive requires in the user project.
|
|
75
|
+
const mod = await import(entryPath);
|
|
76
|
+
|
|
77
|
+
let descriptor;
|
|
78
|
+
|
|
79
|
+
if (mod?.default && typeof mod.default?.run === 'function') {
|
|
80
|
+
descriptor = mod.default;
|
|
81
|
+
} else {
|
|
82
|
+
const sdkMain = _require.resolve('@solidactions/sdk');
|
|
83
|
+
const registryPath = path.resolve(sdkMain, '..', 'invoke', 'registry.js');
|
|
84
|
+
const registry = _require(registryPath);
|
|
85
|
+
descriptor = registry.__getRegisteredWorkflows()[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!descriptor) {
|
|
89
|
+
shimResult = {
|
|
90
|
+
result: {
|
|
91
|
+
status: 'failed',
|
|
92
|
+
error: serializeError(new Error('no workflow registered after importing the entry file')),
|
|
93
|
+
phase: 'run',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
fs.writeFileSync(resultPath, JSON.stringify(shimResult), { mode: 0o600 });
|
|
97
|
+
process.exit(1);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Create the run-row BEFORE invoking (descriptor is now available for
|
|
102
|
+
// workflowName — preserves current behaviour where descriptor.name is
|
|
103
|
+
// used at src/commands/dev.ts:319).
|
|
104
|
+
const sdkMainForInvoke = _require.resolve('@solidactions/sdk');
|
|
105
|
+
const httpClientPath = path.resolve(sdkMainForInvoke, '..', 'http_client.js');
|
|
106
|
+
const { HttpClient } = _require(httpClientPath);
|
|
107
|
+
try {
|
|
108
|
+
const client = new HttpClient({ baseUrl: mockBaseUrl, apiKey: mockApiKey });
|
|
109
|
+
await client.post('/runs/status', {
|
|
110
|
+
workflowUUID: runUuid,
|
|
111
|
+
status: 'PENDING',
|
|
112
|
+
workflowName: descriptor.name ?? '',
|
|
113
|
+
workflowClassName: '',
|
|
114
|
+
workflowConfigName: '',
|
|
115
|
+
output: null,
|
|
116
|
+
error: null,
|
|
117
|
+
authenticatedUser: '',
|
|
118
|
+
assumedRole: '',
|
|
119
|
+
authenticatedRoles: [],
|
|
120
|
+
request: {},
|
|
121
|
+
executorId: 'local-dev',
|
|
122
|
+
applicationVersion: '0',
|
|
123
|
+
applicationID: 'local-dev',
|
|
124
|
+
createdAt: Date.now(),
|
|
125
|
+
priority: 0,
|
|
126
|
+
ownerXid: randomUUID(),
|
|
127
|
+
options: {},
|
|
128
|
+
});
|
|
129
|
+
} catch (_e) {
|
|
130
|
+
// Best-effort, symmetric with the parent's swallow.
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Build InvokeCtx using the context passed from the parent.
|
|
134
|
+
const invokeCtx = {
|
|
135
|
+
input: JSON.parse(input || '{}'),
|
|
136
|
+
vars: Object.freeze(vars),
|
|
137
|
+
run: {
|
|
138
|
+
triggerId: 'local-dev',
|
|
139
|
+
runUuid,
|
|
140
|
+
runSecret: 'local-dev',
|
|
141
|
+
workerSessionId,
|
|
142
|
+
},
|
|
143
|
+
app: {
|
|
144
|
+
appVersion: '0',
|
|
145
|
+
appId: 'local-dev',
|
|
146
|
+
tenantId: 'local-dev',
|
|
147
|
+
},
|
|
148
|
+
api: {
|
|
149
|
+
url: mockBaseUrl,
|
|
150
|
+
key: mockApiKey,
|
|
151
|
+
},
|
|
152
|
+
mode: 'local',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Invoke via internal SDK path (same resolution as runDev).
|
|
156
|
+
const invokePath = path.resolve(sdkMainForInvoke, '..', 'invoke', 'invoke.js');
|
|
157
|
+
const { invoke } = _require(invokePath);
|
|
158
|
+
const result = await invoke(descriptor, invokeCtx);
|
|
159
|
+
|
|
160
|
+
// Serialize Error objects before JSON.stringify (Error → {} otherwise).
|
|
161
|
+
const serializedResult = {
|
|
162
|
+
...result,
|
|
163
|
+
...(result?.error ? { error: serializeError(result.error) } : {}),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
shimResult = { result: serializedResult };
|
|
167
|
+
} catch (e) {
|
|
168
|
+
// Surface module-resolution failures with an actionable message.
|
|
169
|
+
let errorObj = serializeError(e);
|
|
170
|
+
if (e?.code === 'MODULE_NOT_FOUND' || /Cannot find module/i.test(errorObj.message)) {
|
|
171
|
+
const attempted = e?.requireStack?.[0] ?? entryPath;
|
|
172
|
+
errorObj = {
|
|
173
|
+
...errorObj,
|
|
174
|
+
message:
|
|
175
|
+
`Cannot find module: ${errorObj.message}\n` +
|
|
176
|
+
`\nThis usually means a TypeScript file uses a .js-extension import (e.g. './lib/foo.js')\n` +
|
|
177
|
+
`but the real file is foo.ts and could not be found by the loader.\n` +
|
|
178
|
+
`\nVerify that your project's tsconfig.json sets "moduleResolution": "NodeNext" or "Bundler"\n` +
|
|
179
|
+
`and that all imported files exist as .ts sources in: ${path.dirname(attempted)}`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
shimResult = {
|
|
183
|
+
result: { status: 'failed', error: errorObj, phase: 'run' },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fs.writeFileSync(resultPath, JSON.stringify(shimResult), { mode: 0o600 });
|
|
188
|
+
process.exit(shimResult.result?.status === 'failed' ? 1 : 0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
main().catch((e) => {
|
|
192
|
+
process.stderr.write(`dev-shim: unexpected top-level error: ${e?.message ?? e}\n`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
});
|
package/dist/commands/dev.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.dev = dev;
|
|
|
42
42
|
const child_process_1 = require("child_process");
|
|
43
43
|
const path_1 = __importDefault(require("path"));
|
|
44
44
|
const fs_1 = __importDefault(require("fs"));
|
|
45
|
+
const os_1 = __importDefault(require("os"));
|
|
45
46
|
const chalk_1 = __importDefault(require("chalk"));
|
|
46
47
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
47
48
|
const crypto_1 = require("crypto");
|
|
@@ -89,6 +90,127 @@ function findSolidActionsRoot(startPath) {
|
|
|
89
90
|
}
|
|
90
91
|
return null;
|
|
91
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Run the user workflow entry by spawning `npx tsx dist/commands/dev-shim.mjs`.
|
|
95
|
+
*
|
|
96
|
+
* WHY THE CHILD PROCESS (not in-process import):
|
|
97
|
+
* The CLI tsconfig compiles "module": "commonjs", so tsc rewrites await import(x)
|
|
98
|
+
* to require(x) in every .ts file it compiles. tsx's CJS hook does NOT remap
|
|
99
|
+
* .js→.ts in require() calls. A hand-written .mjs shim bypasses tsc entirely,
|
|
100
|
+
* so its import() stays a real ESM dynamic import. When tsx loads .mjs, it uses
|
|
101
|
+
* the ESM loader, which DOES remap .js→.ts for all transitive imports.
|
|
102
|
+
*
|
|
103
|
+
* SECRETS: context is passed as a JSON env var — OAuth proxyToken values never
|
|
104
|
+
* touch disk. The result uses a private temp file (mode 0o600, UUID name).
|
|
105
|
+
*/
|
|
106
|
+
async function runDevViaShim(entryPath, input, vars, mockBaseUrl, runUuid, workerSessionId, stdoutLines, stderrLines) {
|
|
107
|
+
const shimPath = path_1.default.resolve(__dirname, 'dev-shim.mjs');
|
|
108
|
+
// Private temp file for the result (mode 0o600, UUID name).
|
|
109
|
+
const resultPath = path_1.default.join(os_1.default.tmpdir(), `sa-dev-result-${runUuid}-${(0, crypto_1.randomUUID)()}.json`);
|
|
110
|
+
// Write placeholder so the shim can overwrite it.
|
|
111
|
+
fs_1.default.writeFileSync(resultPath, '', { mode: 0o600 });
|
|
112
|
+
const shimCtx = {
|
|
113
|
+
entryPath,
|
|
114
|
+
input,
|
|
115
|
+
vars,
|
|
116
|
+
mockBaseUrl,
|
|
117
|
+
mockApiKey: 'local-dev',
|
|
118
|
+
runUuid,
|
|
119
|
+
workerSessionId,
|
|
120
|
+
resultPath,
|
|
121
|
+
};
|
|
122
|
+
try {
|
|
123
|
+
// Use async spawn (NOT spawnSync): the SDK mock backend runs in THIS
|
|
124
|
+
// process's event loop, and the shim makes HTTP calls back to it
|
|
125
|
+
// (run-row creation, step/sleep/recv routes). spawnSync would block the
|
|
126
|
+
// parent's event loop, deadlocking the mock server — so we await an
|
|
127
|
+
// async child and pump its stdout/stderr while the loop stays live.
|
|
128
|
+
let timedOut = false;
|
|
129
|
+
const spawnError = await new Promise((resolve) => {
|
|
130
|
+
const child = (0, child_process_1.spawn)('npx', ['tsx', shimPath], {
|
|
131
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
132
|
+
env: {
|
|
133
|
+
...process.env,
|
|
134
|
+
SOLIDACTIONS_DEV_SHIM_CONTEXT: JSON.stringify(shimCtx),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
let stdoutBuf = '';
|
|
138
|
+
let stderrBuf = '';
|
|
139
|
+
child.stdout.setEncoding('utf8');
|
|
140
|
+
child.stderr.setEncoding('utf8');
|
|
141
|
+
child.stdout.on('data', (chunk) => { stdoutBuf += chunk; });
|
|
142
|
+
child.stderr.on('data', (chunk) => { stderrBuf += chunk; });
|
|
143
|
+
const timer = setTimeout(() => {
|
|
144
|
+
timedOut = true;
|
|
145
|
+
child.kill('SIGKILL');
|
|
146
|
+
}, 120_000);
|
|
147
|
+
child.on('error', (e) => {
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
if (stdoutBuf.trim()) {
|
|
150
|
+
stdoutLines.push(stdoutBuf.trimEnd());
|
|
151
|
+
}
|
|
152
|
+
if (stderrBuf.trim()) {
|
|
153
|
+
stderrLines.push(stderrBuf.trimEnd());
|
|
154
|
+
}
|
|
155
|
+
resolve(e);
|
|
156
|
+
});
|
|
157
|
+
child.on('close', () => {
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
if (stdoutBuf.trim()) {
|
|
160
|
+
stdoutLines.push(stdoutBuf.trimEnd());
|
|
161
|
+
}
|
|
162
|
+
if (stderrBuf.trim()) {
|
|
163
|
+
stderrLines.push(stderrBuf.trimEnd());
|
|
164
|
+
}
|
|
165
|
+
resolve(undefined);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
if (spawnError) {
|
|
169
|
+
const code = spawnError.code;
|
|
170
|
+
const msg = code === 'ENOENT'
|
|
171
|
+
? 'npx not found. Make sure Node.js is installed.'
|
|
172
|
+
: `Failed to spawn tsx shim: ${spawnError.message}`;
|
|
173
|
+
return {
|
|
174
|
+
result: { status: 'failed', error: { message: msg, name: 'Error' }, phase: 'run' },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
let resultContent = '';
|
|
178
|
+
try {
|
|
179
|
+
resultContent = fs_1.default.readFileSync(resultPath, 'utf8');
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// shim crashed before writing — stderr already captured above
|
|
183
|
+
return {
|
|
184
|
+
result: {
|
|
185
|
+
status: 'failed',
|
|
186
|
+
error: { message: 'dev-shim did not write a result file (check stderr above)', name: 'Error' },
|
|
187
|
+
phase: 'run',
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (!resultContent.trim()) {
|
|
192
|
+
const msg = timedOut
|
|
193
|
+
? 'dev-shim timed out after 120s'
|
|
194
|
+
: 'dev-shim wrote an empty result file (check stderr above)';
|
|
195
|
+
return {
|
|
196
|
+
result: {
|
|
197
|
+
status: 'failed',
|
|
198
|
+
error: { message: msg, name: 'Error' },
|
|
199
|
+
phase: 'run',
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const shimResult = JSON.parse(resultContent);
|
|
204
|
+
return { result: shimResult.result };
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
// Clean up result temp file on all exit paths.
|
|
208
|
+
try {
|
|
209
|
+
fs_1.default.unlinkSync(resultPath);
|
|
210
|
+
}
|
|
211
|
+
catch { /* ignore */ }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
92
214
|
/**
|
|
93
215
|
* Run a workflow entry file in local-dev mode:
|
|
94
216
|
* 1. Fetch declared vars + connections from the platform (via api seam).
|
|
@@ -208,8 +330,33 @@ async function runDev(opts) {
|
|
|
208
330
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
331
|
const { createMockServer } = _require(sdkTestingMain);
|
|
210
332
|
const mockServer = await createMockServer();
|
|
333
|
+
// Pre-generate run IDs so they can be passed to the shim (for .ts entries)
|
|
334
|
+
// or used in-process (for .js/.mjs entries).
|
|
335
|
+
const runUuid = (0, crypto_1.randomUUID)();
|
|
336
|
+
const workerSessionId = (0, crypto_1.randomUUID)();
|
|
211
337
|
try {
|
|
212
|
-
// 7.
|
|
338
|
+
// 7. Load and invoke the workflow.
|
|
339
|
+
//
|
|
340
|
+
// For .ts entries: spawn `npx tsx dist/commands/dev-shim.mjs`. The .mjs shim
|
|
341
|
+
// is NOT compiled by tsc, so its import() stays a real ESM dynamic import.
|
|
342
|
+
// tsx's ESM loader remaps .js-extension imports to .ts files, fixing the
|
|
343
|
+
// Cannot-find-module bug on multi-file NodeNext projects.
|
|
344
|
+
//
|
|
345
|
+
// The shim creates the run-row (it has the descriptor name) and invokes.
|
|
346
|
+
// stdio: pipe — child stdout/stderr are captured into stdoutLines/stderrLines.
|
|
347
|
+
//
|
|
348
|
+
// For .js/.mjs entries: original in-process import path (no change).
|
|
349
|
+
const isTs = /\.(ts|tsx|mts|cts)$/.test(entryPath);
|
|
350
|
+
if (isTs) {
|
|
351
|
+
const shimInvokeResult = await runDevViaShim(entryPath, opts.input, vars, mockServer.baseUrl, runUuid, workerSessionId, stdoutLines, stderrLines);
|
|
352
|
+
return {
|
|
353
|
+
stdout: stdoutLines.join('\n'),
|
|
354
|
+
stderr: stderrLines.join('\n'),
|
|
355
|
+
result: shimInvokeResult.result,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
// JS/MJS path: in-process import (original behaviour, no change).
|
|
359
|
+
// 8. Import the workflow entry file. Prefer the module's default export
|
|
213
360
|
// (a WorkflowDescriptor returned by defineWorkflow) so that repeated
|
|
214
361
|
// runDev() calls in the same test process don't need to clear and
|
|
215
362
|
// re-register from a cached module (cached CJS modules don't re-run
|
|
@@ -236,8 +383,7 @@ async function runDev(opts) {
|
|
|
236
383
|
result: { status: 'failed', error: new Error('no workflow registered'), phase: 'run' },
|
|
237
384
|
};
|
|
238
385
|
}
|
|
239
|
-
//
|
|
240
|
-
const runUuid = (0, crypto_1.randomUUID)();
|
|
386
|
+
// 9. Build InvokeCtx.
|
|
241
387
|
const ctx = {
|
|
242
388
|
input: JSON.parse(opts.input || '{}'),
|
|
243
389
|
vars: Object.freeze(vars),
|
|
@@ -245,7 +391,7 @@ async function runDev(opts) {
|
|
|
245
391
|
triggerId: 'local-dev',
|
|
246
392
|
runUuid,
|
|
247
393
|
runSecret: 'local-dev',
|
|
248
|
-
workerSessionId
|
|
394
|
+
workerSessionId,
|
|
249
395
|
},
|
|
250
396
|
app: {
|
|
251
397
|
appVersion: '0',
|
|
@@ -258,7 +404,7 @@ async function runDev(opts) {
|
|
|
258
404
|
},
|
|
259
405
|
mode: 'local',
|
|
260
406
|
};
|
|
261
|
-
//
|
|
407
|
+
// 10. Create the run-row BEFORE invoking. invoke() itself never creates
|
|
262
408
|
// the durable run record — the production launcher (SolidActions.run
|
|
263
409
|
// → #initOneShotStatusRow) does that first, and step/sleep/recv
|
|
264
410
|
// sub-routes (`/runs/status/<id>/...`) 404 ("Workflow not found")
|
|
@@ -296,7 +442,7 @@ async function runDev(opts) {
|
|
|
296
442
|
// Best-effort, symmetric with #initOneShotStatusRow's swallow.
|
|
297
443
|
err(`failed to create local run-row: ${e?.message ?? e}`);
|
|
298
444
|
}
|
|
299
|
-
//
|
|
445
|
+
// 11. Invoke via internal SDK path (invoke() is not in the public index).
|
|
300
446
|
const invokePath = path_1.default.resolve(sdkMainForInvoke, '..', 'invoke', 'invoke.js');
|
|
301
447
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
302
448
|
const { invoke } = _require(invokePath);
|
|
@@ -320,13 +466,25 @@ const TSX_REEXEC_GUARD = 'SOLIDACTIONS_DEV_TSX_REEXEC';
|
|
|
320
466
|
* Detect whether the current Node process already has a TypeScript loader
|
|
321
467
|
* registered (tsx / ts-node). When true, `await import('<file>.ts')` works
|
|
322
468
|
* in-process and we can run `runDev` directly without re-exec.
|
|
469
|
+
*
|
|
470
|
+
* NOTE: Boolean(process._preload_modules) is intentionally NOT used here —
|
|
471
|
+
* an empty array is truthy, causing a false positive that always skips the
|
|
472
|
+
* re-exec path even when no TypeScript loader is active.
|
|
323
473
|
*/
|
|
324
474
|
function hasTsLoader() {
|
|
325
475
|
if (process.env[TSX_REEXEC_GUARD] === '1') {
|
|
326
476
|
return true;
|
|
327
477
|
}
|
|
328
478
|
const execArgv = process.execArgv.join(' ');
|
|
329
|
-
|
|
479
|
+
if (/tsx|ts-node/.test(execArgv)) {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
// Check _preload_modules for tsx/ts-node entries (non-empty array check).
|
|
483
|
+
const preload = process._preload_modules;
|
|
484
|
+
if (Array.isArray(preload) && preload.length > 0) {
|
|
485
|
+
return preload.some((m) => typeof m === 'string' && /tsx|ts-node/i.test(m));
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
330
488
|
}
|
|
331
489
|
/**
|
|
332
490
|
* Re-exec the CLI under `npx tsx` so that `runDev`'s `await import()` of a
|
package/dist/commands/env-set.js
CHANGED
|
@@ -3,11 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isNonTty = isNonTty;
|
|
6
7
|
exports.envSet = envSet;
|
|
7
8
|
const axios_1 = __importDefault(require("axios"));
|
|
8
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
10
|
const prompts_1 = __importDefault(require("prompts"));
|
|
10
11
|
const api_1 = require("../utils/api");
|
|
12
|
+
/** Returns true when stdin is not an interactive terminal (CI, pipes, scripts). */
|
|
13
|
+
function isNonTty() {
|
|
14
|
+
return !process.stdin.isTTY;
|
|
15
|
+
}
|
|
11
16
|
async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
12
17
|
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
13
18
|
// Detect mode based on arguments
|
|
@@ -31,6 +36,11 @@ async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
|
31
36
|
const mappings = mappingsResponse.data || [];
|
|
32
37
|
const existing = mappings.find((m) => m.env_name === key);
|
|
33
38
|
if (existing && existing.has_value) {
|
|
39
|
+
if (isNonTty()) {
|
|
40
|
+
console.error(chalk_1.default.red(`Variable "${key}" already has a value in "${projectName}" (${environment}). ` +
|
|
41
|
+
`Pass -y / --yes to overwrite without confirmation.`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
34
44
|
console.log(chalk_1.default.yellow(`Variable "${key}" already has a value in "${projectName}" (${environment}).`));
|
|
35
45
|
const confirm = await (0, prompts_1.default)({
|
|
36
46
|
type: 'confirm',
|
|
@@ -119,6 +129,11 @@ async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
|
119
129
|
let action;
|
|
120
130
|
if (existing) {
|
|
121
131
|
if (!options.yes) {
|
|
132
|
+
if (isNonTty()) {
|
|
133
|
+
console.error(chalk_1.default.red(`Global variable "${key}" already exists. ` +
|
|
134
|
+
`Pass -y / --yes to overwrite without confirmation.`));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
122
137
|
console.log(chalk_1.default.yellow(`Global variable "${key}" already exists.`));
|
|
123
138
|
const confirm = await (0, prompts_1.default)({
|
|
124
139
|
type: 'confirm',
|
package/dist/commands/init.js
CHANGED
|
@@ -69,9 +69,10 @@ async function init(directory, options = {}) {
|
|
|
69
69
|
if (directory) {
|
|
70
70
|
console.log(chalk_1.default.gray(` cd ${directory}`));
|
|
71
71
|
}
|
|
72
|
-
console.log(chalk_1.default.gray(` # Fill in WEBHOOK_SECRET and any other env vars in solidactions.yaml:`));
|
|
73
|
-
console.log(chalk_1.default.gray(` solidactions env set ${projectName} WEBHOOK_SECRET <your-secret> -e production`));
|
|
74
72
|
console.log(chalk_1.default.gray(` solidactions project deploy ${projectName} -e production`));
|
|
73
|
+
console.log(chalk_1.default.gray(` # If your workflow uses a webhook, retrieve its auto-generated secret`));
|
|
74
|
+
console.log(chalk_1.default.gray(` # and set that value in your sender (e.g. Telegram secret_token):`));
|
|
75
|
+
console.log(chalk_1.default.gray(` solidactions webhook secret ${projectName} -e production`));
|
|
75
76
|
}
|
|
76
77
|
catch (err) {
|
|
77
78
|
if (err.message?.includes('rate limit')) {
|
|
@@ -3,16 +3,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildBuildLogUrl = buildBuildLogUrl;
|
|
7
|
+
exports.buildBuildLogRequest = buildBuildLogRequest;
|
|
6
8
|
exports.logsBuild = logsBuild;
|
|
7
9
|
const axios_1 = __importDefault(require("axios"));
|
|
8
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
11
|
const api_1 = require("../utils/api");
|
|
10
|
-
|
|
12
|
+
function buildBuildLogUrl(host, projectName, environment) {
|
|
13
|
+
if (environment) {
|
|
14
|
+
return `${host}/api/v1/projects/resolve/build-log`;
|
|
15
|
+
}
|
|
16
|
+
return `${host}/api/v1/projects/${projectName}/build-log`;
|
|
17
|
+
}
|
|
18
|
+
function buildBuildLogRequest(host, projectName, environment) {
|
|
19
|
+
if (environment) {
|
|
20
|
+
return {
|
|
21
|
+
url: `${host}/api/v1/projects/resolve/build-log`,
|
|
22
|
+
params: { name: projectName, environment },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return { url: `${host}/api/v1/projects/${projectName}/build-log` };
|
|
26
|
+
}
|
|
27
|
+
async function logsBuild(projectName, environment) {
|
|
11
28
|
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
12
29
|
console.log(chalk_1.default.blue(`Fetching build logs for project "${projectName}"...`));
|
|
30
|
+
const { url, params } = buildBuildLogRequest(config.host, projectName, environment);
|
|
13
31
|
try {
|
|
14
|
-
const response = await axios_1.default.get(
|
|
32
|
+
const response = await axios_1.default.get(url, {
|
|
15
33
|
headers: (0, api_1.getApiHeaders)(config),
|
|
34
|
+
params,
|
|
16
35
|
});
|
|
17
36
|
const buildLog = response.data.build_log || response.data;
|
|
18
37
|
if (!buildLog || buildLog.length === 0) {
|
|
@@ -29,7 +48,11 @@ async function logsBuild(projectName) {
|
|
|
29
48
|
console.error(chalk_1.default.red('Authentication failed. Run "solidactions login <api-key>" to re-configure.'));
|
|
30
49
|
}
|
|
31
50
|
else if (error.response.status === 404) {
|
|
32
|
-
console.error(chalk_1.default.red(`Project "${projectName}" not found.`));
|
|
51
|
+
console.error(chalk_1.default.red(error.response.data?.message ?? `Project "${projectName}" not found.`));
|
|
52
|
+
const envs = error.response.data?.available_environments;
|
|
53
|
+
if (envs && envs.length > 0) {
|
|
54
|
+
console.error(chalk_1.default.yellow(`Available environments: ${envs.join(', ')}. Pass -e <env> to select one.`));
|
|
55
|
+
}
|
|
33
56
|
}
|
|
34
57
|
else {
|
|
35
58
|
console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
|
|
@@ -3,38 +3,54 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildRunListParams = buildRunListParams;
|
|
6
7
|
exports.runs = runs;
|
|
7
8
|
exports.summaryStatusLabel = summaryStatusLabel;
|
|
8
9
|
exports.detailedOutcomeTag = detailedOutcomeTag;
|
|
9
10
|
const axios_1 = __importDefault(require("axios"));
|
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
12
|
const api_1 = require("../utils/api");
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Build the params object for GET /api/v1/runs.
|
|
15
|
+
* Extracted as a pure function so it can be unit-tested without mocking axios.
|
|
16
|
+
*/
|
|
17
|
+
function buildRunListParams(projectName, options = {}) {
|
|
15
18
|
const defaultLimit = options.detailed ? 5 : 20;
|
|
16
19
|
const limit = options.limit || defaultLimit;
|
|
20
|
+
const params = { limit };
|
|
21
|
+
if (projectName)
|
|
22
|
+
params.project = projectName;
|
|
23
|
+
if (options.environment)
|
|
24
|
+
params.environment = options.environment;
|
|
25
|
+
if (options.offset)
|
|
26
|
+
params.offset = options.offset;
|
|
27
|
+
if (options.status)
|
|
28
|
+
params.status = options.status;
|
|
29
|
+
if (options.since)
|
|
30
|
+
params.since = parseSince(options.since);
|
|
31
|
+
if (options.workflow)
|
|
32
|
+
params.workflow = options.workflow;
|
|
33
|
+
if (options.detailed)
|
|
34
|
+
params.detailed = '1';
|
|
35
|
+
if (options.hasErrors)
|
|
36
|
+
params.has_errors = '1';
|
|
37
|
+
return params;
|
|
38
|
+
}
|
|
39
|
+
async function runs(projectName, options = {}) {
|
|
40
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
17
41
|
try {
|
|
18
|
-
const params =
|
|
19
|
-
if (projectName)
|
|
20
|
-
params.project = projectName;
|
|
21
|
-
if (options.offset)
|
|
22
|
-
params.offset = options.offset;
|
|
23
|
-
if (options.status)
|
|
24
|
-
params.status = options.status;
|
|
25
|
-
if (options.since)
|
|
26
|
-
params.since = parseSince(options.since);
|
|
27
|
-
if (options.workflow)
|
|
28
|
-
params.workflow = options.workflow;
|
|
29
|
-
if (options.detailed)
|
|
30
|
-
params.detailed = '1';
|
|
31
|
-
if (options.hasErrors)
|
|
32
|
-
params.has_errors = '1';
|
|
42
|
+
const params = buildRunListParams(projectName, options);
|
|
33
43
|
const response = await axios_1.default.get(`${config.host}/api/v1/runs`, {
|
|
34
44
|
headers: (0, api_1.getApiHeaders)(config),
|
|
35
45
|
params,
|
|
36
46
|
});
|
|
37
47
|
const runsList = response.data.data || response.data;
|
|
48
|
+
// Check server-side error fields FIRST (project_not_found comes back as 200 with data:[])
|
|
49
|
+
const serverError = response.data.error;
|
|
50
|
+
if (serverError === 'project_not_found') {
|
|
51
|
+
console.error(chalk_1.default.red(response.data.message || `Project '${projectName}' not found.`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
38
54
|
if (!runsList || runsList.length === 0) {
|
|
39
55
|
if (options.json) {
|
|
40
56
|
console.log('[]');
|
|
@@ -58,7 +74,13 @@ async function runs(projectName, options = {}) {
|
|
|
58
74
|
}
|
|
59
75
|
catch (error) {
|
|
60
76
|
if (error.response) {
|
|
61
|
-
|
|
77
|
+
// ambiguous_project is 422 — axios throws, so handle here
|
|
78
|
+
if (error.response.status === 422 && error.response.data?.error === 'ambiguous_project') {
|
|
79
|
+
const envs = (error.response.data.environments ?? []).join(', ');
|
|
80
|
+
console.error(chalk_1.default.yellow(`Ambiguous project '${projectName}': found in environments: ${envs}.`));
|
|
81
|
+
console.error(chalk_1.default.yellow(`Pass -e <env> to disambiguate, e.g.: solidactions run list ${projectName} -e dev`));
|
|
82
|
+
}
|
|
83
|
+
else if (error.response.status === 401) {
|
|
62
84
|
console.error(chalk_1.default.red('Authentication failed. Run "solidactions login <api-key>" to re-configure.'));
|
|
63
85
|
}
|
|
64
86
|
else {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatSecretOutput = formatSecretOutput;
|
|
7
|
+
exports.webhookSecret = webhookSecret;
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const api_1 = require("../utils/api");
|
|
11
|
+
function formatSecretOutput(rows, workflowFilter) {
|
|
12
|
+
if (workflowFilter) {
|
|
13
|
+
const match = rows.find((r) => r.workflow_name === workflowFilter);
|
|
14
|
+
if (!match)
|
|
15
|
+
return `No webhook named "${workflowFilter}" found.`;
|
|
16
|
+
return match.webhook_secret ?? '(secret not returned — check your access level)';
|
|
17
|
+
}
|
|
18
|
+
if (rows.length === 1) {
|
|
19
|
+
return rows[0].webhook_secret ?? '(secret not returned — check your access level)';
|
|
20
|
+
}
|
|
21
|
+
const lines = rows.map((r) => `${(r.workflow_name ?? '?').padEnd(32)}${r.webhook_secret ?? '(none)'}`);
|
|
22
|
+
return ['WORKFLOW'.padEnd(32) + 'SECRET', '-'.repeat(80), ...lines].join('\n');
|
|
23
|
+
}
|
|
24
|
+
async function webhookSecret(projectName, options = {}) {
|
|
25
|
+
const format = options.format ?? 'text';
|
|
26
|
+
if (format !== 'text' && format !== 'json') {
|
|
27
|
+
console.error(chalk_1.default.red(`Invalid --format: ${format}. Expected 'text' or 'json'.`));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
31
|
+
const environment = options.env || 'dev';
|
|
32
|
+
const projectSlug = environment === 'production' ? projectName : `${projectName}-${environment}`;
|
|
33
|
+
if (format === 'text') {
|
|
34
|
+
process.stderr.write(chalk_1.default.dim(`(environment: ${environment} — pass -e <env> to change)\n`));
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const response = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}/webhooks`, {
|
|
38
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
39
|
+
params: { show_secrets: 'true' },
|
|
40
|
+
});
|
|
41
|
+
const rows = response.data.data ?? [];
|
|
42
|
+
if (rows.length === 0) {
|
|
43
|
+
console.error(chalk_1.default.yellow(`No webhook workflows found for project "${projectName}"${environment !== 'production' ? ` (${environment})` : ''}.`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
if (format === 'json') {
|
|
47
|
+
const out = options.workflow
|
|
48
|
+
? rows
|
|
49
|
+
.filter((r) => r.workflow_name === options.workflow)
|
|
50
|
+
.map((r) => ({ workflow: r.workflow_name, secret: r.webhook_secret }))
|
|
51
|
+
: rows.map((r) => ({ workflow: r.workflow_name, secret: r.webhook_secret }));
|
|
52
|
+
console.log(JSON.stringify(out, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.log(formatSecretOutput(rows, options.workflow));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.response) {
|
|
59
|
+
if (error.response.status === 401) {
|
|
60
|
+
console.error(chalk_1.default.red('Authentication failed. Run "solidactions login <api-key>" to re-configure.'));
|
|
61
|
+
}
|
|
62
|
+
else if (error.response.status === 404) {
|
|
63
|
+
console.error(chalk_1.default.red(`Project "${projectName}" not found.`));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.error(chalk_1.default.red('Connection failed:'), error.message);
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ const schedule_set_1 = require("./commands/schedule-set");
|
|
|
26
26
|
const schedule_list_1 = require("./commands/schedule-list");
|
|
27
27
|
const schedule_delete_1 = require("./commands/schedule-delete");
|
|
28
28
|
const webhook_list_1 = require("./commands/webhook-list");
|
|
29
|
+
const webhook_secret_1 = require("./commands/webhook-secret");
|
|
29
30
|
const dev_1 = require("./commands/dev");
|
|
30
31
|
const ai_init_1 = require("./commands/ai-init");
|
|
31
32
|
const ai_examples_1 = require("./commands/ai-examples");
|
|
@@ -158,8 +159,13 @@ project
|
|
|
158
159
|
.option('-e, --env <environment>', 'Target environment (production/staging/dev). Required on first deploy of a new project.')
|
|
159
160
|
.option('--create', 'Create environment project if it doesn\'t exist')
|
|
160
161
|
.option('--config-only', 'Sync YAML env declarations without building/deploying')
|
|
162
|
+
.option('--no-cache', 'Force a fresh build, bypassing all build caches')
|
|
163
|
+
.option('--force-rebuild', 'Force a fresh build, bypassing all build caches (alias for --no-cache)')
|
|
161
164
|
.action((projectName, path, options) => {
|
|
162
|
-
|
|
165
|
+
// Commander's negation convention maps --no-cache to options.cache === false
|
|
166
|
+
// (NOT options.noCache). Normalize both flags to a single boolean.
|
|
167
|
+
const noCache = options.cache === false || options.forceRebuild === true;
|
|
168
|
+
(0, deploy_1.deploy)(projectName, path, { ...options, noCache });
|
|
163
169
|
});
|
|
164
170
|
project
|
|
165
171
|
.command('pull')
|
|
@@ -173,9 +179,10 @@ project
|
|
|
173
179
|
project
|
|
174
180
|
.command('logs')
|
|
175
181
|
.description('View build/deployment logs for a project')
|
|
176
|
-
.argument('<project>', 'Project name')
|
|
177
|
-
.
|
|
178
|
-
(
|
|
182
|
+
.argument('<project>', 'Project name (or family name with -e)')
|
|
183
|
+
.option('-e, --environment <environment>', 'Environment to resolve (production/staging/dev)')
|
|
184
|
+
.action((projectName, options) => {
|
|
185
|
+
(0, project_logs_1.logsBuild)(projectName, options.environment);
|
|
179
186
|
});
|
|
180
187
|
project
|
|
181
188
|
.command('list')
|
|
@@ -210,6 +217,7 @@ runCmd
|
|
|
210
217
|
.option('--detailed', 'Include timeline, steps, and logs per run (default limit: 5)')
|
|
211
218
|
.option('--has-errors', 'Show only runs with errors (step errors, retries, or degraded results)')
|
|
212
219
|
.option('--json', 'Output as JSON')
|
|
220
|
+
.option('-e, --environment <environment>', 'Environment to filter by (production/staging/dev)')
|
|
213
221
|
.action((projectName, options) => {
|
|
214
222
|
(0, run_list_1.runs)(projectName, options);
|
|
215
223
|
});
|
|
@@ -340,6 +348,16 @@ webhook
|
|
|
340
348
|
.action((projectName, options) => {
|
|
341
349
|
(0, webhook_list_1.webhookList)(projectName, options);
|
|
342
350
|
});
|
|
351
|
+
webhook
|
|
352
|
+
.command('secret')
|
|
353
|
+
.description('Print the webhook secret for a project (set this value in your sender)')
|
|
354
|
+
.argument('<project>', 'Project name')
|
|
355
|
+
.option('-e, --env <environment>', 'Environment (production/staging/dev)')
|
|
356
|
+
.option('--workflow <name>', 'Filter to a specific workflow by name')
|
|
357
|
+
.option('--format <format>', 'Output format: text or json', 'text')
|
|
358
|
+
.action((projectName, options) => {
|
|
359
|
+
(0, webhook_secret_1.webhookSecret)(projectName, options);
|
|
360
|
+
});
|
|
343
361
|
// =============================================================================
|
|
344
362
|
// workspace <subcommand>
|
|
345
363
|
// =============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidactions/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "SolidActions CLI - Deploy and manage workflow automation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"deploy"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"build": "tsc",
|
|
35
|
+
"build": "tsc && cp src/commands/dev-shim.mjs dist/commands/dev-shim.mjs",
|
|
36
36
|
"prepublishOnly": "npm run build",
|
|
37
37
|
"test": "vitest run",
|
|
38
38
|
"test:watch": "vitest"
|