@mondaydotcomorg/z2h-cli 0.29.5 → 0.31.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/handlers.d.ts +3 -0
- package/dist/backend/handlers.d.ts.map +1 -1
- package/dist/backend/handlers.js +7 -4
- package/dist/backend/parser/__tests__/ast.test.d.ts.map +1 -0
- package/dist/backend/parser/__tests__/parse.test.d.ts.map +1 -0
- package/dist/backend/parser/api-signatures.d.ts +4 -0
- package/dist/backend/parser/api-signatures.d.ts.map +1 -0
- package/dist/backend/parser/api-signatures.js +31 -0
- package/dist/backend/parser/api-usage.d.ts +5 -0
- package/dist/backend/parser/api-usage.d.ts.map +1 -0
- package/dist/backend/parser/api-usage.js +61 -0
- package/dist/backend/parser/bindings.d.ts +5 -0
- package/dist/backend/parser/bindings.d.ts.map +1 -0
- package/dist/backend/parser/bindings.js +84 -0
- package/dist/backend/parser/handler-declaration.d.ts.map +1 -0
- package/dist/backend/parser/member-path.d.ts +3 -0
- package/dist/backend/parser/member-path.d.ts.map +1 -0
- package/dist/backend/parser/member-path.js +31 -0
- package/dist/backend/parser/messages.d.ts.map +1 -0
- package/dist/backend/parser/parse.d.ts.map +1 -0
- package/dist/backend/{parse.js → parser/parse.js} +9 -9
- package/dist/backend/parser/static-value.d.ts +6 -0
- package/dist/backend/parser/static-value.d.ts.map +1 -0
- package/dist/backend/parser/static-value.js +117 -0
- package/dist/backend/parser/types.d.ts +53 -0
- package/dist/backend/parser/types.d.ts.map +1 -0
- package/dist/backend/parser/types.js +1 -0
- package/dist/backend/parser/utils.d.ts +19 -0
- package/dist/backend/parser/utils.d.ts.map +1 -0
- package/dist/backend/parser/utils.js +97 -0
- package/dist/backend/validate.d.ts +3 -3
- package/dist/backend/validate.d.ts.map +1 -1
- package/dist/backend/validate.js +18 -18
- package/dist/commands/backend.d.ts +2 -13
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +30 -21
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +1 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +21 -10
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +6 -0
- package/dist/esm/backend/handlers.d.ts +3 -0
- package/dist/esm/backend/handlers.d.ts.map +1 -1
- package/dist/esm/backend/handlers.mjs +7 -4
- package/dist/esm/backend/parser/__tests__/ast.test.d.ts.map +1 -0
- package/dist/esm/backend/parser/__tests__/parse.test.d.ts.map +1 -0
- package/dist/esm/backend/parser/api-signatures.d.ts +4 -0
- package/dist/esm/backend/parser/api-signatures.d.ts.map +1 -0
- package/dist/esm/backend/parser/api-signatures.mjs +24 -0
- package/dist/esm/backend/parser/api-usage.d.ts +5 -0
- package/dist/esm/backend/parser/api-usage.d.ts.map +1 -0
- package/dist/esm/backend/parser/api-usage.mjs +58 -0
- package/dist/esm/backend/parser/bindings.d.ts +5 -0
- package/dist/esm/backend/parser/bindings.d.ts.map +1 -0
- package/dist/esm/backend/parser/bindings.mjs +81 -0
- package/dist/esm/backend/parser/handler-declaration.d.ts.map +1 -0
- package/dist/esm/backend/parser/member-path.d.ts +3 -0
- package/dist/esm/backend/parser/member-path.d.ts.map +1 -0
- package/dist/esm/backend/parser/member-path.mjs +29 -0
- package/dist/esm/backend/parser/messages.d.ts.map +1 -0
- package/dist/esm/backend/parser/parse.d.ts.map +1 -0
- package/dist/esm/backend/{parse.mjs → parser/parse.mjs} +1 -1
- package/dist/esm/backend/parser/static-value.d.ts +6 -0
- package/dist/esm/backend/parser/static-value.d.ts.map +1 -0
- package/dist/esm/backend/parser/static-value.mjs +114 -0
- package/dist/esm/backend/parser/types.d.ts +53 -0
- package/dist/esm/backend/parser/types.d.ts.map +1 -0
- package/dist/esm/backend/parser/types.mjs +1 -0
- package/dist/esm/backend/parser/utils.d.ts +19 -0
- package/dist/esm/backend/parser/utils.d.ts.map +1 -0
- package/dist/esm/backend/parser/utils.mjs +88 -0
- package/dist/esm/backend/validate.d.ts +3 -3
- package/dist/esm/backend/validate.d.ts.map +1 -1
- package/dist/esm/backend/validate.mjs +8 -8
- package/dist/esm/commands/backend.d.ts +2 -13
- package/dist/esm/commands/backend.d.ts.map +1 -1
- package/dist/esm/commands/backend.mjs +31 -22
- package/dist/esm/commands/create.d.ts.map +1 -1
- package/dist/esm/commands/create.mjs +1 -0
- package/dist/esm/commands/deploy.d.ts.map +1 -1
- package/dist/esm/commands/deploy.mjs +23 -11
- package/dist/esm/commands/generate.d.ts.map +1 -1
- package/dist/esm/commands/generate.mjs +8 -2
- package/dist/esm/templates/app/usage.mjs +5 -0
- 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 +4 -4
- package/dist/esm/util/machine-identity.d.ts +2 -3
- package/dist/esm/util/machine-identity.d.ts.map +1 -1
- package/dist/esm/util/machine-identity.mjs +7 -4
- package/dist/templates/app/usage.js +7 -0
- 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 +4 -4
- package/dist/util/machine-identity.d.ts +2 -3
- package/dist/util/machine-identity.d.ts.map +1 -1
- package/dist/util/machine-identity.js +11 -3
- package/handler-api/backend-runner/channels/snowflake-channel/run-query.ts +11 -14
- package/handler-api/signatures.json +26 -0
- package/package.json +1 -1
- package/src/backend/__tests__/handlers.test.ts +13 -2
- package/src/backend/__tests__/validate.test.ts +54 -0
- package/src/backend/handlers.ts +10 -5
- package/src/backend/parser/__tests__/ast.test.ts +252 -0
- package/src/backend/{__tests__ → parser/__tests__}/parse.test.ts +2 -2
- package/src/backend/parser/api-signatures.ts +23 -0
- package/src/backend/parser/api-usage.ts +73 -0
- package/src/backend/parser/bindings.ts +87 -0
- package/src/backend/parser/member-path.ts +31 -0
- package/src/backend/{parse.ts → parser/parse.ts} +1 -1
- package/src/backend/parser/static-value.ts +145 -0
- package/src/backend/parser/types.ts +66 -0
- package/src/backend/parser/utils.ts +106 -0
- package/src/backend/validate.ts +12 -8
- package/src/commands/__tests__/backend-deploy.test.ts +70 -5
- package/src/commands/__tests__/backend-validate.test.ts +11 -1
- package/src/commands/__tests__/create.test.ts +1 -0
- package/src/commands/__tests__/deploy.test.ts +28 -0
- package/src/commands/backend.ts +31 -20
- package/src/commands/create.ts +1 -0
- package/src/commands/deploy.ts +29 -12
- package/src/commands/generate.ts +8 -1
- package/src/templates/app/usage.json +1 -0
- package/src/util/broker/app.ts +17 -4
- package/src/util/machine-identity.ts +7 -3
- package/dist/backend/__tests__/ast.test.d.ts.map +0 -1
- package/dist/backend/__tests__/parse.test.d.ts.map +0 -1
- package/dist/backend/ast.d.ts +0 -39
- package/dist/backend/ast.d.ts.map +0 -1
- package/dist/backend/ast.js +0 -149
- package/dist/backend/handler-declaration.d.ts.map +0 -1
- package/dist/backend/messages.d.ts.map +0 -1
- package/dist/backend/parse.d.ts.map +0 -1
- package/dist/esm/backend/__tests__/ast.test.d.ts.map +0 -1
- package/dist/esm/backend/__tests__/parse.test.d.ts.map +0 -1
- package/dist/esm/backend/ast.d.ts +0 -39
- package/dist/esm/backend/ast.d.ts.map +0 -1
- package/dist/esm/backend/ast.mjs +0 -144
- package/dist/esm/backend/handler-declaration.d.ts.map +0 -1
- package/dist/esm/backend/messages.d.ts.map +0 -1
- package/dist/esm/backend/parse.d.ts.map +0 -1
- package/src/backend/__tests__/ast.test.ts +0 -53
- package/src/backend/ast.ts +0 -201
- /package/dist/backend/{__tests__ → parser/__tests__}/ast.test.d.ts +0 -0
- /package/dist/backend/{__tests__ → parser/__tests__}/parse.test.d.ts +0 -0
- /package/dist/backend/{handler-declaration.d.ts → parser/handler-declaration.d.ts} +0 -0
- /package/dist/backend/{handler-declaration.js → parser/handler-declaration.js} +0 -0
- /package/dist/backend/{messages.d.ts → parser/messages.d.ts} +0 -0
- /package/dist/backend/{messages.js → parser/messages.js} +0 -0
- /package/dist/backend/{parse.d.ts → parser/parse.d.ts} +0 -0
- /package/dist/esm/backend/{__tests__ → parser/__tests__}/ast.test.d.ts +0 -0
- /package/dist/esm/backend/{__tests__ → parser/__tests__}/parse.test.d.ts +0 -0
- /package/dist/esm/backend/{handler-declaration.d.ts → parser/handler-declaration.d.ts} +0 -0
- /package/dist/esm/backend/{handler-declaration.mjs → parser/handler-declaration.mjs} +0 -0
- /package/dist/esm/backend/{messages.d.ts → parser/messages.d.ts} +0 -0
- /package/dist/esm/backend/{messages.mjs → parser/messages.mjs} +0 -0
- /package/dist/esm/backend/{parse.d.ts → parser/parse.d.ts} +0 -0
- /package/src/backend/{handler-declaration.ts → parser/handler-declaration.ts} +0 -0
- /package/src/backend/{messages.ts → parser/messages.ts} +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
// ─────────── Source text ───────────
|
|
4
|
+
const sourceTextOf = (node, source) => source.slice(node.start, node.end);
|
|
5
|
+
function displayText(value) {
|
|
6
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
function resolveOrSourceText(node, source, resolve, fallbackText = n => sourceTextOf(n, source)) {
|
|
9
|
+
const resolved = resolve(node);
|
|
10
|
+
return resolved !== undefined ? resolved.value : fallbackText(node);
|
|
11
|
+
}
|
|
12
|
+
// ─────────── Keys and member chains ───────────
|
|
13
|
+
/** Static property key of `obj.key` / `obj['key']` / `{ key: … }`; `undefined` when computed from a non-literal. */
|
|
14
|
+
function propertyKey(key, computed) {
|
|
15
|
+
if (key.type === 'Literal' && typeof key.value === 'string') {
|
|
16
|
+
return key.value;
|
|
17
|
+
}
|
|
18
|
+
if (!computed && key.type === 'Identifier') {
|
|
19
|
+
return key.name;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
/** `a.b.c` / `a['b'].c` → root `a`, parts `[b, c]`; `undefined` if any link is dynamic or the root is not an identifier. */
|
|
24
|
+
function memberChain(node) {
|
|
25
|
+
if (node.type === 'ChainExpression') {
|
|
26
|
+
return memberChain(node.expression);
|
|
27
|
+
}
|
|
28
|
+
const parts = [];
|
|
29
|
+
let cur = node;
|
|
30
|
+
while (cur.type === 'MemberExpression') {
|
|
31
|
+
const key = propertyKey(cur.property, cur.computed);
|
|
32
|
+
if (key === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
parts.unshift(key);
|
|
36
|
+
cur = cur.object;
|
|
37
|
+
}
|
|
38
|
+
return cur.type === 'Identifier' ? { root: cur.name, parts } : undefined;
|
|
39
|
+
}
|
|
40
|
+
// ─────────── Binding patterns ───────────
|
|
41
|
+
/**
|
|
42
|
+
* Every identifier a binding pattern introduces, with its property path from the
|
|
43
|
+
* pattern's value. `path` is deliberately not defaulted: callers pass `[]` for
|
|
44
|
+
* the root and `undefined` once the pattern stops being a plain property read.
|
|
45
|
+
*/
|
|
46
|
+
function patternBindings(pattern, path) {
|
|
47
|
+
switch (pattern.type) {
|
|
48
|
+
case 'Identifier':
|
|
49
|
+
return [{ name: pattern.name, path }];
|
|
50
|
+
case 'ObjectPattern':
|
|
51
|
+
return pattern.properties.flatMap(p => {
|
|
52
|
+
if (p.type === 'RestElement') {
|
|
53
|
+
return patternBindings(p.argument, undefined);
|
|
54
|
+
}
|
|
55
|
+
const key = propertyKey(p.key, p.computed);
|
|
56
|
+
return patternBindings(p.value, path && key !== undefined ? [...path, key] : undefined);
|
|
57
|
+
});
|
|
58
|
+
case 'ArrayPattern':
|
|
59
|
+
return pattern.elements.flatMap(e => (e ? patternBindings(e, undefined) : []));
|
|
60
|
+
case 'AssignmentPattern':
|
|
61
|
+
return patternBindings(pattern.left, undefined);
|
|
62
|
+
case 'RestElement':
|
|
63
|
+
return patternBindings(pattern.argument, undefined);
|
|
64
|
+
default:
|
|
65
|
+
// MemberExpression targets (`obj.x = …`) bind no names.
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const patternNames = (pattern) => patternBindings(pattern, []).map(b => b.name);
|
|
70
|
+
// ─────────── Scopes ───────────
|
|
71
|
+
const SCOPE_TYPES = new Set([
|
|
72
|
+
'Program',
|
|
73
|
+
'BlockStatement',
|
|
74
|
+
'StaticBlock',
|
|
75
|
+
'SwitchStatement',
|
|
76
|
+
'ForStatement',
|
|
77
|
+
'ForInStatement',
|
|
78
|
+
'ForOfStatement',
|
|
79
|
+
]);
|
|
80
|
+
/** Nearest scope enclosing the last node of `ancestors` (which `fullAncestor` includes in the list). */
|
|
81
|
+
function enclosingScope(ancestors) {
|
|
82
|
+
for (let i = ancestors.length - 2; i > 0; i--) {
|
|
83
|
+
if (SCOPE_TYPES.has(ancestors[i].type)) {
|
|
84
|
+
return ancestors[i];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return ancestors[0];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
exports.displayText = displayText;
|
|
91
|
+
exports.enclosingScope = enclosingScope;
|
|
92
|
+
exports.memberChain = memberChain;
|
|
93
|
+
exports.patternBindings = patternBindings;
|
|
94
|
+
exports.patternNames = patternNames;
|
|
95
|
+
exports.propertyKey = propertyKey;
|
|
96
|
+
exports.resolveOrSourceText = resolveOrSourceText;
|
|
97
|
+
exports.sourceTextOf = sourceTextOf;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { ApiUsage } from './parser/types';
|
|
2
|
+
export type { ApiCall, ApiUsage } from './parser/types';
|
|
1
3
|
export interface HandlerValidation {
|
|
2
4
|
/** Every `ctx.api.v1.<domain>` the handler touches (known or not), sorted and unique. */
|
|
3
5
|
apiDomains: string[];
|
|
6
|
+
apiUsages: ApiUsage;
|
|
4
7
|
}
|
|
5
8
|
/**
|
|
6
9
|
* Validates a handler's source before upload: real parsing (via acorn) catches
|
|
@@ -12,9 +15,6 @@ export interface HandlerValidation {
|
|
|
12
15
|
* Unknown `ctx.api.v1.<domain>` usage is a warning, not a failure: this pass is
|
|
13
16
|
* build-time DX feedback for agent-generated code, never a security boundary —
|
|
14
17
|
* the isolate has no ambient capabilities regardless of what a handler contains.
|
|
15
|
-
*
|
|
16
|
-
* Returns the `ctx.api.v1.*` domains the handler touches so callers (integration
|
|
17
|
-
* detection) can reuse the parse instead of re-scanning the source.
|
|
18
18
|
*/
|
|
19
19
|
export declare function validateHandlerSource(file: string, source: string): HandlerValidation;
|
|
20
20
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/backend/validate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/backend/validate.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAIxD,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAmCrF"}
|
package/dist/backend/validate.js
CHANGED
|
@@ -3,10 +3,12 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
3
3
|
const acornWalk = require('acorn-walk');
|
|
4
4
|
const z2hSharedUtils = require('@mondaydotcomorg/z2h-shared-utils');
|
|
5
5
|
const util_logger = require('../util/logger.js');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
6
|
+
const backend_parser_apiUsage = require('./parser/api-usage.js');
|
|
7
|
+
const backend_parser_bindings = require('./parser/bindings.js');
|
|
8
|
+
const backend_parser_handlerDeclaration = require('./parser/handler-declaration.js');
|
|
9
|
+
const backend_parser_memberPath = require('./parser/member-path.js');
|
|
10
|
+
const backend_parser_messages = require('./parser/messages.js');
|
|
11
|
+
const backend_parser_parse = require('./parser/parse.js');
|
|
10
12
|
|
|
11
13
|
const KNOWN_API_DOMAINS = [...z2hSharedUtils.ALWAYS_ON_CHANNELS, ...z2hSharedUtils.DECLARED_INTEGRATIONS];
|
|
12
14
|
/**
|
|
@@ -19,38 +21,36 @@ const KNOWN_API_DOMAINS = [...z2hSharedUtils.ALWAYS_ON_CHANNELS, ...z2hSharedUti
|
|
|
19
21
|
* Unknown `ctx.api.v1.<domain>` usage is a warning, not a failure: this pass is
|
|
20
22
|
* build-time DX feedback for agent-generated code, never a security boundary —
|
|
21
23
|
* the isolate has no ambient capabilities regardless of what a handler contains.
|
|
22
|
-
*
|
|
23
|
-
* Returns the `ctx.api.v1.*` domains the handler touches so callers (integration
|
|
24
|
-
* detection) can reuse the parse instead of re-scanning the source.
|
|
25
24
|
*/
|
|
26
25
|
function validateHandlerSource(file, source) {
|
|
27
|
-
const program =
|
|
28
|
-
const bindings =
|
|
26
|
+
const program = backend_parser_parse.parseOrThrow(file, source);
|
|
27
|
+
const bindings = backend_parser_bindings.collectBindings(program);
|
|
29
28
|
acornWalk.simple(program, {
|
|
30
29
|
CallExpression(node) {
|
|
31
30
|
if (node.callee.type === 'Identifier' && node.callee.name === 'require') {
|
|
32
|
-
throw new Error(
|
|
31
|
+
throw new Error(backend_parser_messages.messages.mustNotImport(file));
|
|
33
32
|
}
|
|
34
33
|
},
|
|
35
34
|
ImportExpression() {
|
|
36
|
-
throw new Error(
|
|
35
|
+
throw new Error(backend_parser_messages.messages.mustNotImport(file));
|
|
37
36
|
},
|
|
38
37
|
});
|
|
39
|
-
const declaration =
|
|
38
|
+
const declaration = backend_parser_handlerDeclaration.findHandlerDeclaration(program.body);
|
|
40
39
|
if (!declaration) {
|
|
41
|
-
throw new Error(
|
|
40
|
+
throw new Error(backend_parser_messages.messages.mustDefine(file));
|
|
42
41
|
}
|
|
43
|
-
if (declaration.kind === 'variable' && (!declaration.init ||
|
|
44
|
-
throw new Error(
|
|
42
|
+
if (declaration.kind === 'variable' && (!declaration.init || backend_parser_handlerDeclaration.NEVER_A_FUNCTION.has(declaration.init.type))) {
|
|
43
|
+
throw new Error(backend_parser_messages.messages.notAFunction(file));
|
|
45
44
|
}
|
|
46
|
-
const paths =
|
|
47
|
-
const apiDomains =
|
|
45
|
+
const paths = backend_parser_memberPath.createPathResolver(bindings, backend_parser_handlerDeclaration.handlerContextParamName(declaration));
|
|
46
|
+
const apiDomains = backend_parser_apiUsage.collectApiDomains(program, paths);
|
|
47
|
+
const apiUsages = backend_parser_apiUsage.collectApiUsages(program, bindings, paths, source);
|
|
48
48
|
for (const domain of apiDomains) {
|
|
49
49
|
if (!KNOWN_API_DOMAINS.includes(domain)) {
|
|
50
50
|
util_logger.warn(`${file}: ctx.api.v1.${domain} is not a known capability (available: ${KNOWN_API_DOMAINS.join(', ')})`);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
return { apiDomains: [...apiDomains].sort() };
|
|
53
|
+
return { apiDomains: [...apiDomains].sort(), apiUsages };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
exports.validateHandlerSource = validateHandlerSource;
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* True if the app ships an app-level backend — a `backend/handlers/` directory.
|
|
3
|
-
* Callers gate on this so the deploy path is explicit about whether a backend is in
|
|
4
|
-
* play.
|
|
5
|
-
*/
|
|
6
1
|
export declare function hasHandlers(appDir: string): boolean;
|
|
7
2
|
/**
|
|
8
3
|
* Deploy the app's backend handlers to S3 and return the integrations detected
|
|
9
4
|
* from their sources. Each `.js` handler is uploaded as its own flat file — no
|
|
10
5
|
* compilation, no bundling, no npm deps.
|
|
11
6
|
*
|
|
12
|
-
* Live and preview differ only in where the handlers land, so that is the only
|
|
13
|
-
* thing `isPreview` switches:
|
|
14
|
-
* - live: `<appName>/backend-<version>` — the app's immutable per-version slot.
|
|
15
|
-
* - preview: `<mfKeyPrefix without /MF>/MS` — beside the preview's `/MF` frontend.
|
|
16
|
-
*
|
|
17
7
|
* Integration sync note: the returned set is re-derived from the CURRENT backend
|
|
18
8
|
* sources on every deploy, and the platform set is reconciled to exactly match
|
|
19
9
|
* it (there is no stored declaration to fall back on). A deploy therefore always
|
|
@@ -29,9 +19,8 @@ export declare function deployBackend(opts: {
|
|
|
29
19
|
isPreview: boolean;
|
|
30
20
|
}): Promise<string[]>;
|
|
31
21
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* uploading anything. Throws on the first invalid handler.
|
|
22
|
+
* Runs the same checks deploy does against the working tree, refreshing the
|
|
23
|
+
* local `usage.json` — no upload to S3. Throws on the first invalid handler.
|
|
35
24
|
*/
|
|
36
25
|
export declare function backendValidateCommand(appDir?: string): Promise<void>;
|
|
37
26
|
export declare function backendInvokeCommand(handlerName: string, opts?: {
|
|
@@ -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":"AAsBA,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;AAED,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAa5G"}
|
package/dist/commands/backend.js
CHANGED
|
@@ -2,9 +2,11 @@ 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 fs$1 = require('fs-extra');
|
|
5
6
|
const clientS3 = require('@aws-sdk/client-s3');
|
|
6
7
|
const util_logger = require('../util/logger.js');
|
|
7
8
|
const util_appName = require('../util/app-name.js');
|
|
9
|
+
const util_machineIdentity = require('../util/machine-identity.js');
|
|
8
10
|
const backend_handlers = require('../backend/handlers.js');
|
|
9
11
|
const backend_detectIntegrations = require('../backend/detect-integrations.js');
|
|
10
12
|
const util_s3_client = require('../util/s3/client.js');
|
|
@@ -23,30 +25,41 @@ function currentAppName() {
|
|
|
23
25
|
}
|
|
24
26
|
return util_appName.mfAppName(raw);
|
|
25
27
|
}
|
|
26
|
-
/**
|
|
27
|
-
* True if the app ships an app-level backend — a `backend/handlers/` directory.
|
|
28
|
-
* Callers gate on this so the deploy path is explicit about whether a backend is in
|
|
29
|
-
* play.
|
|
30
|
-
*/
|
|
31
28
|
function hasHandlers(appDir) {
|
|
32
29
|
return fs.existsSync(path__default.default.join(appDir, 'backend', 'handlers'));
|
|
33
30
|
}
|
|
31
|
+
/** Other features may add top-level fields, so writes below merge into this copy instead of replacing it. */
|
|
32
|
+
async function readExistingUsage(appDir) {
|
|
33
|
+
try {
|
|
34
|
+
return (await fs$1.readJson(path__default.default.join(appDir, 'usage.json')));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err.code === 'ENOENT') {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Keeps the app's git-tracked `usage.json` current with every handler parse (validate or deploy). */
|
|
44
|
+
async function writeUsageJson(appDir, patch) {
|
|
45
|
+
const existing = await readExistingUsage(appDir);
|
|
46
|
+
await fs$1.writeJson(path__default.default.join(appDir, 'usage.json'), { ...existing, ...patch }, { spaces: 2 });
|
|
47
|
+
}
|
|
34
48
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* (or it is empty of sources) so callers can always pass the result to
|
|
38
|
-
* `registerOrUpdateApp` without a separate gate.
|
|
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.
|
|
39
51
|
*/
|
|
40
|
-
async function readDetectAndUpload(appDir, prefix, version, deployedBy, logLabel) {
|
|
52
|
+
async function readDetectAndUpload(appDir, prefix, appName, version, deployedBy, logLabel) {
|
|
41
53
|
if (!hasHandlers(appDir)) {
|
|
42
54
|
return [];
|
|
43
55
|
}
|
|
44
56
|
util_logger.info(`reading backend handlers ${logLabel}`);
|
|
45
|
-
const { handlers, apiDomains } = await backend_handlers.readHandlers(appDir);
|
|
57
|
+
const { handlers, apiDomains, apiUsages } = await backend_handlers.readHandlers(appDir);
|
|
46
58
|
const integrations = backend_detectIntegrations.detectIntegrations(apiDomains);
|
|
47
59
|
if (Object.keys(handlers).length === 0) {
|
|
48
60
|
return integrations;
|
|
49
61
|
}
|
|
62
|
+
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
50
63
|
const s3 = util_s3_client.getS3Client();
|
|
51
64
|
const meta = JSON.stringify({ version, deployedBy, handlers: Object.keys(handlers) });
|
|
52
65
|
const put = (key, body, contentType) => s3.send(new clientS3.PutObjectCommand({ Bucket: constants.BUCKET_WRITE_NAME, Key: key, Body: body, ContentType: contentType }));
|
|
@@ -61,11 +74,6 @@ async function readDetectAndUpload(appDir, prefix, version, deployedBy, logLabel
|
|
|
61
74
|
* from their sources. Each `.js` handler is uploaded as its own flat file — no
|
|
62
75
|
* compilation, no bundling, no npm deps.
|
|
63
76
|
*
|
|
64
|
-
* Live and preview differ only in where the handlers land, so that is the only
|
|
65
|
-
* thing `isPreview` switches:
|
|
66
|
-
* - live: `<appName>/backend-<version>` — the app's immutable per-version slot.
|
|
67
|
-
* - preview: `<mfKeyPrefix without /MF>/MS` — beside the preview's `/MF` frontend.
|
|
68
|
-
*
|
|
69
77
|
* Integration sync note: the returned set is re-derived from the CURRENT backend
|
|
70
78
|
* sources on every deploy, and the platform set is reconciled to exactly match
|
|
71
79
|
* it (there is no stored declaration to fall back on). A deploy therefore always
|
|
@@ -77,25 +85,26 @@ async function deployBackend(opts) {
|
|
|
77
85
|
const prefix = isPreview ? `${mfKeyPrefix.replace(/\/MF$/, '')}/MS` : `${appName}/backend-${version}`;
|
|
78
86
|
const logLabel = isPreview ? 'for preview' : `for "${appName}"`;
|
|
79
87
|
try {
|
|
80
|
-
return await readDetectAndUpload(appDir, prefix, version, deployedBy, logLabel);
|
|
88
|
+
return await readDetectAndUpload(appDir, prefix, appName, version, deployedBy, logLabel);
|
|
81
89
|
}
|
|
82
90
|
catch (err) {
|
|
83
91
|
throw util_errors.tagDeployError('deploy_backend_error', err);
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* uploading anything. Throws on the first invalid handler.
|
|
95
|
+
* Runs the same checks deploy does against the working tree, refreshing the
|
|
96
|
+
* local `usage.json` — no upload to S3. Throws on the first invalid handler.
|
|
90
97
|
*/
|
|
91
98
|
async function backendValidateCommand(appDir = process.cwd()) {
|
|
92
99
|
if (!hasHandlers(appDir)) {
|
|
93
100
|
util_logger.output('[z2h-cli] no backend/handlers/ directory — nothing to validate', { handlers: [], integrations: [] });
|
|
94
101
|
return;
|
|
95
102
|
}
|
|
96
|
-
const { handlers, apiDomains } = await backend_handlers.readHandlers(appDir);
|
|
103
|
+
const { handlers, apiDomains, apiUsages } = await backend_handlers.readHandlers(appDir);
|
|
97
104
|
const names = Object.keys(handlers).sort();
|
|
98
105
|
const integrations = backend_detectIntegrations.detectIntegrations(apiDomains);
|
|
106
|
+
const { name } = (await fs$1.readJson(path__default.default.join(appDir, 'package.json')));
|
|
107
|
+
await writeUsageJson(appDir, { appName: name ? util_appName.mfAppName(name) : undefined, deployedBy: util_machineIdentity.whoAmI(), apiUsages });
|
|
99
108
|
const list = (xs) => (xs.length ? xs.join(', ') : 'none');
|
|
100
109
|
util_logger.output(`[z2h-cli] ${names.length} handler(s) valid: ${list(names)}\n[z2h-cli] integrations: ${list(integrations)}`, {
|
|
101
110
|
handlers: names,
|
|
@@ -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,CA8F5F"}
|
package/dist/commands/create.js
CHANGED
|
@@ -90,6 +90,7 @@ async function createCommand(appName, opts = {}) {
|
|
|
90
90
|
await fs.copy(path__default.default.join(appTemplatesDir, 'gitignore'), path__default.default.join(appDir, '.gitignore'));
|
|
91
91
|
await fs.copy(path__default.default.join(appTemplatesDir, 'claude-settings.json'), path__default.default.join(appDir, '.claude', 'settings.json'));
|
|
92
92
|
await fs.copy(path__default.default.join(appTemplatesDir, 'CLAUDE.md'), path__default.default.join(appDir, 'CLAUDE.md'));
|
|
93
|
+
await fs.copy(path__default.default.join(appTemplatesDir, 'usage.json'), path__default.default.join(appDir, 'usage.json'));
|
|
93
94
|
// html-embed: also copy the source HTML.
|
|
94
95
|
if (template === 'html-embed' && opts.htmlFile) {
|
|
95
96
|
await fs.copy(opts.htmlFile, path__default.default.join(appDir, 'src', 'page.html'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAatG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
|
-
const os = require('node:os');
|
|
4
3
|
const path = require('node:path');
|
|
5
4
|
const fs = require('fs-extra');
|
|
6
5
|
const util_logger = require('../util/logger.js');
|
|
@@ -26,13 +25,8 @@ const commands_tag = require('./tag.js');
|
|
|
26
25
|
|
|
27
26
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
28
27
|
|
|
29
|
-
const os__default = /*#__PURE__*/_interopDefault(os);
|
|
30
28
|
const path__default = /*#__PURE__*/_interopDefault(path);
|
|
31
29
|
|
|
32
|
-
/** Who is publishing this version — the machine identity, else the OS user. */
|
|
33
|
-
function whoDeployed() {
|
|
34
|
-
return util_machineIdentity.getMachineAppName() ?? os__default.default.userInfo().username;
|
|
35
|
-
}
|
|
36
30
|
// Should be run from the consumer app directory — uses process.cwd() to locate it.
|
|
37
31
|
async function deployCommand(opts = {}) {
|
|
38
32
|
const paths = shadow_paths.resolveConsumerPaths(process.cwd());
|
|
@@ -75,7 +69,7 @@ async function buildAndStamp(opts) {
|
|
|
75
69
|
else {
|
|
76
70
|
({ assetManifest } = await commands_build.buildCommand({ publicUrl: opts.publicUrl }));
|
|
77
71
|
}
|
|
78
|
-
const deployedBy =
|
|
72
|
+
const deployedBy = util_machineIdentity.whoAmI();
|
|
79
73
|
return {
|
|
80
74
|
...assetManifest,
|
|
81
75
|
version: opts.nextVersion,
|
|
@@ -90,7 +84,7 @@ async function buildAndStamp(opts) {
|
|
|
90
84
|
* register the release with the platform in one server-side transaction.
|
|
91
85
|
*/
|
|
92
86
|
async function uploadAndRegister(opts) {
|
|
93
|
-
const { paths, keyPrefix, appName, token, nextVersion, entry, gitRemote, isPreview, description } = opts;
|
|
87
|
+
const { paths, keyPrefix, appName, token, nextVersion, entry, gitRemote, isPreview, description, masterAppName } = opts;
|
|
94
88
|
util_logger.info(`uploading ${isPreview ? 'preview ' : ''}build → s3://${constants.BUCKET_WRITE_NAME}/${keyPrefix}/`);
|
|
95
89
|
await util_s3_upload.uploadDir(paths.shadowBuild, keyPrefix);
|
|
96
90
|
// `integrations` is inferred from the backend sources in the working tree and
|
|
@@ -101,12 +95,28 @@ async function uploadAndRegister(opts) {
|
|
|
101
95
|
appName,
|
|
102
96
|
mfKeyPrefix: keyPrefix,
|
|
103
97
|
version: nextVersion,
|
|
104
|
-
deployedBy:
|
|
98
|
+
deployedBy: util_machineIdentity.whoAmI(),
|
|
105
99
|
isPreview,
|
|
106
100
|
});
|
|
107
|
-
await util_broker_app.registerOrUpdateApp({
|
|
101
|
+
await util_broker_app.registerOrUpdateApp({
|
|
102
|
+
appName,
|
|
103
|
+
token,
|
|
104
|
+
nextVersion,
|
|
105
|
+
entry,
|
|
106
|
+
gitRemote,
|
|
107
|
+
integrations,
|
|
108
|
+
description,
|
|
109
|
+
masterAppName,
|
|
110
|
+
});
|
|
108
111
|
}
|
|
109
112
|
async function deployPreview({ paths, consumerName, appName, token, opts }) {
|
|
113
|
+
// A preview inherits from the live app it previews, resolved by name server-side —
|
|
114
|
+
// fail fast with an actionable message if that app was never claimed/deployed (e.g. a
|
|
115
|
+
// local checkout scaffolded before `create` started claiming names), rather than
|
|
116
|
+
// letting a deep backend 404 surface as a generic "App registration failed".
|
|
117
|
+
if (!(await util_broker_app.fetchApp(appName))) {
|
|
118
|
+
throw new util_errors.DeployError('register_error', `Cannot preview "${appName}": it has never been created or deployed. Run 'z2h-cli create ${consumerName}' (or deploy it once) first.`);
|
|
119
|
+
}
|
|
110
120
|
await fs.ensureDir(paths.shadowDir);
|
|
111
121
|
const { previewId } = await shadow_prepare.resolvePortAndMetadata(paths, util_appName.cleanAppName(consumerName));
|
|
112
122
|
const previewSlug = util_appName.buildPreviewSlug(consumerName, previewId);
|
|
@@ -138,6 +148,7 @@ async function deployPreview({ paths, consumerName, appName, token, opts }) {
|
|
|
138
148
|
entry,
|
|
139
149
|
gitRemote: '',
|
|
140
150
|
isPreview: true,
|
|
151
|
+
masterAppName: appName,
|
|
141
152
|
});
|
|
142
153
|
const previewUrl = `https://bigbrain.me/bigbrain-vibe/${previewSlug}`;
|
|
143
154
|
util_logger.output(`[z2h-cli] preview ready ✅\n app: ${appName}\n previewAppName: ${previewAppName}\n open: ${previewUrl}`, {
|
|
@@ -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,CAqF3F"}
|
|
@@ -141,6 +141,12 @@ async function generateCommand(appName, opts) {
|
|
|
141
141
|
if (existing?.deployed) {
|
|
142
142
|
throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
|
|
143
143
|
}
|
|
144
|
+
// generate skips `create` entirely, but the preview it deploys below still resolves this
|
|
145
|
+
// app's own row by name server-side — claim it now so that lookup finds something, same as
|
|
146
|
+
// `create` does for the normal flow.
|
|
147
|
+
if (!existing) {
|
|
148
|
+
await util_broker_app.claimApp(util_appName.mfAppName(appName), util_git_repo.defaultS3Remote(appName));
|
|
149
|
+
}
|
|
144
150
|
util_logger.info(`copying template "${template.id}" → ${appDir}`);
|
|
145
151
|
// Excludes node_modules (installed into templateDir itself by build-templates.mjs, never
|
|
146
152
|
// needed by the generated app), .build-shadow (a scratch dir that build-templates.mjs
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { type ApiUsage } from './validate';
|
|
1
2
|
export interface ReadHandlersResult {
|
|
2
3
|
/** handler name (filename without .js) → JS source */
|
|
3
4
|
handlers: Record<string, string>;
|
|
4
5
|
/** Every `ctx.api.v1.<domain>` any handler touches, sorted and unique. */
|
|
5
6
|
apiDomains: string[];
|
|
7
|
+
/** handler name → per-call `ctx.api.v1` usage, for `usage.json`. */
|
|
8
|
+
apiUsages: Record<string, ApiUsage>;
|
|
6
9
|
}
|
|
7
10
|
/**
|
|
8
11
|
* Reads every `backend/handlers/*.js` file inside `consumerDir` and returns a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/backend/handlers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/backend/handlers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIlE,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,0EAA0E;IAC1E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA8BnF"}
|
|
@@ -12,27 +12,30 @@ const HANDLERS_DIR = path.join('backend', 'handlers');
|
|
|
12
12
|
async function readHandlers(consumerDir) {
|
|
13
13
|
const handlersDir = path.join(consumerDir, HANDLERS_DIR);
|
|
14
14
|
if (!(await pathExists(handlersDir))) {
|
|
15
|
-
return { handlers: {}, apiDomains: [] };
|
|
15
|
+
return { handlers: {}, apiDomains: [], apiUsages: {} };
|
|
16
16
|
}
|
|
17
17
|
const entries = await readdir(handlersDir);
|
|
18
18
|
const tsFiles = entries.filter(f => f.endsWith('.ts'));
|
|
19
19
|
if (tsFiles.length > 0) {
|
|
20
20
|
throw new Error(`TypeScript handlers are not yet supported (${tsFiles.join(', ')}). Compile to .js first.`);
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
// Sorted so usage.json's key order is deterministic across machines, not readdir()'s.
|
|
23
|
+
const jsFiles = entries.filter(f => f.endsWith('.js')).sort();
|
|
23
24
|
if (jsFiles.length === 0) {
|
|
24
|
-
return { handlers: {}, apiDomains: [] };
|
|
25
|
+
return { handlers: {}, apiDomains: [], apiUsages: {} };
|
|
25
26
|
}
|
|
26
27
|
const handlers = {};
|
|
27
28
|
const apiDomains = new Set();
|
|
29
|
+
const apiUsages = {};
|
|
28
30
|
for (const file of jsFiles) {
|
|
29
31
|
const handlerName = path.parse(file).name;
|
|
30
32
|
const source = await readFile(path.join(handlersDir, file), 'utf8');
|
|
31
33
|
const validation = validateHandlerSource(file, source);
|
|
32
34
|
validation.apiDomains.forEach(d => apiDomains.add(d));
|
|
33
35
|
handlers[handlerName] = source;
|
|
36
|
+
apiUsages[handlerName] = validation.apiUsages;
|
|
34
37
|
}
|
|
35
|
-
return { handlers, apiDomains: [...apiDomains].sort() };
|
|
38
|
+
return { handlers, apiDomains: [...apiDomains].sort(), apiUsages };
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export { readHandlers };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.test.d.ts","sourceRoot":"","sources":["../../../../../src/backend/parser/__tests__/ast.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.test.d.ts","sourceRoot":"","sources":["../../../../../src/backend/parser/__tests__/parse.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-signatures.d.ts","sourceRoot":"","sources":["../../../../src/backend/parser/api-signatures.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAI7C,kGAAkG;AAClG,wBAAgB,iBAAiB,IAAI,aAAa,CAcjD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
let cached;
|
|
6
|
+
/** Derived from the runner's channel docs by `scripts/generate-handler-api.mjs` at build time. */
|
|
7
|
+
function loadApiSignatures() {
|
|
8
|
+
if (cached) {
|
|
9
|
+
return cached;
|
|
10
|
+
}
|
|
11
|
+
const root = path.dirname(createRequire(import.meta.url).resolve('@mondaydotcomorg/z2h-cli/package.json'));
|
|
12
|
+
try {
|
|
13
|
+
cached = JSON.parse(readFileSync(path.join(root, 'handler-api', 'signatures.json'), 'utf8'));
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err.code !== 'ENOENT') {
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
cached = {};
|
|
20
|
+
}
|
|
21
|
+
return cached;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { loadApiSignatures };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Program } from 'acorn';
|
|
2
|
+
import type { ApiSignatures, ApiUsage, Bindings, PathResolver } from './types';
|
|
3
|
+
export declare function collectApiDomains(program: Program, paths: PathResolver): Set<string>;
|
|
4
|
+
export declare function collectApiUsages(program: Program, bindings: Bindings, paths: PathResolver, source: string, signatures?: ApiSignatures): ApiUsage;
|
|
5
|
+
//# sourceMappingURL=api-usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-usage.d.ts","sourceRoot":"","sources":["../../../../src/backend/parser/api-usage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,EAAW,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAcxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,CAWpF;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,aAAmC,GAC9C,QAAQ,CAkCV"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { simple } from 'acorn-walk';
|
|
2
|
+
import { resolveOrSourceText } from './utils.mjs';
|
|
3
|
+
import { bestSourceText, staticValue } from './static-value.mjs';
|
|
4
|
+
import { loadApiSignatures } from './api-signatures.mjs';
|
|
5
|
+
|
|
6
|
+
// ctx.api.v1.<domain>.<method>(...) — the only API shape the runner exposes today
|
|
7
|
+
const API_PREFIX = ['ctx', 'api', 'v1'];
|
|
8
|
+
const API_CALL_MIN_PARTS = API_PREFIX.length + 2;
|
|
9
|
+
function isApiPath(parts) {
|
|
10
|
+
return parts[0] === API_PREFIX[0] && parts[1] === API_PREFIX[1] && parts[2] === API_PREFIX[2];
|
|
11
|
+
}
|
|
12
|
+
function isApiCall(parts) {
|
|
13
|
+
return isApiPath(parts) && parts.length >= API_CALL_MIN_PARTS;
|
|
14
|
+
}
|
|
15
|
+
function collectApiDomains(program, paths) {
|
|
16
|
+
const apiDomains = new Set();
|
|
17
|
+
simple(program, {
|
|
18
|
+
MemberExpression(node) {
|
|
19
|
+
const parts = paths.pathOf(node)?.split('.') ?? [];
|
|
20
|
+
if (isApiPath(parts) && parts[3]) {
|
|
21
|
+
apiDomains.add(parts[3]);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return apiDomains;
|
|
26
|
+
}
|
|
27
|
+
function collectApiUsages(program, bindings, paths, source, signatures = loadApiSignatures()) {
|
|
28
|
+
const apiUsages = {};
|
|
29
|
+
simple(program, {
|
|
30
|
+
CallExpression(node) {
|
|
31
|
+
if (node.callee.type !== 'MemberExpression' && node.callee.type !== 'Identifier') {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const parts = paths.pathOf(node.callee)?.split('.') ?? [];
|
|
35
|
+
if (!isApiCall(parts)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const [, , , domain, ...methodParts] = parts;
|
|
39
|
+
const method = methodParts.join('.');
|
|
40
|
+
const sigKey = `${domain}.${method}`;
|
|
41
|
+
const names = signatures[sigKey];
|
|
42
|
+
if (!names) {
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
console.warn(`[ast] no signature for ${sigKey} — argument names will be positional`);
|
|
45
|
+
}
|
|
46
|
+
const call = {};
|
|
47
|
+
node.arguments.forEach((arg, i) => {
|
|
48
|
+
call[names?.[i] ?? `arg${i}`] = resolveOrSourceText(arg, source, node => staticValue(node, bindings, source, new Set()), node => bestSourceText(node, source, bindings));
|
|
49
|
+
});
|
|
50
|
+
const methods = (apiUsages[domain] ??= {});
|
|
51
|
+
const calls = (methods[method] ??= []);
|
|
52
|
+
calls.push(call);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return apiUsages;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { collectApiDomains, collectApiUsages };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Program } from 'acorn';
|
|
2
|
+
import type { Bindings, Declaration } from './types';
|
|
3
|
+
export declare function collectBindings(program: Program): Bindings;
|
|
4
|
+
export declare function declarationAt(bindings: Bindings, name: string, position: number): Declaration | undefined;
|
|
5
|
+
//# sourceMappingURL=bindings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../../../src/backend/parser/bindings.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA0B,OAAO,EAAE,MAAM,OAAO,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAcrD,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CA2D1D;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAQzG"}
|