@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.
Files changed (160) hide show
  1. package/dist/backend/handlers.d.ts +3 -0
  2. package/dist/backend/handlers.d.ts.map +1 -1
  3. package/dist/backend/handlers.js +7 -4
  4. package/dist/backend/parser/__tests__/ast.test.d.ts.map +1 -0
  5. package/dist/backend/parser/__tests__/parse.test.d.ts.map +1 -0
  6. package/dist/backend/parser/api-signatures.d.ts +4 -0
  7. package/dist/backend/parser/api-signatures.d.ts.map +1 -0
  8. package/dist/backend/parser/api-signatures.js +31 -0
  9. package/dist/backend/parser/api-usage.d.ts +5 -0
  10. package/dist/backend/parser/api-usage.d.ts.map +1 -0
  11. package/dist/backend/parser/api-usage.js +61 -0
  12. package/dist/backend/parser/bindings.d.ts +5 -0
  13. package/dist/backend/parser/bindings.d.ts.map +1 -0
  14. package/dist/backend/parser/bindings.js +84 -0
  15. package/dist/backend/parser/handler-declaration.d.ts.map +1 -0
  16. package/dist/backend/parser/member-path.d.ts +3 -0
  17. package/dist/backend/parser/member-path.d.ts.map +1 -0
  18. package/dist/backend/parser/member-path.js +31 -0
  19. package/dist/backend/parser/messages.d.ts.map +1 -0
  20. package/dist/backend/parser/parse.d.ts.map +1 -0
  21. package/dist/backend/{parse.js → parser/parse.js} +9 -9
  22. package/dist/backend/parser/static-value.d.ts +6 -0
  23. package/dist/backend/parser/static-value.d.ts.map +1 -0
  24. package/dist/backend/parser/static-value.js +117 -0
  25. package/dist/backend/parser/types.d.ts +53 -0
  26. package/dist/backend/parser/types.d.ts.map +1 -0
  27. package/dist/backend/parser/types.js +1 -0
  28. package/dist/backend/parser/utils.d.ts +19 -0
  29. package/dist/backend/parser/utils.d.ts.map +1 -0
  30. package/dist/backend/parser/utils.js +97 -0
  31. package/dist/backend/validate.d.ts +3 -3
  32. package/dist/backend/validate.d.ts.map +1 -1
  33. package/dist/backend/validate.js +18 -18
  34. package/dist/commands/backend.d.ts +2 -13
  35. package/dist/commands/backend.d.ts.map +1 -1
  36. package/dist/commands/backend.js +30 -21
  37. package/dist/commands/create.d.ts.map +1 -1
  38. package/dist/commands/create.js +1 -0
  39. package/dist/commands/deploy.d.ts.map +1 -1
  40. package/dist/commands/deploy.js +21 -10
  41. package/dist/commands/generate.d.ts.map +1 -1
  42. package/dist/commands/generate.js +6 -0
  43. package/dist/esm/backend/handlers.d.ts +3 -0
  44. package/dist/esm/backend/handlers.d.ts.map +1 -1
  45. package/dist/esm/backend/handlers.mjs +7 -4
  46. package/dist/esm/backend/parser/__tests__/ast.test.d.ts.map +1 -0
  47. package/dist/esm/backend/parser/__tests__/parse.test.d.ts.map +1 -0
  48. package/dist/esm/backend/parser/api-signatures.d.ts +4 -0
  49. package/dist/esm/backend/parser/api-signatures.d.ts.map +1 -0
  50. package/dist/esm/backend/parser/api-signatures.mjs +24 -0
  51. package/dist/esm/backend/parser/api-usage.d.ts +5 -0
  52. package/dist/esm/backend/parser/api-usage.d.ts.map +1 -0
  53. package/dist/esm/backend/parser/api-usage.mjs +58 -0
  54. package/dist/esm/backend/parser/bindings.d.ts +5 -0
  55. package/dist/esm/backend/parser/bindings.d.ts.map +1 -0
  56. package/dist/esm/backend/parser/bindings.mjs +81 -0
  57. package/dist/esm/backend/parser/handler-declaration.d.ts.map +1 -0
  58. package/dist/esm/backend/parser/member-path.d.ts +3 -0
  59. package/dist/esm/backend/parser/member-path.d.ts.map +1 -0
  60. package/dist/esm/backend/parser/member-path.mjs +29 -0
  61. package/dist/esm/backend/parser/messages.d.ts.map +1 -0
  62. package/dist/esm/backend/parser/parse.d.ts.map +1 -0
  63. package/dist/esm/backend/{parse.mjs → parser/parse.mjs} +1 -1
  64. package/dist/esm/backend/parser/static-value.d.ts +6 -0
  65. package/dist/esm/backend/parser/static-value.d.ts.map +1 -0
  66. package/dist/esm/backend/parser/static-value.mjs +114 -0
  67. package/dist/esm/backend/parser/types.d.ts +53 -0
  68. package/dist/esm/backend/parser/types.d.ts.map +1 -0
  69. package/dist/esm/backend/parser/types.mjs +1 -0
  70. package/dist/esm/backend/parser/utils.d.ts +19 -0
  71. package/dist/esm/backend/parser/utils.d.ts.map +1 -0
  72. package/dist/esm/backend/parser/utils.mjs +88 -0
  73. package/dist/esm/backend/validate.d.ts +3 -3
  74. package/dist/esm/backend/validate.d.ts.map +1 -1
  75. package/dist/esm/backend/validate.mjs +8 -8
  76. package/dist/esm/commands/backend.d.ts +2 -13
  77. package/dist/esm/commands/backend.d.ts.map +1 -1
  78. package/dist/esm/commands/backend.mjs +31 -22
  79. package/dist/esm/commands/create.d.ts.map +1 -1
  80. package/dist/esm/commands/create.mjs +1 -0
  81. package/dist/esm/commands/deploy.d.ts.map +1 -1
  82. package/dist/esm/commands/deploy.mjs +23 -11
  83. package/dist/esm/commands/generate.d.ts.map +1 -1
  84. package/dist/esm/commands/generate.mjs +8 -2
  85. package/dist/esm/templates/app/usage.mjs +5 -0
  86. package/dist/esm/util/broker/app.d.ts +2 -0
  87. package/dist/esm/util/broker/app.d.ts.map +1 -1
  88. package/dist/esm/util/broker/app.mjs +4 -4
  89. package/dist/esm/util/machine-identity.d.ts +2 -3
  90. package/dist/esm/util/machine-identity.d.ts.map +1 -1
  91. package/dist/esm/util/machine-identity.mjs +7 -4
  92. package/dist/templates/app/usage.js +7 -0
  93. package/dist/util/broker/app.d.ts +2 -0
  94. package/dist/util/broker/app.d.ts.map +1 -1
  95. package/dist/util/broker/app.js +4 -4
  96. package/dist/util/machine-identity.d.ts +2 -3
  97. package/dist/util/machine-identity.d.ts.map +1 -1
  98. package/dist/util/machine-identity.js +11 -3
  99. package/handler-api/backend-runner/channels/snowflake-channel/run-query.ts +11 -14
  100. package/handler-api/signatures.json +26 -0
  101. package/package.json +1 -1
  102. package/src/backend/__tests__/handlers.test.ts +13 -2
  103. package/src/backend/__tests__/validate.test.ts +54 -0
  104. package/src/backend/handlers.ts +10 -5
  105. package/src/backend/parser/__tests__/ast.test.ts +252 -0
  106. package/src/backend/{__tests__ → parser/__tests__}/parse.test.ts +2 -2
  107. package/src/backend/parser/api-signatures.ts +23 -0
  108. package/src/backend/parser/api-usage.ts +73 -0
  109. package/src/backend/parser/bindings.ts +87 -0
  110. package/src/backend/parser/member-path.ts +31 -0
  111. package/src/backend/{parse.ts → parser/parse.ts} +1 -1
  112. package/src/backend/parser/static-value.ts +145 -0
  113. package/src/backend/parser/types.ts +66 -0
  114. package/src/backend/parser/utils.ts +106 -0
  115. package/src/backend/validate.ts +12 -8
  116. package/src/commands/__tests__/backend-deploy.test.ts +70 -5
  117. package/src/commands/__tests__/backend-validate.test.ts +11 -1
  118. package/src/commands/__tests__/create.test.ts +1 -0
  119. package/src/commands/__tests__/deploy.test.ts +28 -0
  120. package/src/commands/backend.ts +31 -20
  121. package/src/commands/create.ts +1 -0
  122. package/src/commands/deploy.ts +29 -12
  123. package/src/commands/generate.ts +8 -1
  124. package/src/templates/app/usage.json +1 -0
  125. package/src/util/broker/app.ts +17 -4
  126. package/src/util/machine-identity.ts +7 -3
  127. package/dist/backend/__tests__/ast.test.d.ts.map +0 -1
  128. package/dist/backend/__tests__/parse.test.d.ts.map +0 -1
  129. package/dist/backend/ast.d.ts +0 -39
  130. package/dist/backend/ast.d.ts.map +0 -1
  131. package/dist/backend/ast.js +0 -149
  132. package/dist/backend/handler-declaration.d.ts.map +0 -1
  133. package/dist/backend/messages.d.ts.map +0 -1
  134. package/dist/backend/parse.d.ts.map +0 -1
  135. package/dist/esm/backend/__tests__/ast.test.d.ts.map +0 -1
  136. package/dist/esm/backend/__tests__/parse.test.d.ts.map +0 -1
  137. package/dist/esm/backend/ast.d.ts +0 -39
  138. package/dist/esm/backend/ast.d.ts.map +0 -1
  139. package/dist/esm/backend/ast.mjs +0 -144
  140. package/dist/esm/backend/handler-declaration.d.ts.map +0 -1
  141. package/dist/esm/backend/messages.d.ts.map +0 -1
  142. package/dist/esm/backend/parse.d.ts.map +0 -1
  143. package/src/backend/__tests__/ast.test.ts +0 -53
  144. package/src/backend/ast.ts +0 -201
  145. /package/dist/backend/{__tests__ → parser/__tests__}/ast.test.d.ts +0 -0
  146. /package/dist/backend/{__tests__ → parser/__tests__}/parse.test.d.ts +0 -0
  147. /package/dist/backend/{handler-declaration.d.ts → parser/handler-declaration.d.ts} +0 -0
  148. /package/dist/backend/{handler-declaration.js → parser/handler-declaration.js} +0 -0
  149. /package/dist/backend/{messages.d.ts → parser/messages.d.ts} +0 -0
  150. /package/dist/backend/{messages.js → parser/messages.js} +0 -0
  151. /package/dist/backend/{parse.d.ts → parser/parse.d.ts} +0 -0
  152. /package/dist/esm/backend/{__tests__ → parser/__tests__}/ast.test.d.ts +0 -0
  153. /package/dist/esm/backend/{__tests__ → parser/__tests__}/parse.test.d.ts +0 -0
  154. /package/dist/esm/backend/{handler-declaration.d.ts → parser/handler-declaration.d.ts} +0 -0
  155. /package/dist/esm/backend/{handler-declaration.mjs → parser/handler-declaration.mjs} +0 -0
  156. /package/dist/esm/backend/{messages.d.ts → parser/messages.d.ts} +0 -0
  157. /package/dist/esm/backend/{messages.mjs → parser/messages.mjs} +0 -0
  158. /package/dist/esm/backend/{parse.d.ts → parser/parse.d.ts} +0 -0
  159. /package/src/backend/{handler-declaration.ts → parser/handler-declaration.ts} +0 -0
  160. /package/src/backend/{messages.ts → parser/messages.ts} +0 -0
