@mondaydotcomorg/z2h-cli 0.32.2 → 0.33.1
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/__tests__/custom-integrations.test.d.ts +2 -0
- package/dist/commands/__tests__/custom-integrations.test.d.ts.map +1 -0
- package/dist/commands/backend.d.ts +6 -1
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +35 -3
- package/dist/commands/custom-integrations.d.ts +3 -0
- package/dist/commands/custom-integrations.d.ts.map +1 -0
- package/dist/commands/custom-integrations.js +51 -0
- package/dist/commands/deploy.js +3 -0
- 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/__tests__/custom-integrations.test.d.ts +2 -0
- package/dist/esm/commands/__tests__/custom-integrations.test.d.ts.map +1 -0
- package/dist/esm/commands/backend.d.ts +6 -1
- package/dist/esm/commands/backend.d.ts.map +1 -1
- package/dist/esm/commands/backend.mjs +36 -4
- package/dist/esm/commands/custom-integrations.d.ts +3 -0
- package/dist/esm/commands/custom-integrations.d.ts.map +1 -0
- package/dist/esm/commands/custom-integrations.mjs +48 -0
- package/dist/esm/commands/deploy.mjs +3 -0
- package/dist/esm/index.mjs +18 -4
- package/dist/esm/util/app-name.mjs +1 -1
- package/dist/esm/util/broker/app.d.ts +4 -1
- package/dist/esm/util/broker/app.d.ts.map +1 -1
- package/dist/index.js +18 -4
- package/dist/util/app-name.js +1 -1
- package/dist/util/broker/app.d.ts +4 -1
- package/dist/util/broker/app.d.ts.map +1 -1
- package/handler-api/backend-runner/backend-runner.errors.ts +13 -0
- package/handler-api/backend-runner/backend-runner.types.ts +3 -0
- package/handler-api/backend-runner/channels/channel-map.ts +26 -6
- package/handler-api/backend-runner/channels/channel-telemetry.ts +13 -7
- package/handler-api/backend-runner/channels/channel-trace.ts +52 -0
- package/handler-api/backend-runner/channels/custom-integration-channel/custom-integration.channel.ts +243 -0
- package/handler-api/signatures.json +4 -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-invoke.test.ts +125 -0
- package/src/commands/__tests__/custom-integrations.test.ts +62 -0
- package/src/commands/backend.ts +43 -4
- package/src/commands/custom-integrations.ts +68 -0
- package/src/commands/deploy.ts +3 -0
- package/src/index.ts +24 -5
- package/src/util/app-name.ts +1 -1
- package/src/util/broker/__tests__/app.test.ts +32 -0
- package/src/util/broker/app.ts +4 -1
|
@@ -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":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-integrations.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/custom-integrations.test.ts"],"names":[],"mappings":""}
|
|
@@ -23,7 +23,12 @@ export declare function deployBackend(opts: {
|
|
|
23
23
|
* local `usage.json` — no upload to S3. Throws on the first invalid handler.
|
|
24
24
|
*/
|
|
25
25
|
export declare function backendValidateCommand(appDir?: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Runs a handler from the working tree (or `--code`) on the runner's sandbox and
|
|
28
|
+
* prints the trace. Validated locally first, like deploy; exits 1 when the handler fails.
|
|
29
|
+
*/
|
|
26
30
|
export declare function backendInvokeCommand(handlerName: string, opts?: {
|
|
27
31
|
input?: string;
|
|
28
|
-
|
|
32
|
+
code?: string;
|
|
33
|
+
}, appDir?: string): Promise<void>;
|
|
29
34
|
//# 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;AAqDD;;;;;;;;;;GAUG;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,MAAM,EAAE,CAAC,CASpB;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 };
|
|
@@ -111,7 +114,29 @@ async function backendValidateCommand(appDir = process.cwd()) {
|
|
|
111
114
|
integrations,
|
|
112
115
|
});
|
|
113
116
|
}
|
|
114
|
-
async function
|
|
117
|
+
async function readHandlerSource(appDir, handlerName) {
|
|
118
|
+
const dir = path__default.default.join(appDir, 'backend', 'handlers');
|
|
119
|
+
try {
|
|
120
|
+
return await promises.readFile(path__default.default.join(dir, `${handlerName}.js`), 'utf8');
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err.code !== 'ENOENT') {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
const files = hasHandlers(appDir) ? await promises.readdir(dir) : [];
|
|
127
|
+
const available = files
|
|
128
|
+
.filter(f => f.endsWith('.js'))
|
|
129
|
+
.map(f => path__default.default.parse(f).name)
|
|
130
|
+
.sort();
|
|
131
|
+
throw new Error(`no handler "${handlerName}" in backend/handlers/` +
|
|
132
|
+
(available.length ? ` (available: ${available.join(', ')})` : ' (no handlers found)'));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Runs a handler from the working tree (or `--code`) on the runner's sandbox and
|
|
137
|
+
* prints the trace. Validated locally first, like deploy; exits 1 when the handler fails.
|
|
138
|
+
*/
|
|
139
|
+
async function backendInvokeCommand(handlerName, opts = {}, appDir = process.cwd()) {
|
|
115
140
|
const appName = currentAppName();
|
|
116
141
|
let input = null;
|
|
117
142
|
if (opts.input) {
|
|
@@ -122,8 +147,15 @@ async function backendInvokeCommand(handlerName, opts = {}) {
|
|
|
122
147
|
throw new Error(`--input must be valid JSON (got: ${opts.input})`);
|
|
123
148
|
}
|
|
124
149
|
}
|
|
125
|
-
const
|
|
126
|
-
|
|
150
|
+
const file = `${handlerName}.js`;
|
|
151
|
+
const code = opts.code ?? (await readHandlerSource(appDir, handlerName));
|
|
152
|
+
backend_validate.validateHandlerSource(file, code);
|
|
153
|
+
util_logger.info(`validated ${opts.code ? '--code' : file}`);
|
|
154
|
+
const res = await backend_client.runSandbox(appName, { code, input, handlerName });
|
|
155
|
+
util_logger.output(backend_formatSandboxRun.formatSandboxRun(res), res);
|
|
156
|
+
if (!res.ok) {
|
|
157
|
+
process.exitCode = 1;
|
|
158
|
+
}
|
|
127
159
|
}
|
|
128
160
|
|
|
129
161
|
exports.backendInvokeCommand = backendInvokeCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-integrations.d.ts","sourceRoot":"","sources":["../../src/commands/custom-integrations.ts"],"names":[],"mappings":"AAyBA,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC,CAsBnE;AAED,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB5F"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const util_logger = require('../util/logger.js');
|
|
4
|
+
const util_appName = require('../util/app-name.js');
|
|
5
|
+
const constants = require('../constants.js');
|
|
6
|
+
const util_auth_authStore = require('../util/auth/auth-store.js');
|
|
7
|
+
|
|
8
|
+
function getTargetAppName() {
|
|
9
|
+
const consumerName = util_appName.getCwdAppName();
|
|
10
|
+
if (!consumerName) {
|
|
11
|
+
throw new Error('Run this from inside a consumer app directory (the one with your package.json).');
|
|
12
|
+
}
|
|
13
|
+
return util_appName.mfAppName(consumerName);
|
|
14
|
+
}
|
|
15
|
+
async function listCustomIntegrationsCommand() {
|
|
16
|
+
const appName = getTargetAppName();
|
|
17
|
+
const { token } = await util_auth_authStore.readZthAuth();
|
|
18
|
+
const res = await fetch(`${constants.brokerBaseUrl()}/z2h-apps/${appName}/custom-integrations`, {
|
|
19
|
+
headers: { Authorization: `Bearer ${token}`, ...constants.routingHeaders() },
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const text = await res.text();
|
|
23
|
+
throw new Error(`Listing custom integrations failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
24
|
+
}
|
|
25
|
+
const { connections } = (await res.json());
|
|
26
|
+
if (connections.length === 0) {
|
|
27
|
+
util_logger.output(`[z2h-cli] "${appName}" has no custom integrations registered`, { ok: true, appName, connections: [] });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const lines = connections.map(c => ` ${c.name} → ${c.allowedHost} (${c.authHeaderName})${c.inherited ? ' [inherited from base app]' : ''}`);
|
|
31
|
+
util_logger.output(`[z2h-cli] "${appName}" custom integrations:\n${lines.join('\n')}`, { ok: true, appName, connections });
|
|
32
|
+
}
|
|
33
|
+
async function removeCustomIntegrationCommand(name) {
|
|
34
|
+
if (!name) {
|
|
35
|
+
throw new Error('Specify the connection name to remove — e.g. z2h-cli custom-integration:remove stripe');
|
|
36
|
+
}
|
|
37
|
+
const appName = getTargetAppName();
|
|
38
|
+
const { token } = await util_auth_authStore.readZthAuth();
|
|
39
|
+
const res = await fetch(`${constants.brokerBaseUrl()}/z2h-apps/${appName}/custom-integrations/${encodeURIComponent(name)}`, {
|
|
40
|
+
method: 'DELETE',
|
|
41
|
+
headers: { Authorization: `Bearer ${token}`, ...constants.routingHeaders() },
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const text = await res.text();
|
|
45
|
+
throw new Error(`Removing custom integration failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
46
|
+
}
|
|
47
|
+
util_logger.output(`[z2h-cli] custom integration "${name}" removed from "${appName}"`, { ok: true, appName, name });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
exports.listCustomIntegrationsCommand = listCustomIntegrationsCommand;
|
|
51
|
+
exports.removeCustomIntegrationCommand = removeCustomIntegrationCommand;
|
package/dist/commands/deploy.js
CHANGED
|
@@ -148,6 +148,9 @@ async function deployPreview({ paths, consumerName, appName, token, opts }) {
|
|
|
148
148
|
entry,
|
|
149
149
|
gitRemote: '',
|
|
150
150
|
isPreview: true,
|
|
151
|
+
// The base app this preview is for — bigbrain-zth links the preview's row
|
|
152
|
+
// to it (once the caller's EDITOR access on it is confirmed) so custom
|
|
153
|
+
// integrations set on the base app are inherited by the preview.
|
|
151
154
|
masterAppName: appName,
|
|
152
155
|
});
|
|
153
156
|
const previewUrl = `https://bigbrain.me/bigbrain-vibe/${previewSlug}`;
|
|
@@ -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":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-integrations.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/__tests__/custom-integrations.test.ts"],"names":[],"mappings":""}
|
|
@@ -23,7 +23,12 @@ export declare function deployBackend(opts: {
|
|
|
23
23
|
* local `usage.json` — no upload to S3. Throws on the first invalid handler.
|
|
24
24
|
*/
|
|
25
25
|
export declare function backendValidateCommand(appDir?: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Runs a handler from the working tree (or `--code`) on the runner's sandbox and
|
|
28
|
+
* prints the trace. Validated locally first, like deploy; exits 1 when the handler fails.
|
|
29
|
+
*/
|
|
26
30
|
export declare function backendInvokeCommand(handlerName: string, opts?: {
|
|
27
31
|
input?: string;
|
|
28
|
-
|
|
32
|
+
code?: string;
|
|
33
|
+
}, appDir?: string): Promise<void>;
|
|
29
34
|
//# 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;AAqDD;;;;;;;;;;GAUG;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,MAAM,EAAE,CAAC,CASpB;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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
3
4
|
import { readJson, writeJson } from 'fs-extra';
|
|
4
5
|
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
|
5
6
|
import { output, info } from '../util/logger.mjs';
|
|
@@ -9,7 +10,9 @@ import { readHandlers } from '../backend/handlers.mjs';
|
|
|
9
10
|
import { detectIntegrations } from '../backend/detect-integrations.mjs';
|
|
10
11
|
import { getS3Client } from '../util/s3/client.mjs';
|
|
11
12
|
import { BUCKET_WRITE_NAME } from '../constants.mjs';
|
|
12
|
-
import {
|
|
13
|
+
import { runSandbox } from '../backend/client.mjs';
|
|
14
|
+
import { formatSandboxRun } from '../backend/format-sandbox-run.mjs';
|
|
15
|
+
import { validateHandlerSource } from '../backend/validate.mjs';
|
|
13
16
|
import { tagDeployError } from '../util/errors.mjs';
|
|
14
17
|
|
|
15
18
|
function currentAppName() {
|
|
@@ -105,7 +108,29 @@ async function backendValidateCommand(appDir = process.cwd()) {
|
|
|
105
108
|
integrations,
|
|
106
109
|
});
|
|
107
110
|
}
|
|
108
|
-
async function
|
|
111
|
+
async function readHandlerSource(appDir, handlerName) {
|
|
112
|
+
const dir = path.join(appDir, 'backend', 'handlers');
|
|
113
|
+
try {
|
|
114
|
+
return await readFile(path.join(dir, `${handlerName}.js`), 'utf8');
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err.code !== 'ENOENT') {
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
const files = hasHandlers(appDir) ? await readdir(dir) : [];
|
|
121
|
+
const available = files
|
|
122
|
+
.filter(f => f.endsWith('.js'))
|
|
123
|
+
.map(f => path.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()) {
|
|
109
134
|
const appName = currentAppName();
|
|
110
135
|
let input = null;
|
|
111
136
|
if (opts.input) {
|
|
@@ -116,8 +141,15 @@ async function backendInvokeCommand(handlerName, opts = {}) {
|
|
|
116
141
|
throw new Error(`--input must be valid JSON (got: ${opts.input})`);
|
|
117
142
|
}
|
|
118
143
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
144
|
+
const file = `${handlerName}.js`;
|
|
145
|
+
const code = opts.code ?? (await readHandlerSource(appDir, handlerName));
|
|
146
|
+
validateHandlerSource(file, code);
|
|
147
|
+
info(`validated ${opts.code ? '--code' : file}`);
|
|
148
|
+
const res = await runSandbox(appName, { code, input, handlerName });
|
|
149
|
+
output(formatSandboxRun(res), res);
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
process.exitCode = 1;
|
|
152
|
+
}
|
|
121
153
|
}
|
|
122
154
|
|
|
123
155
|
export { backendInvokeCommand, backendValidateCommand, deployBackend, hasHandlers };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-integrations.d.ts","sourceRoot":"","sources":["../../../src/commands/custom-integrations.ts"],"names":[],"mappings":"AAyBA,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC,CAsBnE;AAED,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB5F"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { output } from '../util/logger.mjs';
|
|
2
|
+
import { getCwdAppName, mfAppName } from '../util/app-name.mjs';
|
|
3
|
+
import { brokerBaseUrl, routingHeaders } from '../constants.mjs';
|
|
4
|
+
import { readZthAuth } from '../util/auth/auth-store.mjs';
|
|
5
|
+
|
|
6
|
+
function getTargetAppName() {
|
|
7
|
+
const consumerName = getCwdAppName();
|
|
8
|
+
if (!consumerName) {
|
|
9
|
+
throw new Error('Run this from inside a consumer app directory (the one with your package.json).');
|
|
10
|
+
}
|
|
11
|
+
return mfAppName(consumerName);
|
|
12
|
+
}
|
|
13
|
+
async function listCustomIntegrationsCommand() {
|
|
14
|
+
const appName = getTargetAppName();
|
|
15
|
+
const { token } = await readZthAuth();
|
|
16
|
+
const res = await fetch(`${brokerBaseUrl()}/z2h-apps/${appName}/custom-integrations`, {
|
|
17
|
+
headers: { Authorization: `Bearer ${token}`, ...routingHeaders() },
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
const text = await res.text();
|
|
21
|
+
throw new Error(`Listing custom integrations failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
22
|
+
}
|
|
23
|
+
const { connections } = (await res.json());
|
|
24
|
+
if (connections.length === 0) {
|
|
25
|
+
output(`[z2h-cli] "${appName}" has no custom integrations registered`, { ok: true, appName, connections: [] });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const lines = connections.map(c => ` ${c.name} → ${c.allowedHost} (${c.authHeaderName})${c.inherited ? ' [inherited from base app]' : ''}`);
|
|
29
|
+
output(`[z2h-cli] "${appName}" custom integrations:\n${lines.join('\n')}`, { ok: true, appName, connections });
|
|
30
|
+
}
|
|
31
|
+
async function removeCustomIntegrationCommand(name) {
|
|
32
|
+
if (!name) {
|
|
33
|
+
throw new Error('Specify the connection name to remove — e.g. z2h-cli custom-integration:remove stripe');
|
|
34
|
+
}
|
|
35
|
+
const appName = getTargetAppName();
|
|
36
|
+
const { token } = await readZthAuth();
|
|
37
|
+
const res = await fetch(`${brokerBaseUrl()}/z2h-apps/${appName}/custom-integrations/${encodeURIComponent(name)}`, {
|
|
38
|
+
method: 'DELETE',
|
|
39
|
+
headers: { Authorization: `Bearer ${token}`, ...routingHeaders() },
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const text = await res.text();
|
|
43
|
+
throw new Error(`Removing custom integration failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
44
|
+
}
|
|
45
|
+
output(`[z2h-cli] custom integration "${name}" removed from "${appName}"`, { ok: true, appName, name });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { listCustomIntegrationsCommand, removeCustomIntegrationCommand };
|