@mondaydotcomorg/z2h-cli 0.33.0 → 0.33.2
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/backend/__tests__/format-sandbox-run.test.d.ts +2 -0
- package/dist/backend/__tests__/format-sandbox-run.test.d.ts.map +1 -0
- package/dist/backend/client.d.ts +32 -2
- package/dist/backend/client.d.ts.map +1 -1
- package/dist/backend/client.js +8 -5
- package/dist/backend/format-sandbox-run.d.ts +4 -0
- package/dist/backend/format-sandbox-run.d.ts.map +1 -0
- package/dist/backend/format-sandbox-run.js +49 -0
- package/dist/commands/__tests__/backend-invoke.test.d.ts +2 -0
- package/dist/commands/__tests__/backend-invoke.test.d.ts.map +1 -0
- package/dist/commands/backend.d.ts +12 -6
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +41 -15
- package/dist/commands/deploy.js +29 -16
- package/dist/esm/backend/__tests__/format-sandbox-run.test.d.ts +2 -0
- package/dist/esm/backend/__tests__/format-sandbox-run.test.d.ts.map +1 -0
- package/dist/esm/backend/client.d.ts +32 -2
- package/dist/esm/backend/client.d.ts.map +1 -1
- package/dist/esm/backend/client.mjs +8 -5
- package/dist/esm/backend/format-sandbox-run.d.ts +4 -0
- package/dist/esm/backend/format-sandbox-run.d.ts.map +1 -0
- package/dist/esm/backend/format-sandbox-run.mjs +47 -0
- package/dist/esm/commands/__tests__/backend-invoke.test.d.ts +2 -0
- package/dist/esm/commands/__tests__/backend-invoke.test.d.ts.map +1 -0
- package/dist/esm/commands/backend.d.ts +12 -6
- package/dist/esm/commands/backend.d.ts.map +1 -1
- package/dist/esm/commands/backend.mjs +42 -16
- package/dist/esm/commands/deploy.mjs +29 -16
- package/dist/esm/index.mjs +9 -4
- package/dist/esm/util/broker/app.d.ts +2 -0
- package/dist/esm/util/broker/app.d.ts.map +1 -1
- package/dist/esm/util/broker/app.mjs +20 -7
- package/dist/index.js +9 -4
- package/dist/util/broker/app.d.ts +2 -0
- package/dist/util/broker/app.d.ts.map +1 -1
- package/dist/util/broker/app.js +20 -7
- package/handler-api/backend-runner/backend-runner.errors.ts +12 -0
- package/handler-api/backend-runner/backend-runner.types.ts +3 -0
- package/handler-api/backend-runner/channels/channel-map.ts +12 -3
- package/handler-api/backend-runner/channels/channel-telemetry.ts +13 -7
- package/handler-api/backend-runner/channels/channel-trace.ts +52 -0
- package/package.json +2 -2
- package/src/backend/__tests__/client.test.ts +17 -13
- package/src/backend/__tests__/format-sandbox-run.test.ts +42 -0
- package/src/backend/client.ts +27 -10
- package/src/backend/format-sandbox-run.ts +51 -0
- package/src/commands/__tests__/backend-deploy.test.ts +42 -21
- package/src/commands/__tests__/backend-invoke.test.ts +125 -0
- package/src/commands/__tests__/deploy.test.ts +53 -0
- package/src/commands/backend.ts +57 -18
- package/src/commands/deploy.ts +31 -17
- package/src/index.ts +13 -5
- package/src/util/broker/app.ts +59 -50
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-sandbox-run.test.d.ts","sourceRoot":"","sources":["../../../src/backend/__tests__/format-sandbox-run.test.ts"],"names":[],"mappings":""}
|
package/dist/backend/client.d.ts
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
/** `Z2H_BACKEND_URL` overrides for staging or a locally-run runner. */
|
|
2
2
|
export declare function backendBaseUrl(): string;
|
|
3
|
-
export interface
|
|
3
|
+
export interface SandboxRunRequest {
|
|
4
|
+
code: string;
|
|
5
|
+
input: unknown;
|
|
6
|
+
handlerName?: string;
|
|
7
|
+
}
|
|
8
|
+
/** One channel call the handler made, as the runner traced it. */
|
|
9
|
+
export interface SandboxCall {
|
|
10
|
+
method: string;
|
|
11
|
+
args: unknown[];
|
|
12
|
+
durationMs: number;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
error?: {
|
|
15
|
+
status: number;
|
|
16
|
+
code?: string;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
result?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface SandboxRunResponse {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
error?: {
|
|
24
|
+
status: number;
|
|
25
|
+
code?: string;
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
calls: SandboxCall[];
|
|
4
29
|
result: unknown;
|
|
5
30
|
durationMs: number;
|
|
6
31
|
}
|
|
7
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Runs handler source from the working tree on the runner's sandbox route —
|
|
34
|
+
* against the app's real channels, without deploying it. A handler failure
|
|
35
|
+
* comes back as `ok: false` with the trace; only a refused request throws.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runSandbox(appName: string, body: SandboxRunRequest): Promise<SandboxRunResponse>;
|
|
8
38
|
export interface AppIntegrationsResponse {
|
|
9
39
|
integrations: string[];
|
|
10
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/backend/client.ts"],"names":[],"mappings":"AAWA,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AA+CD,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/backend/client.ts"],"names":[],"mappings":"AAWA,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AA+CD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAEhG;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAErF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrF"}
|
package/dist/backend/client.js
CHANGED
|
@@ -56,10 +56,13 @@ async function request(method, baseUrl, path, body) {
|
|
|
56
56
|
}
|
|
57
57
|
return (parsed ?? {});
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Runs handler source from the working tree on the runner's sandbox route —
|
|
61
|
+
* against the app's real channels, without deploying it. A handler failure
|
|
62
|
+
* comes back as `ok: false` with the trace; only a refused request throws.
|
|
63
|
+
*/
|
|
64
|
+
function runSandbox(appName, body) {
|
|
65
|
+
return request('POST', backendBaseUrl(), `/z2h-apps/${encodeURIComponent(appName)}/sandbox/run`, body);
|
|
63
66
|
}
|
|
64
67
|
function listAppIntegrations(appName) {
|
|
65
68
|
return request('GET', constants.brokerBaseUrl(), `/z2h-apps/${encodeURIComponent(appName)}/integrations`);
|
|
@@ -73,6 +76,6 @@ function removeIntegration(appName, integration) {
|
|
|
73
76
|
|
|
74
77
|
exports.backendBaseUrl = backendBaseUrl;
|
|
75
78
|
exports.declareIntegration = declareIntegration;
|
|
76
|
-
exports.invokeBackend = invokeBackend;
|
|
77
79
|
exports.listAppIntegrations = listAppIntegrations;
|
|
78
80
|
exports.removeIntegration = removeIntegration;
|
|
81
|
+
exports.runSandbox = runSandbox;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-sandbox-run.d.ts","sourceRoot":"","sources":["../../src/backend/format-sandbox-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAmChE,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAchE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const MAX_SQL_CHARS = 400;
|
|
4
|
+
const seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
|
|
5
|
+
const oneLine = (text) => text.replace(/\s+/g, ' ').trim();
|
|
6
|
+
const clip = (text, max) => (text.length > max ? `${text.slice(0, max)}…` : text);
|
|
7
|
+
/** The first arg is the SQL / GraphQL text for query channels — shown on its own line so it can be read. */
|
|
8
|
+
function describeArgs(args) {
|
|
9
|
+
const [first, ...rest] = args;
|
|
10
|
+
const lines = [];
|
|
11
|
+
if (typeof first === 'string') {
|
|
12
|
+
lines.push(clip(oneLine(first), MAX_SQL_CHARS));
|
|
13
|
+
}
|
|
14
|
+
else if (first !== undefined) {
|
|
15
|
+
lines.push(clip(JSON.stringify(first), MAX_SQL_CHARS));
|
|
16
|
+
}
|
|
17
|
+
const params = rest.filter(arg => arg !== undefined && arg !== null);
|
|
18
|
+
if (params.length > 0) {
|
|
19
|
+
lines.push(`params: ${clip(params.map(p => JSON.stringify(p)).join(', '), MAX_SQL_CHARS)}`);
|
|
20
|
+
}
|
|
21
|
+
return lines;
|
|
22
|
+
}
|
|
23
|
+
function describeCall(call) {
|
|
24
|
+
const rows = Array.isArray(call.result) ? ` ${call.result.length} rows` : '';
|
|
25
|
+
const head = ` ${call.ok ? '✓' : '✗'} ${call.method} ${Math.round(call.durationMs)}ms${rows}`;
|
|
26
|
+
const detail = describeArgs(call.args);
|
|
27
|
+
if (call.error) {
|
|
28
|
+
const code = call.error.code ? ` ${call.error.code}` : '';
|
|
29
|
+
detail.push(`${call.error.status}${code}: ${call.error.message}`);
|
|
30
|
+
}
|
|
31
|
+
return [head, ...detail.map(line => ` ${line}`)];
|
|
32
|
+
}
|
|
33
|
+
/** Human-readable trace of a sandbox run; `--json` prints the raw response instead. */
|
|
34
|
+
function formatSandboxRun(res) {
|
|
35
|
+
const lines = [`[z2h-cli] sandbox run ${res.ok ? '✓' : '✗'} ${seconds(res.durationMs)}`, ''];
|
|
36
|
+
lines.push(res.calls.length > 0 ? 'calls:' : 'calls: none');
|
|
37
|
+
res.calls.forEach(call => lines.push(...describeCall(call)));
|
|
38
|
+
lines.push('');
|
|
39
|
+
if (res.ok) {
|
|
40
|
+
lines.push('result:', JSON.stringify(res.result, null, 2));
|
|
41
|
+
}
|
|
42
|
+
else if (res.error) {
|
|
43
|
+
const code = res.error.code ? ` ${res.error.code}` : '';
|
|
44
|
+
lines.push(`error: ${res.error.status}${code} — ${res.error.message}`);
|
|
45
|
+
}
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exports.formatSandboxRun = formatSandboxRun;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend-invoke.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/backend-invoke.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export declare function hasHandlers(appDir: string): boolean;
|
|
2
|
+
export interface ReadDetectAndUploadResult {
|
|
3
|
+
integrations: string[];
|
|
4
|
+
handlerNames: string[];
|
|
5
|
+
apiUsages: Record<string, unknown>;
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
|
-
* Deploy the app's backend handlers to S3 and return the integrations detected
|
|
4
|
-
* from their sources. Each `.js` handler is uploaded as its own flat file — no
|
|
5
|
-
* compilation, no bundling, no npm deps.
|
|
6
|
-
*
|
|
7
8
|
* Integration sync note: the returned set is re-derived from the CURRENT backend
|
|
8
9
|
* sources on every deploy, and the platform set is reconciled to exactly match
|
|
9
10
|
* it (there is no stored declaration to fall back on). A deploy therefore always
|
|
@@ -17,13 +18,18 @@ export declare function deployBackend(opts: {
|
|
|
17
18
|
version: number;
|
|
18
19
|
deployedBy: string;
|
|
19
20
|
isPreview: boolean;
|
|
20
|
-
}): Promise<
|
|
21
|
+
}): Promise<ReadDetectAndUploadResult>;
|
|
21
22
|
/**
|
|
22
23
|
* Runs the same checks deploy does against the working tree, refreshing the
|
|
23
24
|
* local `usage.json` — no upload to S3. Throws on the first invalid handler.
|
|
24
25
|
*/
|
|
25
26
|
export declare function backendValidateCommand(appDir?: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Runs a handler from the working tree (or `--code`) on the runner's sandbox and
|
|
29
|
+
* prints the trace. Validated locally first, like deploy; exits 1 when the handler fails.
|
|
30
|
+
*/
|
|
26
31
|
export declare function backendInvokeCommand(handlerName: string, opts?: {
|
|
27
32
|
input?: string;
|
|
28
|
-
|
|
33
|
+
code?: string;
|
|
34
|
+
}, appDir?: string): Promise<void>;
|
|
29
35
|
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/commands/backend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/commands/backend.ts"],"names":[],"mappings":"AAyBA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAoBD,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAiCD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CASrC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAe1F;AAsBD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,EAC5C,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
package/dist/commands/backend.js
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const fs = require('node:fs');
|
|
5
|
+
const promises = require('node:fs/promises');
|
|
5
6
|
const fs$1 = require('fs-extra');
|
|
6
7
|
const clientS3 = require('@aws-sdk/client-s3');
|
|
7
8
|
const util_logger = require('../util/logger.js');
|
|
@@ -12,6 +13,8 @@ const backend_detectIntegrations = require('../backend/detect-integrations.js');
|
|
|
12
13
|
const util_s3_client = require('../util/s3/client.js');
|
|
13
14
|
const constants = require('../constants.js');
|
|
14
15
|
const backend_client = require('../backend/client.js');
|
|
16
|
+
const backend_formatSandboxRun = require('../backend/format-sandbox-run.js');
|
|
17
|
+
const backend_validate = require('../backend/validate.js');
|
|
15
18
|
const util_errors = require('../util/errors.js');
|
|
16
19
|
|
|
17
20
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
@@ -45,21 +48,19 @@ async function writeUsageJson(appDir, patch) {
|
|
|
45
48
|
const existing = await readExistingUsage(appDir);
|
|
46
49
|
await fs$1.writeJson(path__default.default.join(appDir, 'usage.json'), { ...existing, ...patch }, { spaces: 2 });
|
|
47
50
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Returns `[]` when there is no `backend/handlers/` dir (or it is empty of sources)
|
|
50
|
-
* so callers can always pass the result to `registerOrUpdateApp` without a separate gate.
|
|
51
|
-
*/
|
|
52
51
|
async function readDetectAndUpload(appDir, prefix, appName, version, deployedBy, logLabel) {
|
|
53
52
|
if (!hasHandlers(appDir)) {
|
|
54
|
-
|
|
53
|
+
await writeUsageJson(appDir, { appName, deployedBy, apiUsages: {} });
|
|
54
|
+
return { integrations: [], handlerNames: [], apiUsages: {} };
|
|
55
55
|
}
|
|
56
56
|
util_logger.info(`reading backend handlers ${logLabel}`);
|
|
57
57
|
const { handlers, apiDomains, apiUsages } = await backend_handlers.readHandlers(appDir);
|
|
58
58
|
const integrations = backend_detectIntegrations.detectIntegrations(apiDomains);
|
|
59
|
+
const handlerNames = Object.keys(handlers);
|
|
60
|
+
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
59
61
|
if (Object.keys(handlers).length === 0) {
|
|
60
|
-
return integrations;
|
|
62
|
+
return { integrations, handlerNames, apiUsages };
|
|
61
63
|
}
|
|
62
|
-
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
63
64
|
const s3 = util_s3_client.getS3Client();
|
|
64
65
|
const meta = JSON.stringify({ version, deployedBy, handlers: Object.keys(handlers) });
|
|
65
66
|
const put = (key, body, contentType) => s3.send(new clientS3.PutObjectCommand({ Bucket: constants.BUCKET_WRITE_NAME, Key: key, Body: body, ContentType: contentType }));
|
|
@@ -67,13 +68,9 @@ async function readDetectAndUpload(appDir, prefix, appName, version, deployedBy,
|
|
|
67
68
|
...Object.entries(handlers).map(([name, code]) => put(`${prefix}/${name}.js`, code, 'application/javascript')),
|
|
68
69
|
put(`${prefix}/meta.json`, meta, 'application/json'),
|
|
69
70
|
]);
|
|
70
|
-
return integrations;
|
|
71
|
+
return { integrations, handlerNames, apiUsages };
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
73
|
-
* Deploy the app's backend handlers to S3 and return the integrations detected
|
|
74
|
-
* from their sources. Each `.js` handler is uploaded as its own flat file — no
|
|
75
|
-
* compilation, no bundling, no npm deps.
|
|
76
|
-
*
|
|
77
74
|
* Integration sync note: the returned set is re-derived from the CURRENT backend
|
|
78
75
|
* sources on every deploy, and the platform set is reconciled to exactly match
|
|
79
76
|
* it (there is no stored declaration to fall back on). A deploy therefore always
|
|
@@ -111,7 +108,29 @@ async function backendValidateCommand(appDir = process.cwd()) {
|
|
|
111
108
|
integrations,
|
|
112
109
|
});
|
|
113
110
|
}
|
|
114
|
-
async function
|
|
111
|
+
async function readHandlerSource(appDir, handlerName) {
|
|
112
|
+
const dir = path__default.default.join(appDir, 'backend', 'handlers');
|
|
113
|
+
try {
|
|
114
|
+
return await promises.readFile(path__default.default.join(dir, `${handlerName}.js`), 'utf8');
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err.code !== 'ENOENT') {
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
const files = hasHandlers(appDir) ? await promises.readdir(dir) : [];
|
|
121
|
+
const available = files
|
|
122
|
+
.filter(f => f.endsWith('.js'))
|
|
123
|
+
.map(f => path__default.default.parse(f).name)
|
|
124
|
+
.sort();
|
|
125
|
+
throw new Error(`no handler "${handlerName}" in backend/handlers/` +
|
|
126
|
+
(available.length ? ` (available: ${available.join(', ')})` : ' (no handlers found)'));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Runs a handler from the working tree (or `--code`) on the runner's sandbox and
|
|
131
|
+
* prints the trace. Validated locally first, like deploy; exits 1 when the handler fails.
|
|
132
|
+
*/
|
|
133
|
+
async function backendInvokeCommand(handlerName, opts = {}, appDir = process.cwd()) {
|
|
115
134
|
const appName = currentAppName();
|
|
116
135
|
let input = null;
|
|
117
136
|
if (opts.input) {
|
|
@@ -122,8 +141,15 @@ async function backendInvokeCommand(handlerName, opts = {}) {
|
|
|
122
141
|
throw new Error(`--input must be valid JSON (got: ${opts.input})`);
|
|
123
142
|
}
|
|
124
143
|
}
|
|
125
|
-
const
|
|
126
|
-
|
|
144
|
+
const file = `${handlerName}.js`;
|
|
145
|
+
const code = opts.code ?? (await readHandlerSource(appDir, handlerName));
|
|
146
|
+
backend_validate.validateHandlerSource(file, code);
|
|
147
|
+
util_logger.info(`validated ${opts.code ? '--code' : file}`);
|
|
148
|
+
const res = await backend_client.runSandbox(appName, { code, input, handlerName });
|
|
149
|
+
util_logger.output(backend_formatSandboxRun.formatSandboxRun(res), res);
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
process.exitCode = 1;
|
|
152
|
+
}
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
exports.backendInvokeCommand = backendInvokeCommand;
|
package/dist/commands/deploy.js
CHANGED
|
@@ -85,12 +85,9 @@ async function buildAndStamp(opts) {
|
|
|
85
85
|
*/
|
|
86
86
|
async function uploadAndRegister(opts) {
|
|
87
87
|
const { paths, keyPrefix, appName, token, nextVersion, entry, gitRemote, isPreview, description, masterAppName } = opts;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// reconciled to exactly match on register — see `deployBackend` for why this
|
|
92
|
-
// depends on deploying from the app's latest merged state.
|
|
93
|
-
const integrations = await commands_backend.deployBackend({
|
|
88
|
+
// Runs before the frontend upload so a `deployBackend` failure can't leave a
|
|
89
|
+
// preview's unversioned `MF` dir overwritten with no rollback.
|
|
90
|
+
const { integrations, handlerNames, apiUsages } = await commands_backend.deployBackend({
|
|
94
91
|
appDir: process.cwd(),
|
|
95
92
|
appName,
|
|
96
93
|
mfKeyPrefix: keyPrefix,
|
|
@@ -98,16 +95,32 @@ async function uploadAndRegister(opts) {
|
|
|
98
95
|
deployedBy: util_machineIdentity.whoAmI(),
|
|
99
96
|
isPreview,
|
|
100
97
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
try {
|
|
99
|
+
util_logger.info(`uploading ${isPreview ? 'preview ' : ''}build → s3://${constants.BUCKET_WRITE_NAME}/${keyPrefix}/`);
|
|
100
|
+
await util_s3_upload.uploadDir(paths.shadowBuild, keyPrefix);
|
|
101
|
+
await util_broker_app.registerOrUpdateApp({
|
|
102
|
+
appName,
|
|
103
|
+
token,
|
|
104
|
+
nextVersion,
|
|
105
|
+
entry,
|
|
106
|
+
gitRemote,
|
|
107
|
+
integrations,
|
|
108
|
+
handlerNames,
|
|
109
|
+
apiUsages,
|
|
110
|
+
description,
|
|
111
|
+
masterAppName,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
// A live deploy is safe either way — RDS's version gates what's actually served, so
|
|
116
|
+
// an orphaned S3 object is harmless — but a preview is served straight from its
|
|
117
|
+
// unversioned prefix with no such gate, so only it needs the warning below.
|
|
118
|
+
if (isPreview) {
|
|
119
|
+
util_logger.warn(`preview backend was already deployed before this failed — re-run "z2h-cli deploy --preview" to retry ` +
|
|
120
|
+
`(every step overwrites, so a retry fully repairs it)`);
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
111
124
|
}
|
|
112
125
|
async function deployPreview({ paths, consumerName, appName, token, opts }) {
|
|
113
126
|
// A preview inherits from the live app it previews, resolved by name server-side —
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-sandbox-run.test.d.ts","sourceRoot":"","sources":["../../../../src/backend/__tests__/format-sandbox-run.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
/** `Z2H_BACKEND_URL` overrides for staging or a locally-run runner. */
|
|
2
2
|
export declare function backendBaseUrl(): string;
|
|
3
|
-
export interface
|
|
3
|
+
export interface SandboxRunRequest {
|
|
4
|
+
code: string;
|
|
5
|
+
input: unknown;
|
|
6
|
+
handlerName?: string;
|
|
7
|
+
}
|
|
8
|
+
/** One channel call the handler made, as the runner traced it. */
|
|
9
|
+
export interface SandboxCall {
|
|
10
|
+
method: string;
|
|
11
|
+
args: unknown[];
|
|
12
|
+
durationMs: number;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
error?: {
|
|
15
|
+
status: number;
|
|
16
|
+
code?: string;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
result?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface SandboxRunResponse {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
error?: {
|
|
24
|
+
status: number;
|
|
25
|
+
code?: string;
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
calls: SandboxCall[];
|
|
4
29
|
result: unknown;
|
|
5
30
|
durationMs: number;
|
|
6
31
|
}
|
|
7
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Runs handler source from the working tree on the runner's sandbox route —
|
|
34
|
+
* against the app's real channels, without deploying it. A handler failure
|
|
35
|
+
* comes back as `ok: false` with the trace; only a refused request throws.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runSandbox(appName: string, body: SandboxRunRequest): Promise<SandboxRunResponse>;
|
|
8
38
|
export interface AppIntegrationsResponse {
|
|
9
39
|
integrations: string[];
|
|
10
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/backend/client.ts"],"names":[],"mappings":"AAWA,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AA+CD,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/backend/client.ts"],"names":[],"mappings":"AAWA,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AA+CD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAEhG;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAErF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrF"}
|
|
@@ -54,10 +54,13 @@ async function request(method, baseUrl, path, body) {
|
|
|
54
54
|
}
|
|
55
55
|
return (parsed ?? {});
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Runs handler source from the working tree on the runner's sandbox route —
|
|
59
|
+
* against the app's real channels, without deploying it. A handler failure
|
|
60
|
+
* comes back as `ok: false` with the trace; only a refused request throws.
|
|
61
|
+
*/
|
|
62
|
+
function runSandbox(appName, body) {
|
|
63
|
+
return request('POST', backendBaseUrl(), `/z2h-apps/${encodeURIComponent(appName)}/sandbox/run`, body);
|
|
61
64
|
}
|
|
62
65
|
function listAppIntegrations(appName) {
|
|
63
66
|
return request('GET', brokerBaseUrl(), `/z2h-apps/${encodeURIComponent(appName)}/integrations`);
|
|
@@ -69,4 +72,4 @@ function removeIntegration(appName, integration) {
|
|
|
69
72
|
return request('DELETE', brokerBaseUrl(), `/z2h-apps/${encodeURIComponent(appName)}/integrations/${encodeURIComponent(integration)}`);
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
export { backendBaseUrl, declareIntegration,
|
|
75
|
+
export { backendBaseUrl, declareIntegration, listAppIntegrations, removeIntegration, runSandbox };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-sandbox-run.d.ts","sourceRoot":"","sources":["../../../src/backend/format-sandbox-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAmChE,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAchE"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const MAX_SQL_CHARS = 400;
|
|
2
|
+
const seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
|
|
3
|
+
const oneLine = (text) => text.replace(/\s+/g, ' ').trim();
|
|
4
|
+
const clip = (text, max) => (text.length > max ? `${text.slice(0, max)}…` : text);
|
|
5
|
+
/** The first arg is the SQL / GraphQL text for query channels — shown on its own line so it can be read. */
|
|
6
|
+
function describeArgs(args) {
|
|
7
|
+
const [first, ...rest] = args;
|
|
8
|
+
const lines = [];
|
|
9
|
+
if (typeof first === 'string') {
|
|
10
|
+
lines.push(clip(oneLine(first), MAX_SQL_CHARS));
|
|
11
|
+
}
|
|
12
|
+
else if (first !== undefined) {
|
|
13
|
+
lines.push(clip(JSON.stringify(first), MAX_SQL_CHARS));
|
|
14
|
+
}
|
|
15
|
+
const params = rest.filter(arg => arg !== undefined && arg !== null);
|
|
16
|
+
if (params.length > 0) {
|
|
17
|
+
lines.push(`params: ${clip(params.map(p => JSON.stringify(p)).join(', '), MAX_SQL_CHARS)}`);
|
|
18
|
+
}
|
|
19
|
+
return lines;
|
|
20
|
+
}
|
|
21
|
+
function describeCall(call) {
|
|
22
|
+
const rows = Array.isArray(call.result) ? ` ${call.result.length} rows` : '';
|
|
23
|
+
const head = ` ${call.ok ? '✓' : '✗'} ${call.method} ${Math.round(call.durationMs)}ms${rows}`;
|
|
24
|
+
const detail = describeArgs(call.args);
|
|
25
|
+
if (call.error) {
|
|
26
|
+
const code = call.error.code ? ` ${call.error.code}` : '';
|
|
27
|
+
detail.push(`${call.error.status}${code}: ${call.error.message}`);
|
|
28
|
+
}
|
|
29
|
+
return [head, ...detail.map(line => ` ${line}`)];
|
|
30
|
+
}
|
|
31
|
+
/** Human-readable trace of a sandbox run; `--json` prints the raw response instead. */
|
|
32
|
+
function formatSandboxRun(res) {
|
|
33
|
+
const lines = [`[z2h-cli] sandbox run ${res.ok ? '✓' : '✗'} ${seconds(res.durationMs)}`, ''];
|
|
34
|
+
lines.push(res.calls.length > 0 ? 'calls:' : 'calls: none');
|
|
35
|
+
res.calls.forEach(call => lines.push(...describeCall(call)));
|
|
36
|
+
lines.push('');
|
|
37
|
+
if (res.ok) {
|
|
38
|
+
lines.push('result:', JSON.stringify(res.result, null, 2));
|
|
39
|
+
}
|
|
40
|
+
else if (res.error) {
|
|
41
|
+
const code = res.error.code ? ` ${res.error.code}` : '';
|
|
42
|
+
lines.push(`error: ${res.error.status}${code} — ${res.error.message}`);
|
|
43
|
+
}
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { formatSandboxRun };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend-invoke.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/__tests__/backend-invoke.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export declare function hasHandlers(appDir: string): boolean;
|
|
2
|
+
export interface ReadDetectAndUploadResult {
|
|
3
|
+
integrations: string[];
|
|
4
|
+
handlerNames: string[];
|
|
5
|
+
apiUsages: Record<string, unknown>;
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
|
-
* Deploy the app's backend handlers to S3 and return the integrations detected
|
|
4
|
-
* from their sources. Each `.js` handler is uploaded as its own flat file — no
|
|
5
|
-
* compilation, no bundling, no npm deps.
|
|
6
|
-
*
|
|
7
8
|
* Integration sync note: the returned set is re-derived from the CURRENT backend
|
|
8
9
|
* sources on every deploy, and the platform set is reconciled to exactly match
|
|
9
10
|
* it (there is no stored declaration to fall back on). A deploy therefore always
|
|
@@ -17,13 +18,18 @@ export declare function deployBackend(opts: {
|
|
|
17
18
|
version: number;
|
|
18
19
|
deployedBy: string;
|
|
19
20
|
isPreview: boolean;
|
|
20
|
-
}): Promise<
|
|
21
|
+
}): Promise<ReadDetectAndUploadResult>;
|
|
21
22
|
/**
|
|
22
23
|
* Runs the same checks deploy does against the working tree, refreshing the
|
|
23
24
|
* local `usage.json` — no upload to S3. Throws on the first invalid handler.
|
|
24
25
|
*/
|
|
25
26
|
export declare function backendValidateCommand(appDir?: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Runs a handler from the working tree (or `--code`) on the runner's sandbox and
|
|
29
|
+
* prints the trace. Validated locally first, like deploy; exits 1 when the handler fails.
|
|
30
|
+
*/
|
|
26
31
|
export declare function backendInvokeCommand(handlerName: string, opts?: {
|
|
27
32
|
input?: string;
|
|
28
|
-
|
|
33
|
+
code?: string;
|
|
34
|
+
}, appDir?: string): Promise<void>;
|
|
29
35
|
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/commands/backend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/commands/backend.ts"],"names":[],"mappings":"AAyBA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAoBD,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAiCD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CASrC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAe1F;AAsBD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,EAC5C,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|