@@ -1,8 +1,10 @@
1
1
  import path from 'node:path';
2
2
  import { existsSync } from 'node:fs';
3
+ import { readJson, writeJson } from 'fs-extra';
3
4
  import { PutObjectCommand } from '@aws-sdk/client-s3';
4
5
  import { info, output } from '../util/logger';
5
6
  import { mfAppName, getCwdAppName } from '../util/app-name';
7
+ import { whoAmI } from '../util/machine-identity';
6
8
  import { readHandlers } from '../backend/handlers';
7
9
  import { detectIntegrations } from '../backend/detect-integrations';
8
10
  import { getS3Client } from '../util/s3/client';
@@ -18,24 +20,36 @@ function currentAppName(): string {
18
20
  return mfAppName(raw);
19
21
  }
20
22
 
21
- /**
22
- * True if the app ships an app-level backend — a `backend/handlers/` directory.
23
- * Callers gate on this so the deploy path is explicit about whether a backend is in
24
- * play.
25
- */
26
23
  export function hasHandlers(appDir: string): boolean {
27
24
  return existsSync(path.join(appDir, 'backend', 'handlers'));
28
25
  }
29
26
 
27
+ /** Other features may add top-level fields, so writes below merge into this copy instead of replacing it. */
28
+ async function readExistingUsage(appDir: string): Promise<Record<string, unknown>> {
29
+ try {
30
+ return (await readJson(path.join(appDir, 'usage.json'))) as Record<string, unknown>;
31
+ } catch (err) {
32
+ if ((err as { code?: string }).code === 'ENOENT') {
33
+ return {};
34
+ }
35
+ throw err;
36
+ }
37
+ }
38
+
39
+ /** Keeps the app's git-tracked `usage.json` current with every handler parse (validate or deploy). */
40
+ async function writeUsageJson(appDir: string, patch: Record<string, unknown>): Promise<void> {
41
+ const existing = await readExistingUsage(appDir);
42
+ await writeJson(path.join(appDir, 'usage.json'), { ...existing, ...patch }, { spaces: 2 });
43
+ }
44
+
30
45
  /**
31
- * Read handlers, detect declared integrations, and upload to `prefix` when there
32
- * is anything to upload. Returns `[]` when there is no `backend/handlers/` dir
33
- * (or it is empty of sources) so callers can always pass the result to
34
- * `registerOrUpdateApp` without a separate gate.
46
+ * Returns `[]` when there is no `backend/handlers/` dir (or it is empty of sources)
47
+ * so callers can always pass the result to `registerOrUpdateApp` without a separate gate.
35
48
  */
