@mondaydotcomorg/z2h-cli 0.27.1 → 0.29.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/backend/__tests__/ast.test.d.ts +2 -0
- package/dist/backend/__tests__/ast.test.d.ts.map +1 -0
- package/dist/backend/__tests__/parse.test.d.ts +2 -0
- package/dist/backend/__tests__/parse.test.d.ts.map +1 -0
- package/dist/backend/__tests__/validate.test.d.ts +2 -0
- package/dist/backend/__tests__/validate.test.d.ts.map +1 -0
- package/dist/backend/ast.d.ts +41 -0
- package/dist/backend/ast.d.ts.map +1 -0
- package/dist/backend/ast.js +152 -0
- package/dist/backend/detect-integrations.d.ts +8 -4
- package/dist/backend/detect-integrations.d.ts.map +1 -1
- package/dist/backend/detect-integrations.js +12 -15
- package/dist/backend/handler-declaration.d.ts +17 -0
- package/dist/backend/handler-declaration.d.ts.map +1 -0
- package/dist/backend/handler-declaration.js +83 -0
- package/dist/backend/handlers.d.ts +2 -0
- package/dist/backend/handlers.d.ts.map +1 -1
- package/dist/backend/handlers.js +7 -36
- package/dist/backend/messages.d.ts +10 -0
- package/dist/backend/messages.d.ts.map +1 -0
- package/dist/backend/messages.js +14 -0
- package/dist/backend/parse.d.ts +11 -0
- package/dist/backend/parse.d.ts.map +1 -0
- package/dist/backend/parse.js +66 -0
- package/dist/backend/rules/__tests__/snowflake-static-sql.test.d.ts +2 -0
- package/dist/backend/rules/__tests__/snowflake-static-sql.test.d.ts.map +1 -0
- package/dist/backend/rules/index.d.ts +5 -0
- package/dist/backend/rules/index.d.ts.map +1 -0
- package/dist/backend/rules/index.js +8 -0
- package/dist/backend/rules/snowflake-static-sql.d.ts +15 -0
- package/dist/backend/rules/snowflake-static-sql.d.ts.map +1 -0
- package/dist/backend/rules/snowflake-static-sql.js +75 -0
- package/dist/backend/rules/types.d.ts +29 -0
- package/dist/backend/rules/types.d.ts.map +1 -0
- package/dist/backend/rules/types.js +1 -0
- package/dist/backend/validate.d.ts +23 -0
- package/dist/backend/validate.d.ts.map +1 -0
- package/dist/backend/validate.js +64 -0
- package/dist/commands/__tests__/backend-validate.test.d.ts +2 -0
- package/dist/commands/__tests__/backend-validate.test.d.ts.map +1 -0
- package/dist/commands/backend.d.ts +6 -0
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +22 -2
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +10 -3
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +2 -1
- package/dist/constants.d.ts +1 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -6
- package/dist/esm/backend/__tests__/ast.test.d.ts +2 -0
- package/dist/esm/backend/__tests__/ast.test.d.ts.map +1 -0
- package/dist/esm/backend/__tests__/parse.test.d.ts +2 -0
- package/dist/esm/backend/__tests__/parse.test.d.ts.map +1 -0
- package/dist/esm/backend/__tests__/validate.test.d.ts +2 -0
- package/dist/esm/backend/__tests__/validate.test.d.ts.map +1 -0
- package/dist/esm/backend/ast.d.ts +41 -0
- package/dist/esm/backend/ast.d.ts.map +1 -0
- package/dist/esm/backend/ast.mjs +146 -0
- package/dist/esm/backend/detect-integrations.d.ts +8 -4
- package/dist/esm/backend/detect-integrations.d.ts.map +1 -1
- package/dist/esm/backend/detect-integrations.mjs +12 -15
- package/dist/esm/backend/handler-declaration.d.ts +17 -0
- package/dist/esm/backend/handler-declaration.d.ts.map +1 -0
- package/dist/esm/backend/handler-declaration.mjs +78 -0
- package/dist/esm/backend/handlers.d.ts +2 -0
- package/dist/esm/backend/handlers.d.ts.map +1 -1
- package/dist/esm/backend/handlers.mjs +7 -36
- package/dist/esm/backend/messages.d.ts +10 -0
- package/dist/esm/backend/messages.d.ts.map +1 -0
- package/dist/esm/backend/messages.mjs +12 -0
- package/dist/esm/backend/parse.d.ts +11 -0
- package/dist/esm/backend/parse.d.ts.map +1 -0
- package/dist/esm/backend/parse.mjs +64 -0
- package/dist/esm/backend/rules/__tests__/snowflake-static-sql.test.d.ts +2 -0
- package/dist/esm/backend/rules/__tests__/snowflake-static-sql.test.d.ts.map +1 -0
- package/dist/esm/backend/rules/index.d.ts +5 -0
- package/dist/esm/backend/rules/index.d.ts.map +1 -0
- package/dist/esm/backend/rules/index.mjs +6 -0
- package/dist/esm/backend/rules/snowflake-static-sql.d.ts +15 -0
- package/dist/esm/backend/rules/snowflake-static-sql.d.ts.map +1 -0
- package/dist/esm/backend/rules/snowflake-static-sql.mjs +73 -0
- package/dist/esm/backend/rules/types.d.ts +29 -0
- package/dist/esm/backend/rules/types.d.ts.map +1 -0
- package/dist/esm/backend/rules/types.mjs +1 -0
- package/dist/esm/backend/validate.d.ts +23 -0
- package/dist/esm/backend/validate.d.ts.map +1 -0
- package/dist/esm/backend/validate.mjs +62 -0
- package/dist/esm/commands/__tests__/backend-validate.test.d.ts +2 -0
- package/dist/esm/commands/__tests__/backend-validate.test.d.ts.map +1 -0
- package/dist/esm/commands/backend.d.ts +6 -0
- package/dist/esm/commands/backend.d.ts.map +1 -1
- package/dist/esm/commands/backend.mjs +23 -4
- package/dist/esm/commands/create.d.ts.map +1 -1
- package/dist/esm/commands/create.mjs +11 -4
- package/dist/esm/commands/generate.d.ts.map +1 -1
- package/dist/esm/commands/generate.mjs +2 -1
- package/dist/esm/constants.d.ts +1 -2
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.mjs +3 -6
- package/dist/esm/index.mjs +6 -2
- package/dist/esm/util/broker/app.d.ts +12 -2
- package/dist/esm/util/broker/app.d.ts.map +1 -1
- package/dist/esm/util/broker/app.mjs +20 -2
- package/dist/index.js +5 -1
- package/dist/util/broker/app.d.ts +12 -2
- package/dist/util/broker/app.d.ts.map +1 -1
- package/dist/util/broker/app.js +20 -1
- package/package.json +4 -2
- package/src/backend/__tests__/ast.test.ts +53 -0
- package/src/backend/__tests__/detect-integrations.test.ts +12 -30
- package/src/backend/__tests__/handlers.test.ts +21 -34
- package/src/backend/__tests__/parse.test.ts +25 -0
- package/src/backend/__tests__/validate.test.ts +246 -0
- package/src/backend/ast.ts +204 -0
- package/src/backend/detect-integrations.ts +12 -14
- package/src/backend/handler-declaration.ts +104 -0
- package/src/backend/handlers.ts +9 -44
- package/src/backend/messages.ts +16 -0
- package/src/backend/parse.ts +82 -0
- package/src/backend/rules/__tests__/snowflake-static-sql.test.ts +175 -0
- package/src/backend/rules/index.ts +7 -0
- package/src/backend/rules/snowflake-static-sql.ts +78 -0
- package/src/backend/rules/types.ts +30 -0
- package/src/backend/validate.ts +73 -0
- package/src/commands/__tests__/backend-deploy.test.ts +5 -3
- package/src/commands/__tests__/backend-validate.test.ts +71 -0
- package/src/commands/__tests__/create.test.ts +46 -0
- package/src/commands/__tests__/edit.test.ts +6 -0
- package/src/commands/backend.ts +23 -3
- package/src/commands/create.ts +13 -4
- package/src/commands/generate.ts +2 -1
- package/src/constants.ts +5 -6
- package/src/index.ts +9 -2
- package/src/util/broker/__tests__/app.test.ts +29 -1
- package/src/util/broker/app.ts +24 -2
|
@@ -4,7 +4,7 @@ import { PutObjectCommand } from '@aws-sdk/client-s3';
|
|
|
4
4
|
import { output, info } from '../util/logger.mjs';
|
|
5
5
|
import { getCwdAppName, mfAppName } from '../util/app-name.mjs';
|
|
6
6
|
import { readHandlers } from '../backend/handlers.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { detectIntegrations } from '../backend/detect-integrations.mjs';
|
|
8
8
|
import { getS3Client } from '../util/s3/client.mjs';
|
|
9
9
|
import { BUCKET_WRITE_NAME } from '../constants.mjs';
|
|
10
10
|
import { invokeBackend } from '../backend/client.mjs';
|
|
@@ -36,8 +36,8 @@ async function readDetectAndUpload(appDir, prefix, version, deployedBy, logLabel
|
|
|
36
36
|
return [];
|
|
37
37
|
}
|
|
38
38
|
info(`reading backend handlers ${logLabel}`);
|
|
39
|
-
const { handlers } = await readHandlers(appDir);
|
|
40
|
-
const integrations =
|
|
39
|
+
const { handlers, apiDomains } = await readHandlers(appDir);
|
|
40
|
+
const integrations = detectIntegrations(apiDomains);
|
|
41
41
|
if (Object.keys(handlers).length === 0) {
|
|
42
42
|
return integrations;
|
|
43
43
|
}
|
|
@@ -77,6 +77,25 @@ async function deployBackend(opts) {
|
|
|
77
77
|
throw tagDeployError('deploy_backend_error', err);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Run the same handler validation deploy runs (syntax, isolate constraints,
|
|
82
|
+
* handler rules, integration detection) against the working tree — without
|
|
83
|
+
* uploading anything. Throws on the first invalid handler.
|
|
84
|
+
*/
|
|
85
|
+
async function backendValidateCommand(appDir = process.cwd()) {
|
|
86
|
+
if (!hasHandlers(appDir)) {
|
|
87
|
+
output('[z2h-cli] no backend/handlers/ directory — nothing to validate', { handlers: [], integrations: [] });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const { handlers, apiDomains } = await readHandlers(appDir);
|
|
91
|
+
const names = Object.keys(handlers).sort();
|
|
92
|
+
const integrations = detectIntegrations(apiDomains);
|
|
93
|
+
const list = (xs) => (xs.length ? xs.join(', ') : 'none');
|
|
94
|
+
output(`[z2h-cli] ${names.length} handler(s) valid: ${list(names)}\n[z2h-cli] integrations: ${list(integrations)}`, {
|
|
95
|
+
handlers: names,
|
|
96
|
+
integrations,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
80
99
|
async function backendInvokeCommand(handlerName, opts = {}) {
|
|
81
100
|
const appName = currentAppName();
|
|
82
101
|
let input = null;
|
|
@@ -92,4 +111,4 @@ async function backendInvokeCommand(handlerName, opts = {}) {
|
|
|
92
111
|
output(`[z2h-cli] result (${Math.round(res.durationMs)}ms):\n${JSON.stringify(res.result, null, 2)}`, res);
|
|
93
112
|
}
|
|
94
113
|
|
|
95
|
-
export { backendInvokeCommand, deployBackend, hasHandlers };
|
|
114
|
+
export { backendInvokeCommand, backendValidateCommand, deployBackend, hasHandlers };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/create.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAKpH,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAwBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/create.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAKpH,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAwBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6F5F"}
|
|
@@ -8,7 +8,7 @@ import { runYarnInstall } from '../util/npm/install.mjs';
|
|
|
8
8
|
import { initRepo } from '../util/git/repo.mjs';
|
|
9
9
|
import { validateRemoteOptions, resolveRemoteUrl } from '../util/git/remote-options.mjs';
|
|
10
10
|
export { REMOTE_TYPES } from '../util/git/remote-options.mjs';
|
|
11
|
-
import { fetchApp } from '../util/broker/app.mjs';
|
|
11
|
+
import { fetchApp, claimApp } from '../util/broker/app.mjs';
|
|
12
12
|
import { validateAppName, mfAppName } from '../util/app-name.mjs';
|
|
13
13
|
import { prepareShadow } from '../shadow/prepare.mjs';
|
|
14
14
|
|
|
@@ -53,11 +53,19 @@ async function createCommand(appName, opts = {}) {
|
|
|
53
53
|
if (!(await isDirEmpty(appDir))) {
|
|
54
54
|
throw new Error(`${appDir} is non-empty. Pick a different name or wipe it first.`);
|
|
55
55
|
}
|
|
56
|
-
// Check the broker — fail fast if the app
|
|
56
|
+
// Check the broker — fail fast if the app already has a real release.
|
|
57
|
+
// A claimed-but-unpublished stub (`entry: null`) is fine to proceed past:
|
|
58
|
+
// either it's this same name being re-created after a local wipe (we'd only
|
|
59
|
+
// see it at all if we still have access to it), or it genuinely doesn't
|
|
60
|
+
// exist yet and we claim it below.
|
|
57
61
|
const existing = await fetchApp(mfAppName(appName));
|
|
58
|
-
if (existing) {
|
|
62
|
+
if (existing?.deployed) {
|
|
59
63
|
throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
|
|
60
64
|
}
|
|
65
|
+
const gitRemote = resolveRemoteUrl(opts.remote, appName, opts.repoUrl);
|
|
66
|
+
if (!existing) {
|
|
67
|
+
await claimApp(mfAppName(appName), gitRemote);
|
|
68
|
+
}
|
|
61
69
|
// Compute substitutions.
|
|
62
70
|
const vars = {
|
|
63
71
|
__APP_NAME__: appName,
|
|
@@ -82,7 +90,6 @@ async function createCommand(appName, opts = {}) {
|
|
|
82
90
|
await copy(opts.htmlFile, path.join(appDir, 'src', 'page.html'));
|
|
83
91
|
}
|
|
84
92
|
info(`initializing git repo for ${appName}...`);
|
|
85
|
-
const gitRemote = resolveRemoteUrl(opts.remote, appName, opts.repoUrl);
|
|
86
93
|
await initRepo(appDir, gitRemote);
|
|
87
94
|
info(`installing dependencies for ${appName}...`);
|
|
88
95
|
await runYarnInstall({ cwd: workspace });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/generate.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAwHD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/generate.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAwHD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA8E3F"}
|
|
@@ -128,8 +128,9 @@ async function generateCommand(appName, opts) {
|
|
|
128
128
|
if (!(await isDirEmpty(appDir))) {
|
|
129
129
|
throw new Error(`${appDir} is non-empty. Pick a different name or wipe it first.`);
|
|
130
130
|
}
|
|
131
|
+
// A claimed-but-unpublished stub (`entry: null`) is fine to proceed past — only a real release blocks generate.
|
|
131
132
|
const existing = await fetchApp(mfAppName(appName));
|
|
132
|
-
if (existing) {
|
|
133
|
+
if (existing?.deployed) {
|
|
133
134
|
throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
|
|
134
135
|
}
|
|
135
136
|
info(`copying template "${template.id}" → ${appDir}`);
|
package/dist/esm/constants.d.ts
CHANGED
|
@@ -20,8 +20,7 @@ export declare const S3_SKILLS_BUCKET_NAME = "prod-use1-bigbrain-zth-skills";
|
|
|
20
20
|
export declare const DATAVIZ_SKILLS_PREFIX = "dataviz/";
|
|
21
21
|
export declare const APP_NAME_PATTERN: RegExp;
|
|
22
22
|
export declare const PREVIEW_APP_NAME_PREFIX = "zth-preview-";
|
|
23
|
-
export
|
|
24
|
-
export type DeclaredIntegration = (typeof DECLARED_INTEGRATIONS)[number];
|
|
23
|
+
export { ALWAYS_ON_CHANNELS, DECLARED_INTEGRATIONS, type DeclaredIntegration } from '@mondaydotcomorg/z2h-shared-utils';
|
|
25
24
|
export declare const DEFAULT_BROKER_BASE_URL = "https://bigbrain-zth.bigbrain.me";
|
|
26
25
|
/**
|
|
27
26
|
* Resolves the broker origin (no trailing slash). Honors
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,MAAM,eAAe,SAAS,CAAC;AAGtC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAEjD,CAAC;AAKF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAMF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAG9B,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAQ3C,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AACpE,eAAO,MAAM,MAAM,cAAc,CAAC;AAClC,eAAO,MAAM,gBAAgB,wEAA4D,CAAC;AAE1F,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,mBAAmB,+CAAgD,CAAC;AAKjF,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAK7C,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,qBAAqB,kCAAkC,CAAC;AAIrE,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAMhD,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAEzD,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAMtD,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,MAAM,eAAe,SAAS,CAAC;AAGtC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAEjD,CAAC;AAKF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAMF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAG9B,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAQ3C,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AACpE,eAAO,MAAM,MAAM,cAAc,CAAC;AAClC,eAAO,MAAM,gBAAgB,wEAA4D,CAAC;AAE1F,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,mBAAmB,+CAAgD,CAAC;AAKjF,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAK7C,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,qBAAqB,kCAAkC,CAAC;AAIrE,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAMhD,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAEzD,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAMtD,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAKxH,eAAO,MAAM,uBAAuB,qCAAqC,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGvD;AAKD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAG9C,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAGnD,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAMrE,eAAO,MAAM,iBAAiB,EAAG,eAAwB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
|
package/dist/esm/constants.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { ALWAYS_ON_CHANNELS, DECLARED_INTEGRATIONS } from '@mondaydotcomorg/z2h-shared-utils';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Cross-file constants. Narrowly-used literals (templates, prefixes used in
|
|
3
5
|
* one place) stay local to their file to keep this index small.
|
|
@@ -55,11 +57,6 @@ const DATAVIZ_SKILLS_PREFIX = 'dataviz/';
|
|
|
55
57
|
// upfront for friendlier errors.
|
|
56
58
|
const APP_NAME_PATTERN = /^[a-z][a-z0-9-]{1,49}$/;
|
|
57
59
|
const PREVIEW_APP_NAME_PREFIX = 'zth-preview-';
|
|
58
|
-
// ─────────── Declared (OAuth-gated) backend integrations ─────
|
|
59
|
-
// Closed set the CLI detects from handler sources and the broker validates on
|
|
60
|
-
// register/update. Always-on channels (`db`, `llm`, `snowflake`) are not listed —
|
|
61
|
-
// the runner builds those regardless. Shared via `@mondaydotcomorg/z2h-cli/constants`.
|
|
62
|
-
const DECLARED_INTEGRATIONS = ['monday'];
|
|
63
60
|
// ─────────── Broker origin + npm registry (shared §3 contract) ─────
|
|
64
61
|
// The bigbrain-zth broker fronts both the STS credential route and the npm
|
|
65
62
|
// proxy. `Z2H_BROKER_BASE_URL` overrides the origin for tests/staging.
|
|
@@ -101,4 +98,4 @@ function brokerNpmRegistryUrl() {
|
|
|
101
98
|
return `${brokerBaseUrl()}${NPM_BROKER_PATH}`;
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
export { APP_NAME_PATTERN, BROKER_NPM_SCOPE, BUCKET_READ_DNS_NAME, BUCKET_READ_DNS_URL, BUCKET_WRITE_NAME, BUCKET_WRITE_URL, DATAVIZ_SKILLS_PREFIX,
|
|
101
|
+
export { APP_NAME_PATTERN, BROKER_NPM_SCOPE, BUCKET_READ_DNS_NAME, BUCKET_READ_DNS_URL, BUCKET_WRITE_NAME, BUCKET_WRITE_URL, DATAVIZ_SKILLS_PREFIX, DEFAULT_BROKER_BASE_URL, GITIGNORE_ENTRIES, MANIFEST_KEY, MANIFEST_READ_URL, MAX_PORT, MIN_PORT, NPM_BROKER_PATH, PREVIEW_APP_NAME_PREFIX, PUBLIC_NPM_REGISTRY_URL, REGION, S3_PREVIEWS_FOLDER, S3_SKILLS_BUCKET_NAME, SHADOW_DEV_DEPS, SHADOW_DIR_NAME, SHADOW_SCRIPTS, Z2H_NPM_TOKEN_ENV, brokerBaseUrl, brokerNpmRegistryUrl, routingHeaders };
|
package/dist/esm/index.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { generateZ2HTokenCommand } from './commands/generate-z2h-token.mjs';
|
|
|
17
17
|
import { migrateCommand } from './commands/migrate.mjs';
|
|
18
18
|
import { tagCommand } from './commands/tag.mjs';
|
|
19
19
|
import { editCommand } from './commands/edit.mjs';
|
|
20
|
-
import { backendInvokeCommand } from './commands/backend.mjs';
|
|
20
|
+
import { backendValidateCommand, backendInvokeCommand } from './commands/backend.mjs';
|
|
21
21
|
import { doctorCommand } from './commands/doctor.mjs';
|
|
22
22
|
import { fail, errorLog, setOutputMode, getOutputMode } from './util/logger.mjs';
|
|
23
23
|
import { runCommand } from './util/run-command.mjs';
|
|
@@ -170,7 +170,11 @@ async function main() {
|
|
|
170
170
|
.action(runCommand({ data: cwdApp }, (opts) => deleteCommand(opts)));
|
|
171
171
|
const backend = program
|
|
172
172
|
.command('backend')
|
|
173
|
-
.description("Inspect the app's backend: invoke a deployed handler by name");
|
|
173
|
+
.description("Inspect the app's backend: validate handler sources or invoke a deployed handler by name");
|
|
174
|
+
backend
|
|
175
|
+
.command('validate')
|
|
176
|
+
.description('Validate backend/handlers/*.js exactly as deploy would (syntax, isolate rules, integrations) without uploading')
|
|
177
|
+
.action(runCommand({ data: cwdApp }, () => backendValidateCommand()));
|
|
174
178
|
backend
|
|
175
179
|
.command('invoke <handlerName>')
|
|
176
180
|
.description('Invoke a deployed backend handler by name (the filename without .ts) — mainly for testing a handler without going through the frontend')
|
|
@@ -2,16 +2,26 @@ import type { ManifestEntry } from '../manifest';
|
|
|
2
2
|
export interface AppInfo {
|
|
3
3
|
appName: string;
|
|
4
4
|
version: number;
|
|
5
|
-
|
|
5
|
+
/** Null for a claimed-but-unpublished app — reserved via `create`, never deployed yet. */
|
|
6
|
+
entry: Record<string, unknown> | null;
|
|
7
|
+
/** True once a real release has landed (`entry` is set) — false for a claimed-but-unpublished stub. */
|
|
8
|
+
deployed: boolean;
|
|
6
9
|
deployedBy: string;
|
|
7
10
|
gitRemote: string;
|
|
8
11
|
createdBy: string;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* Fetches a single app's current state from the broker.
|
|
12
|
-
* Returns null if the app has never been
|
|
15
|
+
* Returns null if the app has never even been claimed (404).
|
|
13
16
|
*/
|
|
14
17
|
export declare function fetchApp(appName: string): Promise<AppInfo | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Reserves an app name (+ owner grant) before any deploy — called once from
|
|
20
|
+
* `create`. Idempotent from the caller's perspective: `create` only calls this
|
|
21
|
+
* when {@link fetchApp} found nothing at all, so a 409 here means someone else
|
|
22
|
+
* claimed the exact same name in the gap between the two calls.
|
|
23
|
+
*/
|
|
24
|
+
export declare function claimApp(appName: string, gitRemote: string): Promise<void>;
|
|
15
25
|
export interface RegisterOrUpdateOptions {
|
|
16
26
|
appName: string;
|
|
17
27
|
token: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../../src/util/broker/app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../../src/util/broker/app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,uGAAuG;IACvG,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAsBvE;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWhF;AA4CD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBtF"}
|
|
@@ -6,7 +6,7 @@ import { tagDeployError, DeployError } from '../errors.mjs';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Fetches a single app's current state from the broker.
|
|
9
|
-
* Returns null if the app has never been
|
|
9
|
+
* Returns null if the app has never even been claimed (404).
|
|
10
10
|
*/
|
|
11
11
|
async function fetchApp(appName) {
|
|
12
12
|
const { token } = await readZthAuth();
|
|
@@ -29,6 +29,24 @@ async function fetchApp(appName) {
|
|
|
29
29
|
}
|
|
30
30
|
return res.json();
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Reserves an app name (+ owner grant) before any deploy — called once from
|
|
34
|
+
* `create`. Idempotent from the caller's perspective: `create` only calls this
|
|
35
|
+
* when {@link fetchApp} found nothing at all, so a 409 here means someone else
|
|
36
|
+
* claimed the exact same name in the gap between the two calls.
|
|
37
|
+
*/
|
|
38
|
+
async function claimApp(appName, gitRemote) {
|
|
39
|
+
const { token } = await readZthAuth();
|
|
40
|
+
const res = await fetch(`${brokerBaseUrl()}/z2h-cli/apps/claim`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...routingHeaders() },
|
|
43
|
+
body: JSON.stringify({ appName, gitRemote }),
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
const text = await res.text();
|
|
47
|
+
throw new Error(`Claiming app name failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
32
50
|
async function registerApp(appName, token, version, entry, visibility, gitRemote, integrations, description) {
|
|
33
51
|
const res = await fetch(`${brokerBaseUrl()}/z2h-cli/apps/register`, {
|
|
34
52
|
method: 'POST',
|
|
@@ -78,4 +96,4 @@ async function registerOrUpdateApp(opts) {
|
|
|
78
96
|
}
|
|
79
97
|
}
|
|
80
98
|
|
|
81
|
-
export { fetchApp, registerOrUpdateApp };
|
|
99
|
+
export { claimApp, fetchApp, registerOrUpdateApp };
|
package/dist/index.js
CHANGED
|
@@ -176,7 +176,11 @@ async function main() {
|
|
|
176
176
|
.action(util_runCommand.runCommand({ data: cwdApp }, (opts) => commands_delete.deleteCommand(opts)));
|
|
177
177
|
const backend = program
|
|
178
178
|
.command('backend')
|
|
179
|
-
.description("Inspect the app's backend: invoke a deployed handler by name");
|
|
179
|
+
.description("Inspect the app's backend: validate handler sources or invoke a deployed handler by name");
|
|
180
|
+
backend
|
|
181
|
+
.command('validate')
|
|
182
|
+
.description('Validate backend/handlers/*.js exactly as deploy would (syntax, isolate rules, integrations) without uploading')
|
|
183
|
+
.action(util_runCommand.runCommand({ data: cwdApp }, () => commands_backend.backendValidateCommand()));
|
|
180
184
|
backend
|
|
181
185
|
.command('invoke <handlerName>')
|
|
182
186
|
.description('Invoke a deployed backend handler by name (the filename without .ts) — mainly for testing a handler without going through the frontend')
|
|
@@ -2,16 +2,26 @@ import type { ManifestEntry } from '../manifest';
|
|
|
2
2
|
export interface AppInfo {
|
|
3
3
|
appName: string;
|
|
4
4
|
version: number;
|
|
5
|
-
|
|
5
|
+
/** Null for a claimed-but-unpublished app — reserved via `create`, never deployed yet. */
|
|
6
|
+
entry: Record<string, unknown> | null;
|
|
7
|
+
/** True once a real release has landed (`entry` is set) — false for a claimed-but-unpublished stub. */
|
|
8
|
+
deployed: boolean;
|
|
6
9
|
deployedBy: string;
|
|
7
10
|
gitRemote: string;
|
|
8
11
|
createdBy: string;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* Fetches a single app's current state from the broker.
|
|
12
|
-
* Returns null if the app has never been
|
|
15
|
+
* Returns null if the app has never even been claimed (404).
|
|
13
16
|
*/
|
|
14
17
|
export declare function fetchApp(appName: string): Promise<AppInfo | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Reserves an app name (+ owner grant) before any deploy — called once from
|
|
20
|
+
* `create`. Idempotent from the caller's perspective: `create` only calls this
|
|
21
|
+
* when {@link fetchApp} found nothing at all, so a 409 here means someone else
|
|
22
|
+
* claimed the exact same name in the gap between the two calls.
|
|
23
|
+
*/
|
|
24
|
+
export declare function claimApp(appName: string, gitRemote: string): Promise<void>;
|
|
15
25
|
export interface RegisterOrUpdateOptions {
|
|
16
26
|
appName: string;
|
|
17
27
|
token: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/util/broker/app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/util/broker/app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,uGAAuG;IACvG,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAsBvE;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWhF;AA4CD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBtF"}
|
package/dist/util/broker/app.js
CHANGED
|
@@ -8,7 +8,7 @@ const util_errors = require('../errors.js');
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Fetches a single app's current state from the broker.
|
|
11
|
-
* Returns null if the app has never been
|
|
11
|
+
* Returns null if the app has never even been claimed (404).
|
|
12
12
|
*/
|
|
13
13
|
async function fetchApp(appName) {
|
|
14
14
|
const { token } = await util_auth_authStore.readZthAuth();
|
|
@@ -31,6 +31,24 @@ async function fetchApp(appName) {
|
|
|
31
31
|
}
|
|
32
32
|
return res.json();
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Reserves an app name (+ owner grant) before any deploy — called once from
|
|
36
|
+
* `create`. Idempotent from the caller's perspective: `create` only calls this
|
|
37
|
+
* when {@link fetchApp} found nothing at all, so a 409 here means someone else
|
|
38
|
+
* claimed the exact same name in the gap between the two calls.
|
|
39
|
+
*/
|
|
40
|
+
async function claimApp(appName, gitRemote) {
|
|
41
|
+
const { token } = await util_auth_authStore.readZthAuth();
|
|
42
|
+
const res = await fetch(`${constants.brokerBaseUrl()}/z2h-cli/apps/claim`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...constants.routingHeaders() },
|
|
45
|
+
body: JSON.stringify({ appName, gitRemote }),
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const text = await res.text();
|
|
49
|
+
throw new Error(`Claiming app name failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
34
52
|
async function registerApp(appName, token, version, entry, visibility, gitRemote, integrations, description) {
|
|
35
53
|
const res = await fetch(`${constants.brokerBaseUrl()}/z2h-cli/apps/register`, {
|
|
36
54
|
method: 'POST',
|
|
@@ -80,5 +98,6 @@ async function registerOrUpdateApp(opts) {
|
|
|
80
98
|
}
|
|
81
99
|
}
|
|
82
100
|
|
|
101
|
+
exports.claimApp = claimApp;
|
|
83
102
|
exports.fetchApp = fetchApp;
|
|
84
103
|
exports.registerOrUpdateApp = registerOrUpdateApp;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mondaydotcomorg/z2h-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"bin": "./bin/z2h-cli.js",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -52,7 +52,9 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@aws-sdk/client-s3": "^3.706.0",
|
|
54
54
|
"@mondaydotcomorg/z2h-dashboard-templates": "^0.3.4",
|
|
55
|
-
"@mondaydotcomorg/z2h-shared-utils": "^0.
|
|
55
|
+
"@mondaydotcomorg/z2h-shared-utils": "^0.6.0",
|
|
56
|
+
"acorn": "^8.14.0",
|
|
57
|
+
"acorn-walk": "^8.3.4",
|
|
56
58
|
"chokidar": "^3.6.0",
|
|
57
59
|
"commander": "^12.1.0",
|
|
58
60
|
"cors": "^2.8.6",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { parse, type ExpressionStatement, type Program } from 'acorn';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { collectBindings, createPathResolver } from '../ast';
|
|
4
|
+
|
|
5
|
+
const program = (source: string): Program => parse(source, { ecmaVersion: 'latest', sourceType: 'script' });
|
|
6
|
+
|
|
7
|
+
/** Resolves the expression in the program's last statement. */
|
|
8
|
+
const resolveLast = (source: string, contextParam?: string) => {
|
|
9
|
+
const p = program(source);
|
|
10
|
+
const last = p.body[p.body.length - 1] as ExpressionStatement;
|
|
11
|
+
return createPathResolver(collectBindings(p), contextParam).pathOf(last.expression);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('createPathResolver', () => {
|
|
15
|
+
it('returns the literal chain when nothing is aliased', () => {
|
|
16
|
+
expect(resolveLast('ctx.api.v1.db.get;')).toBe('ctx.api.v1.db.get');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('treats computed string literals as static keys', () => {
|
|
20
|
+
expect(resolveLast('ctx.api.v1["db"].get;')).toBe('ctx.api.v1.db.get');
|
|
21
|
+
expect(resolveLast("const { ['db']: store } = ctx.api.v1; store.get;")).toBe('ctx.api.v1.db.get');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns undefined for dynamic links and non-identifier roots', () => {
|
|
25
|
+
expect(resolveLast('ctx.api.v1[db].get;')).toBeUndefined();
|
|
26
|
+
expect(resolveLast('fn().db;')).toBeUndefined();
|
|
27
|
+
expect(resolveLast('"x";')).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('rewrites the handler context parameter to ctx', () => {
|
|
31
|
+
expect(resolveLast('context.api.v1.db.get;', 'context')).toBe('ctx.api.v1.db.get');
|
|
32
|
+
expect(resolveLast('const sf = context.api.v1.snowflake; sf.query;', 'context')).toBe('ctx.api.v1.snowflake.query');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('follows const aliases, including destructuring and chains', () => {
|
|
36
|
+
expect(resolveLast('const sf = ctx.api.v1.snowflake; sf.query;')).toBe('ctx.api.v1.snowflake.query');
|
|
37
|
+
expect(resolveLast('const { v1 } = ctx.api; v1.db;')).toBe('ctx.api.v1.db');
|
|
38
|
+
expect(resolveLast('const { v1: { db: store } } = ctx.api; store.get;')).toBe('ctx.api.v1.db.get');
|
|
39
|
+
expect(resolveLast('const a = ctx.api; const b = a.v1; const c = b.db; c;')).toBe('ctx.api.v1.db');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('leaves unpinnable names as written', () => {
|
|
43
|
+
expect(resolveLast('let sf = ctx.api.v1.snowflake; sf.query;')).toBe('sf.query');
|
|
44
|
+
expect(resolveLast('const sf = ctx.api.v1.snowflake; sf = 1; sf.query;')).toBe('sf.query');
|
|
45
|
+
expect(resolveLast('const sf = ctx.api.v1.snowflake; function f(sf) {} sf.query;')).toBe('sf.query');
|
|
46
|
+
expect(resolveLast('const sf = ctx.api.v1.snowflake; { const sf = 1; } sf.query;')).toBe('sf.query');
|
|
47
|
+
expect(resolveLast('const [sf] = ctx.api; sf.query;')).toBe('sf.query');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('does not loop on circular aliases', () => {
|
|
51
|
+
expect(resolveLast('const a = b.x; const b = a.y; a.z;')).toBe('a.y.x.z');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,42 +1,24 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { detectIntegrations } from '../detect-integrations';
|
|
3
3
|
|
|
4
|
-
describe('
|
|
5
|
-
it('returns [] when
|
|
6
|
-
expect(
|
|
4
|
+
describe('detectIntegrations', () => {
|
|
5
|
+
it('returns [] when no domains are touched', () => {
|
|
6
|
+
expect(detectIntegrations([])).toEqual([]);
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
it('
|
|
10
|
-
expect(
|
|
11
|
-
detectIntegrationsFromHandlers({
|
|
12
|
-
ping: 'async function handler(ctx) { return ctx.db.get("x"); }',
|
|
13
|
-
q: 'async function handler(ctx) { return ctx.api.v1.snowflake.runQuery("select 1"); }',
|
|
14
|
-
})
|
|
15
|
-
).toEqual([]);
|
|
9
|
+
it('ignores always-on channels', () => {
|
|
10
|
+
expect(detectIntegrations(['db', 'llm', 'snowflake'])).toEqual([]);
|
|
16
11
|
});
|
|
17
12
|
|
|
18
|
-
it('
|
|
19
|
-
expect(
|
|
20
|
-
detectIntegrationsFromHandlers({
|
|
21
|
-
boards: 'async function handler(ctx) {\n return ctx.api.v1.monday.query("{ boards { id } }");\n}',
|
|
22
|
-
})
|
|
23
|
-
).toEqual(['monday']);
|
|
13
|
+
it('ignores unknown domains', () => {
|
|
14
|
+
expect(detectIntegrations(['totallyMadeUp'])).toEqual([]);
|
|
24
15
|
});
|
|
25
16
|
|
|
26
|
-
it('
|
|
27
|
-
expect(
|
|
28
|
-
detectIntegrationsFromHandlers({
|
|
29
|
-
a: 'ctx.api.v1.monday.query("x")',
|
|
30
|
-
b: 'ctx.api.v1.monday.query("y"); ctx.api.v1.snowflake.runQuery("z")',
|
|
31
|
-
})
|
|
32
|
-
).toEqual(['monday']);
|
|
17
|
+
it('keeps declared integrations', () => {
|
|
18
|
+
expect(detectIntegrations(['snowflake', 'monday', 'db'])).toEqual(['monday']);
|
|
33
19
|
});
|
|
34
20
|
|
|
35
|
-
it('
|
|
36
|
-
expect(
|
|
37
|
-
detectIntegrationsFromHandlers({
|
|
38
|
-
a: 'const monday = 1; api.v1.monday; ctx.api.v2.monday',
|
|
39
|
-
})
|
|
40
|
-
).toEqual([]);
|
|
21
|
+
it('dedupes', () => {
|
|
22
|
+
expect(detectIntegrations(['monday', 'monday'])).toEqual(['monday']);
|
|
41
23
|
});
|
|
42
24
|
});
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { mkdtemp, rm, ensureDir, writeFile } from 'fs-extra';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
6
|
import { readHandlers } from '../handlers';
|
|
7
7
|
|
|
8
|
+
vi.mock('../../util/tracker', () => ({
|
|
9
|
+
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
10
|
+
trackDuration: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
8
13
|
let appDir: string;
|
|
9
14
|
|
|
10
15
|
const writeHandler = (name: string, source: string) =>
|
|
@@ -23,16 +28,14 @@ describe('readHandlers — discovery', () => {
|
|
|
23
28
|
it('returns empty handlers when backend/handlers does not exist', async () => {
|
|
24
29
|
const dir = await mkdtemp(path.join(os.tmpdir(), 'z2h-empty-'));
|
|
25
30
|
try {
|
|
26
|
-
|
|
27
|
-
expect(result.handlers).toEqual({});
|
|
31
|
+
await expect(readHandlers(dir)).resolves.toEqual({ handlers: {}, apiDomains: [] });
|
|
28
32
|
} finally {
|
|
29
33
|
await rm(dir, { recursive: true, force: true });
|
|
30
34
|
}
|
|
31
35
|
});
|
|
32
36
|
|
|
33
37
|
it('returns empty handlers when the directory is empty', async () => {
|
|
34
|
-
|
|
35
|
-
expect(result.handlers).toEqual({});
|
|
38
|
+
await expect(readHandlers(appDir)).resolves.toEqual({ handlers: {}, apiDomains: [] });
|
|
36
39
|
});
|
|
37
40
|
|
|
38
41
|
it('uses the filename (without .js) as the handler name', async () => {
|
|
@@ -57,43 +60,27 @@ describe('readHandlers — output contract', () => {
|
|
|
57
60
|
expect(handlers['my_handler']).toBe(source);
|
|
58
61
|
});
|
|
59
62
|
|
|
60
|
-
it.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
it('merges the ctx.api.v1 domains touched across all handlers, sorted and unique', async () => {
|
|
64
|
+
await writeHandler('a.js', 'async function handler(ctx) { return ctx.api.v1.monday.query("x"); }');
|
|
65
|
+
await writeHandler(
|
|
66
|
+
'b.js',
|
|
67
|
+
'async function handler(ctx) { ctx.api.v1.db.get("k"); return ctx.api.v1.monday.query("y"); }'
|
|
68
|
+
);
|
|
69
|
+
const { apiDomains } = await readHandlers(appDir);
|
|
70
|
+
expect(apiDomains).toEqual(['db', 'monday']);
|
|
67
71
|
});
|
|
68
72
|
});
|
|
69
73
|
|
|
74
|
+
// Per-shape validation rules are covered in validate.test.ts; here we only prove
|
|
75
|
+
// that readHandlers runs the validator and names the offending file.
|
|
70
76
|
describe('readHandlers — validation', () => {
|
|
71
|
-
it('rejects a file that does not define handler', async () => {
|
|
72
|
-
await writeHandler('bad.js', 'async function main(ctx) { return 1; }');
|
|
73
|
-
await expect(readHandlers(appDir)).rejects.toThrow(/must define a handler/);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('rejects import statements', async () => {
|
|
77
|
-
await writeHandler('bad.js', "import { foo } from './utils';\nasync function handler(ctx) { return foo; }");
|
|
78
|
-
await expect(readHandlers(appDir)).rejects.toThrow(/must not import/);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('rejects require calls', async () => {
|
|
82
|
-
await writeHandler('bad.js', "const foo = require('./utils');\nasync function handler(ctx) { return foo; }");
|
|
83
|
-
await expect(readHandlers(appDir)).rejects.toThrow(/must not import/);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
77
|
it('rejects .ts handler files with a clear message', async () => {
|
|
87
78
|
await writeHandler('call_snowflake.ts', 'async function handler(ctx) { return 1; }');
|
|
88
79
|
await expect(readHandlers(appDir)).rejects.toThrow(/TypeScript handlers are not yet supported/);
|
|
89
80
|
});
|
|
90
81
|
|
|
91
|
-
it
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
['a default-exported function', 'export default async function handler(ctx) { return 1; }'],
|
|
95
|
-
])('rejects %s — the isolate has no module system', async (_label, source) => {
|
|
96
|
-
await writeHandler('bad.js', source);
|
|
97
|
-
await expect(readHandlers(appDir)).rejects.toThrow(/must not export handler/);
|
|
82
|
+
it('surfaces validation errors with the filename', async () => {
|
|
83
|
+
await writeHandler('bad.js', 'async function handler(ctx) {\n return ctx.input.(;\n}');
|
|
84
|
+
await expect(readHandlers(appDir)).rejects.toThrow(/^bad\.js:2:19: not valid JavaScript/);
|
|
98
85
|
});
|
|
99
86
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { trackEvent } from '../../util/tracker';
|
|
3
|
+
import { parseOrThrow } from '../parse';
|
|
4
|
+
|
|
5
|
+
vi.mock('../../util/tracker', () => ({
|
|
6
|
+
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
7
|
+
trackDuration: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const track = vi.mocked(trackEvent);
|
|
11
|
+
|
|
12
|
+
describe('parseOrThrow — telemetry', () => {
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
track.mockClear();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('tracks the same message it throws', () => {
|
|
18
|
+
expect(() => parseOrThrow('h.js', 'async function handler(ctx) {\n return ctx.input.(;\n}')).toThrow(
|
|
19
|
+
'h.js:2:19: not valid JavaScript — Unexpected token'
|
|
20
|
+
);
|
|
21
|
+
expect(track).toHaveBeenCalledWith('z2h_cli_handler_parse_error', {
|
|
22
|
+
error: 'h.js:2:19: not valid JavaScript — Unexpected token',
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|