36
49
  async function readDetectAndUpload(
37
50
  appDir: string,
38
51
  prefix: string,
52
+ appName: string,
39
53
  version: number,
40
54
  deployedBy: string,
41
55
  logLabel: string
@@ -44,11 +58,12 @@ async function readDetectAndUpload(
44
58
  return [];
45
59
  }
46
60
  info(`reading backend handlers ${logLabel}`);
47
- const { handlers, apiDomains } = await readHandlers(appDir);
61
+ const { handlers, apiDomains, apiUsages } = await readHandlers(appDir);
48
62
  const integrations = detectIntegrations(apiDomains);
49
63
  if (Object.keys(handlers).length === 0) {
50
64
  return integrations;
51
65
  }
66
+ await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
52
67
  const s3 = getS3Client();
53
68
  const meta = JSON.stringify({ version, deployedBy, handlers: Object.keys(handlers) });
54
69
  const put = (key: string, body: string, contentType: string) =>
@@ -65,11 +80,6 @@ async function readDetectAndUpload(
65
80
  * from their sources. Each `.js` handler is uploaded as its own flat file — no
66
81
  * compilation, no bundling, no npm deps.
67
82
  *
68
- * Live and preview differ only in where the handlers land, so that is the only
69
- * thing `isPreview` switches:
70
- * - live: `<appName>/backend-<version>` — the app's immutable per-version slot.
71
- * - preview: `<mfKeyPrefix without /MF>/MS` — beside the preview's `/MF` frontend.
72
- *
73
83
  * Integration sync note: the returned set is re-derived from the CURRENT backend
74
84
  * sources on every deploy, and the platform set is reconciled to exactly match
75
85
  * it (there is no stored declaration to fall back on). A deploy therefore always
@@ -88,25 +98,26 @@ export async function deployBackend(opts: {
88
98
  const prefix = isPreview ? `${mfKeyPrefix.replace(/\/MF$/, '')}/MS` : `${appName}/backend-${version}`;
89
99
  const logLabel = isPreview ? 'for preview' : `for "${appName}"`;
90
100
  try {
91
- return await readDetectAndUpload(appDir, prefix, version, deployedBy, logLabel);
101
+ return await readDetectAndUpload(appDir, prefix, appName, version, deployedBy, logLabel);
92
102
  } catch (err) {
93
103
  throw tagDeployError('deploy_backend_error', err);
94
104
  }
95
105
  }
96
106
 
97
107
  /**
98
- * Run the same handler validation deploy runs (syntax, isolate constraints,
99
- * handler rules, integration detection) against the working tree — without
100
- * uploading anything. Throws on the first invalid handler.
108
+ * Runs the same checks deploy does against the working tree, refreshing the
109
+ * local `usage.json` — no upload to S3. Throws on the first invalid handler.
101
110
  */
102
111
  export async function backendValidateCommand(appDir: string = process.cwd()): Promise<void> {
103
112
  if (!hasHandlers(appDir)) {
104
113
  output('[z2h-cli] no backend/handlers/ directory — nothing to validate', { handlers: [], integrations: [] });
105
114
  return;
106
115
  }
107
- const { handlers, apiDomains } = await readHandlers(appDir);
116
+ const { handlers, apiDomains, apiUsages } = await readHandlers(appDir);
108
117
  const names = Object.keys(handlers).sort();
109
118
  const integrations = detectIntegrations(apiDomains);
119
+ const { name } = (await readJson(path.join(appDir, 'package.json'))) as { name?: string };
120
+ await writeUsageJson(appDir, { appName: name ? mfAppName(name) : undefined, deployedBy: whoAmI(), apiUsages });
110
121
  const list = (xs: string[]) => (xs.length ? xs.join(', ') : 'none');
111
122
  output(`[z2h-cli] ${names.length} handler(s) valid: ${list(names)}\n[z2h-cli] integrations: ${list(integrations)}`, {
112
123
  handlers: names,
@@ -108,6 +108,7 @@ export async function createCommand(appName: string, opts: CreateOptions = {}):
108
108
  await copy(path.join(appTemplatesDir, 'gitignore'), path.join(appDir, '.gitignore'));
109
109
  await copy(path.join(appTemplatesDir, 'claude-settings.json'), path.join(appDir, '.claude', 'settings.json'));
110
110
  await copy(path.join(appTemplatesDir, 'CLAUDE.md'), path.join(appDir, 'CLAUDE.md'));
111
+ await copy(path.join(appTemplatesDir, 'usage.json'), path.join(appDir, 'usage.json'));
111
112
 
112
113
  // html-embed: also copy the source HTML.
113
114
  if (template === 'html-embed' && opts.htmlFile) {
@@ -1,4 +1,3 @@
1
- import os from 'node:os';
2
1
  import path from 'node:path';
3
2
  import { copy, ensureDir, readJson } from 'fs-extra';
4
3
  import { info, warn, output, getOutputMode } from '../util/logger';
@@ -19,8 +18,8 @@ import { getRemoteUrl, guardCleanTree, pullAndResolve, pushToRemote, initRepo }
19
18
  import { resolveGitEnv } from '../util/git/git-env';
20
19
  import { type RemoteType, validateRemoteOptions, resolveRemoteUrl } from '../util/git/remote-options';
21
20
  import { setupS3Client } from '../util/s3/client';
22
- import { getMachineAppName } from '../util/machine-identity';
23
- import { registerOrUpdateApp } from '../util/broker/app';
21
+ import { whoAmI } from '../util/machine-identity';
22
+ import { fetchApp, registerOrUpdateApp } from '../util/broker/app';
24
23
  import { openInBrowser } from '../util/open-browser';
25
24
  import { resolvePortAndMetadata } from '../shadow/prepare';
26
25
  import { DeployError, tagDeployError, grantReasonForStatus } from '../util/errors';
@@ -30,11 +29,6 @@ import { promptForTags } from './tag';
30
29
  import type { ConsumerPaths, DeployFailureReason } from '../types';
31
30
  import type { ManifestEntry, AssetManifestFile } from '../util/manifest';
32
31
 
33
- /** Who is publishing this version — the machine identity, else the OS user. */
34
- function whoDeployed(): string {
35
- return getMachineAppName() ?? os.userInfo().username;
36
- }
37
-
38
32
  export interface DeployOptions {
39
33
  dryRun?: boolean;
40
34
  remote?: RemoteType;
@@ -106,7 +100,7 @@ async function buildAndStamp(opts: {
106
100
  } else {
107
101
  ({ assetManifest } = await buildCommand({ publicUrl: opts.publicUrl }));
108
102
  }
109
- const deployedBy = whoDeployed();
103
+ const deployedBy = whoAmI();
110
104
 
111
105
  return {
112
106
  ...assetManifest,
@@ -132,8 +126,10 @@ async function uploadAndRegister(opts: {
132
126
  gitRemote: string;
133
127
  isPreview: boolean;
134
128
  description?: string;
129
+ masterAppName?: string;
135
130
  }): Promise<void> {
136
- const { paths, keyPrefix, appName, token, nextVersion, entry, gitRemote, isPreview, description } = opts;
131
+ const { paths, keyPrefix, appName, token, nextVersion, entry, gitRemote, isPreview, description, masterAppName } =
132
+ opts;
137
133
 
138
134
  info(`uploading ${isPreview ? 'preview ' : ''}build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
139
135
  await uploadDir(paths.shadowBuild, keyPrefix);
@@ -146,14 +142,34 @@ async function uploadAndRegister(opts: {
146
142
  appName,
147
143
  mfKeyPrefix: keyPrefix,
148
144
  version: nextVersion,
149
- deployedBy: whoDeployed(),
145
+ deployedBy: whoAmI(),
150
146
  isPreview,
151
147
  });
152
148
 
153
- await registerOrUpdateApp({ appName, token, nextVersion, entry, gitRemote, integrations, description });
149
+ await registerOrUpdateApp({
150
+ appName,
151
+ token,
152
+ nextVersion,
153
+ entry,
154
+ gitRemote,
155
+ integrations,
156
+ description,
157
+ masterAppName,
158
+ });
154
159
  }
155
160
 
156
161
  async function deployPreview({ paths, consumerName, appName, token, opts }: DeployContext): Promise<void> {
162
+ // A preview inherits from the live app it previews, resolved by name server-side —
163
+ // fail fast with an actionable message if that app was never claimed/deployed (e.g. a
164
+ // local checkout scaffolded before `create` started claiming names), rather than
165
+ // letting a deep backend 404 surface as a generic "App registration failed".
166
+ if (!(await fetchApp(appName))) {
167
+ throw new DeployError(
168
+ 'register_error',
169
+ `Cannot preview "${appName}": it has never been created or deployed. Run 'z2h-cli create ${consumerName}' (or deploy it once) first.`
170
+ );
171
+ }
172
+
157
173
  await ensureDir(paths.shadowDir);
158
174
  const { previewId } = await resolvePortAndMetadata(paths, cleanAppName(consumerName));
159
175
 
@@ -190,6 +206,7 @@ async function deployPreview({ paths, consumerName, appName, token, opts }: Depl
190
206
  entry,
191
207
  gitRemote: '',
192
208
  isPreview: true,
209
+ masterAppName: appName,
193
210
  });
194
211
 
195
212
  const previewUrl = `https://bigbrain.me/bigbrain-vibe/${previewSlug}`;
@@ -5,7 +5,7 @@ import { execa } from 'execa';
5
5
  import { pathExists, readdir } from 'fs-extra';
6
6
  import { assertWorkspace } from '../util/workspace';
7
7
  import { validateAppName, cleanAppName, mfAppName } from '../util/app-name';
8
- import { fetchApp } from '../util/broker/app';
8
+ import { claimApp, fetchApp } from '../util/broker/app';
9
9
  import { patchBuiltBundle, patchOneFile, type PatchedConfig } from '../util/dashboard-templates/patch-bundle';
10
10
  import { findTemplate, type DashboardTemplateEntry } from '../util/dashboard-templates/load-catalog';
11
11
  import { initRepo, defaultS3Remote } from '../util/git/repo';
@@ -163,6 +163,13 @@ export async function generateCommand(appName: string, opts: GenerateOptions): P
163
163
  throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
164
164
  }
165
165
 
166
+ // generate skips `create` entirely, but the preview it deploys below still resolves this
167
+ // app's own row by name server-side — claim it now so that lookup finds something, same as
168
+ // `create` does for the normal flow.
169
+ if (!existing) {
170
+ await claimApp(mfAppName(appName), defaultS3Remote(appName));
171
+ }
172
+
166
173
  info(`copying template "${template.id}" → ${appDir}`);
167
174
  // Excludes node_modules (installed into templateDir itself by build-templates.mjs, never
168
175
  // needed by the generated app), .build-shadow (a scratch dir that build-templates.mjs
@@ -0,0 +1 @@
1
+ {}
@@ -72,12 +72,13 @@ async function registerApp(
72
72
  visibility: 'public' | 'private',
73
73
  gitRemote: string,
74
74
  integrations?: string[],
75
- description?: string
75
+ description?: string,
76
+ masterAppName?: string
76
77
  ): Promise<void> {
77
78
  const res = await fetch(`${brokerBaseUrl()}/z2h-cli/apps/register`, {
78
79
  method: 'POST',
79
80
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...routingHeaders() },
80
- body: JSON.stringify({ appName, version, entry, visibility, gitRemote, integrations, description }),
81
+ body: JSON.stringify({ appName, version, entry, visibility, gitRemote, integrations, description, masterAppName }),
81
82
  });
82
83
  if (!res.ok) {
83
84
  const text = await res.text();
@@ -114,6 +115,8 @@ export interface RegisterOrUpdateOptions {
114
115
  gitRemote: string;
115
116
  integrations?: string[];
116
117
  description?: string;
118
+ /** For a preview app's first registration, the real app it previews. Ignored past v1. */
119
+ masterAppName?: string;
117
120
  }
118
121
 
119
122
  /**
@@ -123,13 +126,23 @@ export interface RegisterOrUpdateOptions {
123
126
  * deploy and preview so this branching logic lives in exactly one place.
124
127
  */
125
128
  export async function registerOrUpdateApp(opts: RegisterOrUpdateOptions): Promise<void> {
126
- const { appName, token, nextVersion, entry, gitRemote, integrations, description } = opts;
129
+ const { appName, token, nextVersion, entry, gitRemote, integrations, description, masterAppName } = opts;
127
130
 
128
131
  try {
129
132
  if (nextVersion === 1) {
130
133
  const visibility = await resolveAppVisibility(appName);
131
134
  info(`registering "${appName}" → v${nextVersion} (${visibility})`);
132
- await registerApp(appName, token, nextVersion, entry, visibility, gitRemote, integrations, description);
135
+ await registerApp(
136
+ appName,
137
+ token,
138
+ nextVersion,
139
+ entry,
140
+ visibility,
141
+ gitRemote,
142
+ integrations,
143
+ description,
144
+ masterAppName
145
+ );
133
146
  } else {
134
147
  info(`updating "${appName}" → v${nextVersion}`);
135
148
  await updateApp(appName, token, nextVersion, entry, gitRemote, integrations, description);
@@ -1,10 +1,14 @@
1
+ import os from 'node:os';
2
+
1
3
  /**
2
4
  * Returns the machine app name when running as a service (e.g. agent-runner).
3
5
  * Available when the process is a monday.com microservice with `APP_NAME` set
4
- * by the platform runtime.
5
- *
6
- * Returns `undefined` for interactive human users (fallback to os.userInfo).
6
+ * by the platform runtime. Returns `undefined` for interactive human users.
7
7
  */
8
8
  export function getMachineAppName(): string | undefined {
9
9
  return process.env.APP_NAME || undefined;
10
10
  }
11
+
12
+ export function whoAmI(): string {
13
+ return getMachineAppName() ?? os.userInfo().username;
14
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"ast.test.d.ts","sourceRoot":"","sources":["../../../src/backend/__tests__/ast.test.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse.test.d.ts","sourceRoot":"","sources":["../../../src/backend/__tests__/parse.test.ts"],"names":[],"mappings":""}
@@ -1,39 +0,0 @@
1
- import type { AnyNode, Expression, Pattern, Program, VariableDeclaration } from 'acorn';
2
- export interface Declaration {
3
- kind: VariableDeclaration['kind'];
4
- init: Expression | null | undefined;
5
- /**
6
- * Property path from `init` to this binding: `[]` for `const x = init`,
7
- * `['a', 'b']` for `const { a: { b: x } } = init`, `undefined` when the value
8
- * is not a plain property read (array/rest/default patterns).
9
- */
10
- path: string[] | undefined;
11
- }
12
- export interface Bindings {
13
- /** Every `var|let|const <name> = …` in the file, by name. */
14
- declarations: Map<string, Declaration[]>;
15
- /** Names whose value cannot be pinned: reassigned, updated, or bound as parameters. */
16
- unpinnable: Set<string>;
17
- }
18
- /** Every identifier a binding pattern introduces (`{ a, b: [c] } = …` → a, c). */
19
- export declare const patternNames: (pattern: Pattern) => string[];
20
- export declare function collectBindings(program: Program): Bindings;
21
- export interface PathResolver {
22
- /**
23
- * Dotted path of a member chain, following `const` aliases back to their
24
- * source: with `const sf = ctx.api.v1.snowflake`, `sf.query` resolves to
25
- * `ctx.api.v1.snowflake.query`. Destructuring counts too
26
- * (`const { snowflake: sf } = ctx.api.v1`). `undefined` if any link is dynamic.
27
- *
28
- * Only single, never-reassigned, never-shadowed `const` bindings are followed —
29
- * anything else is left as written, so a `let` alias or a parameter of the same
30
- * name is never mistaken for the channel. The handler's first parameter is
31
- * treated as `ctx` even when renamed (`handler(context)` → `context.api.v1.db`
32
- * resolves as `ctx.api.v1.db`).
33
- */
34
- pathOf(node: AnyNode): string | undefined;
35
- }
36
- export declare function createPathResolver(bindings: Bindings, contextParam?: string): PathResolver;
37
- /** Every `ctx.api.v1.<domain>` the program touches (known or not), through `paths`. */
38
- export declare function collectApiDomains(program: Program, paths: PathResolver): Set<string>;
39
- //# sourceMappingURL=ast.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/backend/ast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAKxF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,QAAQ;IACvB,6DAA6D;IAC7D,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACzC,uFAAuF;IACvF,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB;AA8CD,kFAAkF;AAClF,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,MAAM,EAAmD,CAAC;AAE1G,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CA4C1D;AA2BD,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,CA6B1F;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,CAWpF"}
@@ -1,149 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
-
3
- const acornWalk = require('acorn-walk');
4
-
5
- /**
6
- * Every identifier a binding pattern introduces, with its property path from the
7
- * pattern's value. `path` is deliberately not defaulted: callers pass `[]` for
8
- * the root and `undefined` once the pattern stops being a plain property read.
9
- */
10
- function patternBindings(pattern, path) {
11
- switch (pattern.type) {
12
- case 'Identifier':
13
- return [{ name: pattern.name, path }];
14
- case 'ObjectPattern':
15
- return pattern.properties.flatMap(p => {
16
- if (p.type === 'RestElement') {
17
- return patternBindings(p.argument, undefined);
18
- }
19
- const key = propertyKey(p.key, p.computed);
20
- return patternBindings(p.value, path && key !== undefined ? [...path, key] : undefined);
21
- });
22
- case 'ArrayPattern':
23
- return pattern.elements.flatMap(e => (e ? patternBindings(e, undefined) : []));
24
- case 'AssignmentPattern':
25
- return patternBindings(pattern.left, undefined);
26
- case 'RestElement':
27
- return patternBindings(pattern.argument, undefined);
28
- default:
29
- // MemberExpression targets (`obj.x = …`) bind no names.
30
- return [];
31
- }
32
- }
33
- function propertyKey(key, computed) {
34
- if (key.type === 'Literal' && typeof key.value === 'string') {
35
- return key.value;
36
- }
37
- if (!computed && key.type === 'Identifier') {
38
- return key.name;
39
- }
40
- return undefined;
41
- }
42
- /** Every identifier a binding pattern introduces (`{ a, b: [c] } = …` → a, c). */
43
- const patternNames = (pattern) => patternBindings(pattern, []).map(b => b.name);
44
- function collectBindings(program) {
45
- const declarations = new Map();
46
- const unpinnable = new Set();
47
- const markUnpinnable = (pattern) => patternNames(pattern).forEach(n => unpinnable.add(n));
48
- acornWalk.full(program, node => {
49
- switch (node.type) {
50
- case 'VariableDeclaration':
51
- for (const declarator of node.declarations) {
52
- for (const { name, path } of patternBindings(declarator.id, [])) {
53
- const list = declarations.get(name) ?? [];
54
- list.push({ kind: node.kind, init: declarator.init, path });
55
- declarations.set(name, list);
56
- }
57
- }
58
- break;
59
- case 'AssignmentExpression':
60
- markUnpinnable(node.left);
61
- break;
62
- case 'UpdateExpression':
63
- if (node.argument.type === 'Identifier') {
64
- unpinnable.add(node.argument.name);
65
- }
66
- break;
67
- case 'ForInStatement':
68
- case 'ForOfStatement':
69
- if (node.left.type !== 'VariableDeclaration') {
70
- markUnpinnable(node.left);
71
- }
72
- break;
73
- case 'FunctionDeclaration':
74
- case 'FunctionExpression':
75
- case 'ArrowFunctionExpression':
76
- node.params.forEach(markUnpinnable);
77
- break;
78
- case 'CatchClause':
79
- if (node.param) {
80
- markUnpinnable(node.param);
81
- }
82
- break;
83
- }
84
- });
85
- return { declarations, unpinnable };
86
- }
87
- /** `a.b.c` / `a['b'].c` → root `a`, parts `[b, c]`; `undefined` if any link is dynamic or the root is not an identifier. */
88
- function memberChain(node) {
89
- if (node.type === 'ChainExpression') {
90
- return memberChain(node.expression);
91
- }
92
- const parts = [];
93
- let cur = node;
94
- while (cur.type === 'MemberExpression') {
95
- const key = propertyKey(cur.property, cur.computed);
96
- if (key === undefined) {
97
- return undefined;
98
- }
99
- parts.unshift(key);
100
- cur = cur.object;
101
- }
102
- return cur.type === 'Identifier' ? { root: cur.name, parts } : undefined;
103
- }
104
- function createPathResolver(bindings, contextParam) {
105
- function resolveChain(node, resolving) {
106
- const chain = memberChain(node);
107
- if (!chain) {
108
- return undefined;
109
- }
110
- return [resolveRoot(chain.root, resolving), ...chain.parts].join('.');
111
- }
112
- function resolveRoot(name, resolving) {
113
- if (contextParam !== undefined && name === contextParam) {
114
- return 'ctx';
115
- }
116
- if (bindings.unpinnable.has(name) || resolving.has(name)) {
117
- return name;
118
- }
119
- const decls = bindings.declarations.get(name);
120
- if (!decls || decls.length !== 1) {
121
- return name;
122
- }
123
- const [{ kind, init, path }] = decls;
124
- if (kind !== 'const' || !init || !path) {
125
- return name;
126
- }
127
- const base = resolveChain(init, new Set(resolving).add(name));
128
- return base ? [base, ...path].join('.') : name;
129
- }
130
- return { pathOf: node => resolveChain(node, new Set()) };
131
- }
132
- /** Every `ctx.api.v1.<domain>` the program touches (known or not), through `paths`. */
133
- function collectApiDomains(program, paths) {
134
- const apiDomains = new Set();
135
- acornWalk.simple(program, {
136
- MemberExpression(node) {
137
- const [obj, api, version, domain] = paths.pathOf(node)?.split('.') ?? [];
138
- if (obj === 'ctx' && api === 'api' && version === 'v1' && domain) {
139
- apiDomains.add(domain);
140
- }
141
- },
142
- });
143
- return apiDomains;
144
- }
145
-
146
- exports.collectApiDomains = collectApiDomains;
147
- exports.collectBindings = collectBindings;
148
- exports.createPathResolver = createPathResolver;
149
- exports.patternNames = patternNames;
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-declaration.d.ts","sourceRoot":"","sources":["../../src/backend/handler-declaration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,UAAU,EACV,mBAAmB,EAEnB,iBAAiB,EAEjB,SAAS,EACV,MAAM,OAAO,CAAC;AAMf,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,CAAC;AAE9D,8DAA8D;AAC9D,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,CAAC,SAAS,GAAG,iBAAiB,CAAC,EAAE,GAC/C,kBAAkB,GAAG,SAAS,CAahC;AAED,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,iBAAiB,GAAG,OAAO,CAe3E;AAsCD,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAK/C,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/backend/messages.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ;uBACA,MAAM;yBACJ,MAAM;0BACL,MAAM;iCAEC,MAAM;0BAEb,MAAM;6BAEH,MAAM,UAAU,MAAM;wBAE3B,MAAM,QAAQ,MAAM,UAAU,MAAM,UAAU,MAAM;CAEzE,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/backend/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8D,KAAK,OAAO,EAAkB,MAAM,OAAO,CAAC;AAkDjH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAuBhE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ast.test.d.ts","sourceRoot":"","sources":["../../../../src/backend/__tests__/ast.test.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse.test.d.ts","sourceRoot":"","sources":["../../../../src/backend/__tests__/parse.test.ts"],"names":[],"mappings":""}
@@ -1,39 +0,0 @@
1
- import type { AnyNode, Expression, Pattern, Program, VariableDeclaration } from 'acorn';
2
- export interface Declaration {
3
- kind: VariableDeclaration['kind'];
4
- init: Expression | null | undefined;
5
- /**
6
- * Property path from `init` to this binding: `[]` for `const x = init`,
7
- * `['a', 'b']` for `const { a: { b: x } } = init`, `undefined` when the value
8
- * is not a plain property read (array/rest/default patterns).
9
- */
10
- path: string[] | undefined;
11
- }
12
- export interface Bindings {
13
- /** Every `var|let|const <name> = …` in the file, by name. */
14
- declarations: Map<string, Declaration[]>;
15
- /** Names whose value cannot be pinned: reassigned, updated, or bound as parameters. */
16
- unpinnable: Set<string>;
17
- }
18
- /** Every identifier a binding pattern introduces (`{ a, b: [c] } = …` → a, c). */
19
- export declare const patternNames: (pattern: Pattern) => string[];
20
- export declare function collectBindings(program: Program): Bindings;
21
- export interface PathResolver {
22
- /**
23
- * Dotted path of a member chain, following `const` aliases back to their
24
- * source: with `const sf = ctx.api.v1.snowflake`, `sf.query` resolves to
25
- * `ctx.api.v1.snowflake.query`. Destructuring counts too
26
- * (`const { snowflake: sf } = ctx.api.v1`). `undefined` if any link is dynamic.
27
- *
28
- * Only single, never-reassigned, never-shadowed `const` bindings are followed —
29
- * anything else is left as written, so a `let` alias or a parameter of the same
30
- * name is never mistaken for the channel. The handler's first parameter is
31
- * treated as `ctx` even when renamed (`handler(context)` → `context.api.v1.db`
32
- * resolves as `ctx.api.v1.db`).
33
- */
34
- pathOf(node: AnyNode): string | undefined;
35
- }
36
- export declare function createPathResolver(bindings: Bindings, contextParam?: string): PathResolver;
37
- /** Every `ctx.api.v1.<domain>` the program touches (known or not), through `paths`. */
38
- export declare function collectApiDomains(program: Program, paths: PathResolver): Set<string>;
39
- //# sourceMappingURL=ast.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../../src/backend/ast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAKxF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,QAAQ;IACvB,6DAA6D;IAC7D,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACzC,uFAAuF;IACvF,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB;AA8CD,kFAAkF;AAClF,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,MAAM,EAAmD,CAAC;AAE1G,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CA4C1D;AA2BD,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,CA6B1F;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,CAWpF"}