@mondaydotcomorg/z2h-cli 0.15.2 → 0.17.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 (100) hide show
  1. package/dist/commands/__tests__/deploy.test.d.ts +2 -0
  2. package/dist/commands/__tests__/deploy.test.d.ts.map +1 -0
  3. package/dist/commands/build.d.ts +8 -1
  4. package/dist/commands/build.d.ts.map +1 -1
  5. package/dist/commands/build.js +12 -0
  6. package/dist/commands/create.d.ts.map +1 -1
  7. package/dist/commands/create.js +1 -12
  8. package/dist/commands/deploy.d.ts +3 -0
  9. package/dist/commands/deploy.d.ts.map +1 -1
  10. package/dist/commands/deploy.js +69 -72
  11. package/dist/commands/doctor.d.ts.map +1 -1
  12. package/dist/commands/doctor.js +1 -11
  13. package/dist/constants.d.ts +2 -0
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/constants.js +10 -5
  16. package/dist/esm/commands/__tests__/deploy.test.d.ts +2 -0
  17. package/dist/esm/commands/__tests__/deploy.test.d.ts.map +1 -0
  18. package/dist/esm/commands/build.d.ts +8 -1
  19. package/dist/esm/commands/build.d.ts.map +1 -1
  20. package/dist/esm/commands/build.mjs +9 -1
  21. package/dist/esm/commands/create.d.ts.map +1 -1
  22. package/dist/esm/commands/create.mjs +2 -13
  23. package/dist/esm/commands/deploy.d.ts +3 -0
  24. package/dist/esm/commands/deploy.d.ts.map +1 -1
  25. package/dist/esm/commands/deploy.mjs +72 -74
  26. package/dist/esm/commands/doctor.d.ts.map +1 -1
  27. package/dist/esm/commands/doctor.mjs +1 -11
  28. package/dist/esm/constants.d.ts +2 -0
  29. package/dist/esm/constants.d.ts.map +1 -1
  30. package/dist/esm/constants.mjs +9 -6
  31. package/dist/esm/index.mjs +9 -15
  32. package/dist/esm/migrations/006-remove-start-script.d.ts +3 -0
  33. package/dist/esm/migrations/006-remove-start-script.d.ts.map +1 -0
  34. package/dist/esm/migrations/006-remove-start-script.mjs +22 -0
  35. package/dist/esm/util/__tests__/app-name.test.d.ts +2 -0
  36. package/dist/esm/util/__tests__/app-name.test.d.ts.map +1 -0
  37. package/dist/esm/util/app-name.d.ts +17 -0
  38. package/dist/esm/util/app-name.d.ts.map +1 -1
  39. package/dist/esm/util/app-name.mjs +45 -1
  40. package/dist/esm/util/auth/broker-credential-provider.d.ts +7 -5
  41. package/dist/esm/util/auth/broker-credential-provider.d.ts.map +1 -1
  42. package/dist/esm/util/auth/broker-credential-provider.mjs +28 -16
  43. package/dist/esm/util/broker/__tests__/app.test.d.ts +2 -0
  44. package/dist/esm/util/broker/__tests__/app.test.d.ts.map +1 -0
  45. package/dist/esm/util/broker/app.d.ts +15 -0
  46. package/dist/esm/util/broker/app.d.ts.map +1 -1
  47. package/dist/esm/util/broker/app.mjs +44 -1
  48. package/dist/esm/util/prompt-visibility.d.ts +2 -0
  49. package/dist/esm/util/prompt-visibility.d.ts.map +1 -0
  50. package/dist/esm/util/prompt-visibility.mjs +19 -0
  51. package/dist/index.js +9 -15
  52. package/dist/migrations/006-remove-start-script.d.ts +3 -0
  53. package/dist/migrations/006-remove-start-script.d.ts.map +1 -0
  54. package/dist/migrations/006-remove-start-script.js +24 -0
  55. package/dist/util/__tests__/app-name.test.d.ts +2 -0
  56. package/dist/util/__tests__/app-name.test.d.ts.map +1 -0
  57. package/dist/util/app-name.d.ts +17 -0
  58. package/dist/util/app-name.d.ts.map +1 -1
  59. package/dist/util/app-name.js +49 -2
  60. package/dist/util/auth/broker-credential-provider.d.ts +7 -5
  61. package/dist/util/auth/broker-credential-provider.d.ts.map +1 -1
  62. package/dist/util/auth/broker-credential-provider.js +28 -16
  63. package/dist/util/broker/__tests__/app.test.d.ts +2 -0
  64. package/dist/util/broker/__tests__/app.test.d.ts.map +1 -0
  65. package/dist/util/broker/app.d.ts +15 -0
  66. package/dist/util/broker/app.d.ts.map +1 -1
  67. package/dist/util/broker/app.js +44 -0
  68. package/dist/util/prompt-visibility.d.ts +2 -0
  69. package/dist/util/prompt-visibility.d.ts.map +1 -0
  70. package/dist/util/prompt-visibility.js +21 -0
  71. package/migrations.json +5 -0
  72. package/package.json +1 -1
  73. package/src/commands/__tests__/create.test.ts +5 -0
  74. package/src/commands/__tests__/deploy.test.ts +215 -0
  75. package/src/commands/__tests__/doctor.test.ts +0 -12
  76. package/src/commands/build.ts +20 -2
  77. package/src/commands/create.ts +2 -18
  78. package/src/commands/deploy.ts +113 -93
  79. package/src/commands/doctor.ts +1 -20
  80. package/src/constants.ts +10 -5
  81. package/src/index.ts +31 -25
  82. package/src/migrations/006-remove-start-script.ts +24 -0
  83. package/src/shadow/__tests__/merge-package-json.test.ts +0 -1
  84. package/src/templates/app/CLAUDE.md +3 -3
  85. package/src/templates/app/package.json.tmpl +0 -1
  86. package/src/templates/workspace/README.md +2 -2
  87. package/src/util/__tests__/app-name.test.ts +78 -0
  88. package/src/util/app-name.ts +55 -0
  89. package/src/util/auth/__tests__/broker-credential-provider.test.ts +53 -1
  90. package/src/util/auth/broker-credential-provider.ts +41 -16
  91. package/src/util/broker/__tests__/app.test.ts +104 -0
  92. package/src/util/broker/app.ts +68 -0
  93. package/src/util/prompt-visibility.ts +18 -0
  94. package/dist/commands/dev.d.ts +0 -5
  95. package/dist/commands/dev.d.ts.map +0 -1
  96. package/dist/commands/dev.js +0 -92
  97. package/dist/esm/commands/dev.d.ts +0 -5
  98. package/dist/esm/commands/dev.d.ts.map +0 -1
  99. package/dist/esm/commands/dev.mjs +0 -85
  100. package/src/commands/dev.ts +0 -93
@@ -1,22 +1,23 @@
1
- import path from 'node:path';
2
1
  import os from 'node:os';
3
- import { createInterface } from 'node:readline';
4
- import { readJson, pathExists } from 'fs-extra';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { readJson } from 'fs-extra';
5
4
  import { info, output, getOutputMode, warn } from '../util/logger.mjs';
6
5
  import { resolveConsumerPaths } from '../shadow/paths.mjs';
7
6
  import { fetchAppTags, fetchTagCatalog, setAppTags } from '../util/tags.mjs';
8
7
  import { uploadDir } from '../util/s3/upload.mjs';
9
- import { BUCKET_WRITE_NAME, brokerBaseUrl, routingHeaders, BUCKET_READ_DNS_URL } from '../constants.mjs';
8
+ import { BUCKET_WRITE_NAME, brokerBaseUrl, routingHeaders, S3_PREVIEWS_FOLDER, BUCKET_READ_DNS_URL } from '../constants.mjs';
10
9
  import { fetchDeployVersion, brokerCredentialProvider } from '../util/auth/broker-credential-provider.mjs';
11
10
  import { readZthAuth } from '../util/auth/auth-store.mjs';
12
- import { mfAppName, cleanAppName } from '../util/app-name.mjs';
13
- import { getMachineAppName } from '../util/machine-identity.mjs';
11
+ import { validateAppName, cleanAppName, mfAppName, buildPreviewSlug } from '../util/app-name.mjs';
14
12
  import { getRemoteUrl, initRepo, guardCleanTree, pullAndResolve, pushToRemote } from '../util/git/repo.mjs';
15
13
  import { resolveGitEnv } from '../util/git/git-env.mjs';
16
14
  import { validateRemoteOptions, resolveRemoteUrl } from '../util/git/remote-options.mjs';
17
15
  import { setupS3Client } from '../util/s3/client.mjs';
18
- import { promptForTags } from './tag.mjs';
16
+ import { getMachineAppName } from '../util/machine-identity.mjs';
17
+ import { registerOrUpdateApp } from '../util/broker/app.mjs';
18
+ import { openInBrowser } from '../util/open-browser.mjs';
19
19
  import { buildCommand } from './build.mjs';
20
+ import { promptForTags } from './tag.mjs';
20
21
 
21
22
  // Should be run from the consumer app directory — uses process.cwd() to locate it.
22
23
  async function deployCommand(opts = {}) {
@@ -25,10 +26,68 @@ async function deployCommand(opts = {}) {
25
26
  if (!consumerPkg.name) {
26
27
  throw new Error('Consumer package.json must have a "name" field');
27
28
  }
29
+ validateAppName(cleanAppName(consumerPkg.name));
28
30
  const appName = mfAppName(consumerPkg.name);
29
31
  // Validate the user's deploy token is on disk before doing any expensive
30
32
  // work. Throws Z2hAuthMissingError with a remediation hint if not.
31
33
  const { token } = await readZthAuth();
34
+ const ctx = { paths, consumerName: consumerPkg.name, appName, token, opts };
35
+ if (opts.preview) {
36
+ await deployPreview(ctx);
37
+ }
38
+ else {
39
+ await deployLive(ctx);
40
+ }
41
+ }
42
+ /**
43
+ * Runs the build step and turns its output into a ManifestEntry ready to
44
+ * register/update — the one step a real deploy and a preview both need
45
+ * identically: build, then stamp version/url/deployedBy/gitRemote onto the
46
+ * asset manifest Trident emitted.
47
+ */
48
+ async function buildAndStamp(opts) {
49
+ const { assetManifest } = await buildCommand({ publicUrl: opts.publicUrl });
50
+ const deployedBy = getMachineAppName() ?? os.userInfo().username;
51
+ return {
52
+ ...assetManifest,
53
+ version: opts.nextVersion,
54
+ url: opts.publicUrl,
55
+ deployedBy,
56
+ gitRemote: opts.gitRemote,
57
+ };
58
+ }
59
+ async function deployPreview({ paths, consumerName, appName, token, opts }) {
60
+ const previewId = opts.previewId ?? randomUUID().slice(0, 8);
61
+ const previewSlug = buildPreviewSlug(consumerName, previewId);
62
+ const previewAppName = mfAppName(previewSlug);
63
+ const keyPrefix = `${S3_PREVIEWS_FOLDER}/${appName}/${previewId}/MF`;
64
+ // Full CDN URL, not a relative path — trident-skeleton uses a manifest
65
+ // entry's `url` verbatim as the asset base, never joined with its own mfUrl.
66
+ const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
67
+ const nextVersion = (await fetchDeployVersion(previewAppName)) + 1;
68
+ info('building');
69
+ const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote: '' });
70
+ const creds = await brokerCredentialProvider({ appName, previewId })();
71
+ setupS3Client(creds);
72
+ info(`uploading preview build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
73
+ await uploadDir(paths.shadowBuild, keyPrefix);
74
+ await registerOrUpdateApp({ appName: previewAppName, token, nextVersion, entry, gitRemote: '' });
75
+ const previewUrl = `https://bigbrain.me/bigbrain-vibe/${previewSlug}`;
76
+ output(`[z2h-cli] preview ready ✅\n app: ${appName}\n previewAppName: ${previewAppName}\n open: ${previewUrl}`, {
77
+ ok: true,
78
+ appName,
79
+ previewId,
80
+ previewAppName,
81
+ bucket: BUCKET_WRITE_NAME,
82
+ keyPrefix,
83
+ url,
84
+ previewUrl,
85
+ });
86
+ if (opts.open) {
87
+ void openInBrowser(previewUrl);
88
+ }
89
+ }
90
+ async function deployLive({ paths, consumerName, appName, token, opts }) {
32
91
  let gitRemote = await getRemoteUrl(process.cwd());
33
92
  if (gitRemote) {
34
93
  if (opts.remote || opts.repoUrl) {
@@ -47,7 +106,7 @@ async function deployCommand(opts = {}) {
47
106
  validateRemoteOptions(opts);
48
107
  // Pass the raw consumer name (not the mf-prefixed appName) so defaultS3Remote
49
108
  // produces the correct s3://bucket/mf-<name>/git path without double prefix.
50
- const remote = resolveRemoteUrl(opts.remote, consumerPkg.name, opts.repoUrl);
109
+ const remote = resolveRemoteUrl(opts.remote, consumerName, opts.repoUrl);
51
110
  info(`initializing git repo with ${opts.remote} remote...`);
52
111
  await initRepo(process.cwd(), remote);
53
112
  gitRemote = remote;
@@ -67,24 +126,7 @@ async function deployCommand(opts = {}) {
67
126
  const keyPrefix = `${appName}/${nextVersion}`;
68
127
  const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
69
128
  info('building');
70
- await buildCommand({ publicUrl: url });
71
- if (!(await pathExists(paths.shadowBuild))) {
72
- throw new Error(`No build output at ${paths.shadowBuild} after build step.`);
73
- }
74
- // Trident emits the canonical per-MF manifest at build/asset-manifest.json.
75
- const assetManifestPath = path.join(paths.shadowBuild, 'asset-manifest.json');
76
- if (!(await pathExists(assetManifestPath))) {
77
- throw new Error(`Missing asset-manifest.json at ${assetManifestPath}.`);
78
- }
79
- const assetManifest = (await readJson(assetManifestPath));
80
- const deployedBy = getMachineAppName() ?? os.userInfo().username;
81
- const entry = {
82
- ...assetManifest,
83
- version: nextVersion,
84
- url,
85
- deployedBy,
86
- gitRemote,
87
- };
129
+ const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote });
88
130
  if (opts.dryRun) {
89
131
  output(`[z2h-cli] dry-run: would upload to s3://${BUCKET_WRITE_NAME}/${keyPrefix}/ as v${version}`, {
90
132
  ok: true,
@@ -114,18 +156,7 @@ async function deployCommand(opts = {}) {
114
156
  }
115
157
  info(`uploading build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
116
158
  await uploadDir(paths.shadowBuild, keyPrefix);
117
- // v1 creates the app row (+ owner grant); later versions release onto it.
118
- // The two are separate endpoints, so the CLI picks based on the version it
119
- // was handed.
120
- if (nextVersion === 1) {
121
- const visibility = await resolveFirstDeployVisibility(appName);
122
- info(`registering app "${appName}" → v${nextVersion} (${visibility})`);
123
- await registerApp(appName, token, nextVersion, entry, visibility, gitRemote);
124
- }
125
- else {
126
- info(`releasing "${appName}" → v${nextVersion}`);
127
- await updateApp(appName, token, nextVersion, entry, gitRemote);
128
- }
159
+ await registerOrUpdateApp({ appName, token, nextVersion, entry, gitRemote });
129
160
  }
130
161
  finally {
131
162
  await releaseLockBestEffort(appName, token);
@@ -136,7 +167,7 @@ async function deployCommand(opts = {}) {
136
167
  // The host MF parses the app name from the URL and prepends `mf-` itself
137
168
  // before looking it up in the manifest. So the user-facing URL must use
138
169
  // the consumer's de-scoped package name (no `mf-` prefix, no `@scope/`).
139
- const liveUrl = `https://bigbrain.me/bigbrain-vibe/${cleanAppName(consumerPkg.name)}`;
170
+ const liveUrl = `https://bigbrain.me/bigbrain-vibe/${cleanAppName(consumerName)}`;
140
171
  output(`[z2h-cli] deployed ✅\n app: ${appName}\n version: ${nextVersion}\n open: ${liveUrl}`, {
141
172
  ok: true,
142
173
  appName,
@@ -147,16 +178,8 @@ async function deployCommand(opts = {}) {
147
178
  liveUrl,
148
179
  entry,
149
180
  });
150
- }
151
- async function registerApp(appName, token, version, entry, visibility, gitRemote) {
152
- const res = await fetch(`${brokerBaseUrl()}/z2h-cli/apps/register`, {
153
- method: 'POST',
154
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...routingHeaders() },
155
- body: JSON.stringify({ appName, version, entry, visibility, gitRemote }),
156
- });
157
- if (!res.ok) {
158
- const text = await res.text();
159
- throw new Error(`App registration failed (${res.status}): ${text.slice(0, 1024)}`);
181
+ if (opts.open) {
182
+ void openInBrowser(liveUrl);
160
183
  }
161
184
  }
162
185
  const TAG_NUDGE = "run 'z2h-cli tag' to categorize this app (app type & team)";
@@ -197,31 +220,6 @@ async function acquireDeployLock(appName, token) {
197
220
  }
198
221
  info(`deploy lock acquired for ${appName}`);
199
222
  }
200
- async function updateApp(appName, token, version, entry, gitRemote) {
201
- const res = await fetch(`${brokerBaseUrl()}/z2h-cli/apps/update`, {
202
- method: 'POST',
203
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...routingHeaders() },
204
- body: JSON.stringify({ appName, version, entry, gitRemote }),
205
- });
206
- if (!res.ok) {
207
- const text = await res.text();
208
- throw new Error(`App update failed (${res.status}): ${text.slice(0, 1024)}`);
209
- }
210
- }
211
- // First deploy only: decide whether the app is born public or private. The
212
- // choice is passed to the register endpoint (default private). Non-interactive
213
- // runs (--json or no TTY) always default to private.
214
- async function resolveFirstDeployVisibility(appName) {
215
- if (getOutputMode() === 'json' || !process.stdout.isTTY) {
216
- return 'private';
217
- }
218
- const rl = createInterface({ input: process.stdin, output: process.stdout });
219
- const answer = await new Promise(resolve => {
220
- rl.question(`Make "${appName}" public to all monday.com employees? (y/N) `, resolve);
221
- });
222
- rl.close();
223
- return answer.trim().toLowerCase() === 'y' ? 'public' : 'private';
224
- }
225
223
  // best-effort — the 2-min TTL backstop handles failures
226
224
  async function releaseLockBestEffort(appName, token) {
227
225
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor.ts"],"names":[],"mappings":"AA2TA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAqEnD"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor.ts"],"names":[],"mappings":"AAwSA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAqEnD"}
@@ -16,7 +16,6 @@ const Z2H_REPO_ROOT = path.join(HOME, 'Development', 'bigbrain-z2h');
16
16
  const BOOTSTRAP_CMD = `bash ${path.join(Z2H_REPO_ROOT, 'z2h/setup/bootstrap.sh')}`;
17
17
  const UPDATE_CMD = `bash ${path.join(Z2H_REPO_ROOT, 'z2h/setup/update.sh')}`;
18
18
  const CLAUDE_PLUGINS_JSON = path.join(HOME, '.claude', 'plugins', 'installed_plugins.json');
19
- const SSL_CERT_PATH = path.join(HOME, 'Library', 'Application Support', 'devcert', 'domains', 'webpack.llama.fan', 'certificate.crt');
20
19
  // ── helpers ───────────────────────────────────────────────────────────────────
21
20
  async function checkTool(label, opts) {
22
21
  const { cmd, args, failStatus = 'fail', getVersion, fix } = opts;
@@ -210,15 +209,6 @@ async function checkReactVersion() {
210
209
  detail: reactVersion,
211
210
  };
212
211
  }
213
- async function checkSslCert() {
214
- const ok = await pathExists(SSL_CERT_PATH);
215
- return {
216
- label: 'SSL cert',
217
- status: ok ? 'ok' : 'warn',
218
- detail: ok ? 'webpack.llama.fan' : 'not found — auto-installs on first z2h-cli dev',
219
- fix: ok ? undefined : 'z2h-cli dev',
220
- };
221
- }
222
212
  async function checkPlugin(pluginKey, label, isMandatory, pluginsJson) {
223
213
  const missingStatus = isMandatory ? 'fail' : 'warn';
224
214
  if (!pluginsJson) {
@@ -265,7 +255,7 @@ async function doctorCommand() {
265
255
  }
266
256
  const systemChecks = await Promise.all([checkNvm(), checkNode(), checkYarn(), checkAws(), checkGitRemoteS3()]);
267
257
  const cliChecks = await Promise.all([checkZ2hCli(), checkAuthToken(), checkNpmTokenInProfile()]);
268
- const workspaceChecks = await Promise.all([checkWorkspace(), checkReactVersion(), checkSslCert()]);
258
+ const workspaceChecks = await Promise.all([checkWorkspace(), checkReactVersion()]);
269
259
  const pluginChecks = await Promise.all([
270
260
  checkPlugin('z2h@bigbrain-z2h', 'z2h plugin', true, pluginsJson),
271
261
  checkPlugin('cf-external-claude-plugin@client-foundations-ai-tools', 'cf-external plugin', false, pluginsJson),
@@ -13,9 +13,11 @@ export declare const REGION = "us-east-1";
13
13
  export declare const BUCKET_WRITE_URL = "https://prod-use1-bigbrain-zth-mf-assets.s3.us-east-1.amazonaws.com";
14
14
  export declare const BUCKET_READ_DNS_NAME = "bigbrain-zth-mf-assets";
15
15
  export declare const BUCKET_READ_DNS_URL = "https://bigbrain-zth-mf-assets.bigbrain.me";
16
+ export declare const S3_PREVIEWS_FOLDER = "previews";
16
17
  export declare const MANIFEST_KEY = "mf-assets";
17
18
  export declare const MANIFEST_READ_URL = "https://bigbrain-zth-mf-assets.bigbrain.me/mf-assets";
18
19
  export declare const APP_NAME_PATTERN: RegExp;
20
+ export declare const PREVIEW_APP_NAME_PREFIX = "zth-preview-";
19
21
  export declare const DEFAULT_BROKER_BASE_URL = "https://bigbrain-zth.bigbrain.me";
20
22
  /**
21
23
  * 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,CAGjD,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,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAKzD,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"}
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;AAIjF,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAK7C,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAEzD,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAKtD,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"}
@@ -6,7 +6,6 @@
6
6
  const SHADOW_DIR_NAME = '.zth';
7
7
  // ─────────── Shadow scripts ───────
8
8
  const SHADOW_SCRIPTS = {
9
- start: 'trident start',
10
9
  build: 'trident build',
11
10
  };
12
11
  // ─────────── Shadow dev deps ───────
@@ -15,10 +14,10 @@ const SHADOW_SCRIPTS = {
15
14
  const SHADOW_DEV_DEPS = {
16
15
  '@mondaydotcomorg/trident-toolkit': '^2.9.25',
17
16
  };
18
- // ─────────── Auto-allocated dev port range ────
19
- // High enough to dodge common dev defaults (3000 Next, 5173 Vite, 8080).
20
- // Collisions within the 7k-port pool are rare for POC; if trident can't
21
- // bind, the user sees a clear bind error from the dev server and re-runs.
17
+ // ─────────── Auto-allocated shadow port range ────
18
+ // Baked into every shadow's .tridentrc.js regardless of whether a dev server
19
+ // ever binds it. High enough to dodge common dev defaults (3000 Next, 5173
20
+ // Vite, 8080). Collisions within the 7k-port pool are rare for POC.
22
21
  const MIN_PORT = 13000;
23
22
  const MAX_PORT = 19999;
24
23
  // ─────────── Gitignore entries auto-appended to the consumer's file ────
@@ -33,6 +32,9 @@ const REGION = 'us-east-1';
33
32
  const BUCKET_WRITE_URL = `https://${BUCKET_WRITE_NAME}.s3.${REGION}.amazonaws.com`;
34
33
  const BUCKET_READ_DNS_NAME = 'bigbrain-zth-mf-assets';
35
34
  const BUCKET_READ_DNS_URL = `https://${BUCKET_READ_DNS_NAME}.bigbrain.me`;
35
+ // Root prefix for ephemeral preview uploads: `previews/<appName>/<previewId>/MF/...`.
36
+ // Shared by the CLI (upload path) and bigbrain-zth (STS policy + manifest overlay).
37
+ const S3_PREVIEWS_FOLDER = 'previews';
36
38
  // No `.json` extension — TridentSkeleton's `mfUrl` API appends `/mf-assets`
37
39
  // to the bucket base when fetching, so the manifest must live at this exact
38
40
  // key. See microfrontends/mf-bigbrain-zth/.../ZeroToHeroHost.tsx.
@@ -43,6 +45,7 @@ const MANIFEST_READ_URL = `${BUCKET_READ_DNS_URL}/${MANIFEST_KEY}`;
43
45
  // backend STS broker rejects anything else with 400; the CLI can validate
44
46
  // upfront for friendlier errors.
45
47
  const APP_NAME_PATTERN = /^[a-z][a-z0-9-]{1,49}$/;
48
+ const PREVIEW_APP_NAME_PREFIX = 'zth-preview-';
46
49
  // ─────────── Broker origin + npm registry (shared §3 contract) ─────
47
50
  // The bigbrain-zth broker fronts both the STS credential route and the npm
48
51
  // proxy. `Z2H_BROKER_BASE_URL` overrides the origin for tests/staging.
@@ -84,4 +87,4 @@ function brokerNpmRegistryUrl() {
84
87
  return `${brokerBaseUrl()}${NPM_BROKER_PATH}`;
85
88
  }
86
89
 
87
- export { APP_NAME_PATTERN, BROKER_NPM_SCOPE, BUCKET_READ_DNS_NAME, BUCKET_READ_DNS_URL, BUCKET_WRITE_NAME, BUCKET_WRITE_URL, DEFAULT_BROKER_BASE_URL, GITIGNORE_ENTRIES, MANIFEST_KEY, MANIFEST_READ_URL, MAX_PORT, MIN_PORT, NPM_BROKER_PATH, PUBLIC_NPM_REGISTRY_URL, REGION, SHADOW_DEV_DEPS, SHADOW_DIR_NAME, SHADOW_SCRIPTS, Z2H_NPM_TOKEN_ENV, brokerBaseUrl, brokerNpmRegistryUrl, routingHeaders };
90
+ export { APP_NAME_PATTERN, BROKER_NPM_SCOPE, BUCKET_READ_DNS_NAME, BUCKET_READ_DNS_URL, BUCKET_WRITE_NAME, BUCKET_WRITE_URL, 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, SHADOW_DEV_DEPS, SHADOW_DIR_NAME, SHADOW_SCRIPTS, Z2H_NPM_TOKEN_ENV, brokerBaseUrl, brokerNpmRegistryUrl, routingHeaders };
@@ -4,7 +4,6 @@ import path from 'node:path';
4
4
  import { Command } from 'commander';
5
5
  import { execa } from 'execa';
6
6
  import { getWorkspaceHome } from './util/workspace.mjs';
7
- import { devCommand } from './commands/dev.mjs';
8
7
  import { buildCommand } from './commands/build.mjs';
9
8
  import { cleanCommand } from './commands/clean.mjs';
10
9
  import { deployCommand } from './commands/deploy.mjs';
@@ -79,18 +78,6 @@ async function main() {
79
78
  await fail(`build failed: ${err.message}`, { command: 'build' }, 2);
80
79
  }
81
80
  });
82
- program
83
- .command('dev')
84
- .description('Start the Trident dev server against .zth/ with native Vite HMR')
85
- .option('--open', 'open the browser automatically when the dev server is ready')
86
- .action(async (opts) => {
87
- try {
88
- await devCommand(opts);
89
- }
90
- catch (err) {
91
- await fail(`dev failed: ${err.message}`, { command: 'dev' }, 2);
92
- }
93
- });
94
81
  program
95
82
  .command('clean')
96
83
  .description('Delete <consumer>/.zth/ and <consumer>/build/')
@@ -106,16 +93,23 @@ async function main() {
106
93
  .command('deploy')
107
94
  .description('Build, upload to S3, and register the app with the Z2H backend')
108
95
  .option('--dry-run', 'print what would happen without uploading anything')
96
+ .option('--preview [previewId]', 'build an ephemeral, shareable preview instead of deploying — no git required, no deploy lock, never touches the live version; pass an existing previewId to update it instead of minting a new one')
97
+ .option('--open', 'open the resulting app/preview URL in the browser on success')
109
98
  .option('--remote <type>', 'First deploy only: source control remote to initialise — s3 (shared storage) or github (your repo). Error if the app already has a git remote.')
110
99
  .option('--repo-url <url>', 'First deploy only: GitHub repo URL — required when --remote=github. Error if the app already has a git remote.')
111
100
  .action(async (opts) => {
112
101
  try {
113
- await trackDuration('z2h_app_deploy_success', () => deployCommand(opts), {
102
+ await trackDuration(opts.preview ? 'z2h_app_preview_success' : 'z2h_app_deploy_success', () => deployCommand({
103
+ ...opts,
104
+ preview: Boolean(opts.preview),
105
+ previewId: typeof opts.preview === 'string' ? opts.preview : undefined,
106
+ }), {
114
107
  app_name: getCwdAppName(),
115
108
  });
116
109
  }
117
110
  catch (err) {
118
- await fail(`deploy failed: ${err.message}`, { command: 'deploy' }, 2);
111
+ const label = opts.preview ? 'preview' : 'deploy';
112
+ await fail(`${label} failed: ${err.message}`, { command: label }, 2);
119
113
  }
120
114
  });
121
115
  program
@@ -0,0 +1,3 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ export default function removeStartScript(tree: Tree): void;
3
+ //# sourceMappingURL=006-remove-start-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"006-remove-start-script.d.ts","sourceRoot":"","sources":["../../../src/migrations/006-remove-start-script.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMvC,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAiB1D"}
@@ -0,0 +1,22 @@
1
+ // `dev` is gone from the CLI (superseded by `deploy --preview`), so any
2
+ // consumer's `start` script still pointing at it is now a dangling command.
3
+ // Only removes the exact value the template ever wrote — leaves a
4
+ // consumer-customized `start` script alone.
5
+ function removeStartScript(tree) {
6
+ for (const entry of tree.children('.')) {
7
+ if (entry.startsWith('.')) {
8
+ continue;
9
+ }
10
+ const pkgPath = `${entry}/package.json`;
11
+ if (!tree.exists(pkgPath)) {
12
+ continue;
13
+ }
14
+ const pkg = JSON.parse(tree.read(pkgPath, 'utf-8'));
15
+ if (pkg.scripts?.start === 'z2h-cli dev') {
16
+ delete pkg.scripts.start;
17
+ tree.write(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
18
+ }
19
+ }
20
+ }
21
+
22
+ export { removeStartScript as default };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=app-name.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-name.test.d.ts","sourceRoot":"","sources":["../../../../src/util/__tests__/app-name.test.ts"],"names":[],"mappings":""}
@@ -10,4 +10,21 @@ export declare function getCwdAppName(): string | undefined;
10
10
  * emitted bundle is `MF-mf-<app>`).
11
11
  */
12
12
  export declare function mfAppName(name: string): string;
13
+ /**
14
+ * Validates a de-scoped, kebab-case app name against every rule shared by
15
+ * `create` and `deploy` (incl. `deploy --preview`) — pattern, reserved
16
+ * platform names, and the preview-prefix reservation. Throws with a
17
+ * friendly message on the first violation.
18
+ */
19
+ export declare function validateAppName(appName: string): void;
20
+ /**
21
+ * Builds the slug for a preview's synthetic RDS app row:
22
+ * `zth-preview-<shortid>-<realAppName>`, pre-`mf-`-prefixing. Uniqueness
23
+ * comes entirely from `shortId` (derived from the previewId, already a
24
+ * randomUUID() — 32 bits of entropy); truncating the human-readable tail to
25
+ * fit the cap is safe on its own, a shortId collision is astronomically
26
+ * unlikely at POC scale and would just 409 on registerApp for a retry.
27
+ */
28
+ export declare function buildPreviewSlug(appName: string, previewId: string): string;
29
+ export declare function isPreviewAppName(appName: string): boolean;
13
30
  //# sourceMappingURL=app-name.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-name.d.ts","sourceRoot":"","sources":["../../../src/util/app-name.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAMlD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}
1
+ {"version":3,"file":"app-name.d.ts","sourceRoot":"","sources":["../../../src/util/app-name.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAMlD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAOD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAiBrD;AAMD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM3E;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD"}
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { APP_NAME_PATTERN, PREVIEW_APP_NAME_PREFIX } from '../constants.mjs';
2
3
 
3
4
  /**
4
5
  * Strip any npm scope (`@scope/`) from a package name.
@@ -23,5 +24,48 @@ function getCwdAppName() {
23
24
  function mfAppName(name) {
24
25
  return `mf-${cleanAppName(name)}`;
25
26
  }
27
+ // Names reserved by the host MF (mf-bigbrain-zth) for platform pages under
28
+ // /bigbrain-vibe/<name>. Creating an app with one of these would shadow the
29
+ // platform page in the URL, so we refuse upfront.
30
+ const RESERVED_APP_NAMES = new Set(['generate-z2h-token']);
31
+ /**
32
+ * Validates a de-scoped, kebab-case app name against every rule shared by
33
+ * `create` and `deploy` (incl. `deploy --preview`) — pattern, reserved
34
+ * platform names, and the preview-prefix reservation. Throws with a
35
+ * friendly message on the first violation.
36
+ */
37
+ function validateAppName(appName) {
38
+ if (RESERVED_APP_NAMES.has(appName)) {
39
+ throw new Error(`App name "${appName}" is reserved by the Z2H host for a platform page under /bigbrain-vibe/${appName}. Pick a different name.`);
40
+ }
41
+ if (!APP_NAME_PATTERN.test(appName)) {
42
+ throw new Error(`App name "${appName}" is invalid. Use lowercase kebab-case (a-z, 0-9, -), ` +
43
+ `2-50 chars, starting with a letter.`);
44
+ }
45
+ if (appName.startsWith(PREVIEW_APP_NAME_PREFIX)) {
46
+ throw new Error(`App names starting with "${PREVIEW_APP_NAME_PREFIX}" are reserved for the ephemeral preview mechanism. Pick a different name.`);
47
+ }
48
+ }
49
+ // Preview slugs are capped here so the mf-prefixed name (`mf-` + slug) stays
50
+ // within APP_NAME_PATTERN's 50-char limit (3 chars reserved for `mf-`).
51
+ const PREVIEW_SLUG_MAX_LEN = 47;
52
+ /**
53
+ * Builds the slug for a preview's synthetic RDS app row:
54
+ * `zth-preview-<shortid>-<realAppName>`, pre-`mf-`-prefixing. Uniqueness
55
+ * comes entirely from `shortId` (derived from the previewId, already a
56
+ * randomUUID() — 32 bits of entropy); truncating the human-readable tail to
57
+ * fit the cap is safe on its own, a shortId collision is astronomically
58
+ * unlikely at POC scale and would just 409 on registerApp for a retry.
59
+ */
60
+ function buildPreviewSlug(appName, previewId) {
61
+ const shortId = previewId.replace(/-/g, '').slice(0, 8);
62
+ const base = `${PREVIEW_APP_NAME_PREFIX}${shortId}-${cleanAppName(appName)}`;
63
+ // Trailing hyphens are stripped post-slice — a cut that lands right after a
64
+ // hyphen in appName would otherwise leave one dangling.
65
+ return base.slice(0, PREVIEW_SLUG_MAX_LEN).replace(/-+$/, '');
66
+ }
67
+ function isPreviewAppName(appName) {
68
+ return appName.startsWith(`mf-${PREVIEW_APP_NAME_PREFIX}`);
69
+ }
26
70
 
27
- export { cleanAppName, getCwdAppName, mfAppName };
71
+ export { buildPreviewSlug, cleanAppName, getCwdAppName, isPreviewAppName, mfAppName, validateAppName };
@@ -8,12 +8,14 @@ export declare const DEPLOY_VERSION_PATH = "/z2h-cli/apps/version";
8
8
  */
9
9
  export declare function fetchDeployVersion(appName: string): Promise<number>;
10
10
  /**
11
- * AWS SDK credential provider that fetches short-lived deploy credentials
12
- * from the bigbrain-zth STS broker for a specific version. Credentials are
13
- * cached in memory until 5 minutes before expiry; concurrent calls share a
14
- * single in-flight fetch.
11
+ * AWS SDK credential provider that fetches short-lived credentials from the
12
+ * bigbrain-zth STS broker, scoped to `appName` — or, when `previewId` is
13
+ * given, to a single `previews/<appName>/<previewId>/*` slot instead of the
14
+ * full deploy path. Credentials are cached in memory until 5 minutes before
15
+ * expiry; concurrent calls share a single in-flight fetch.
15
16
  */
16
- export declare function brokerCredentialProvider({ appName }: {
17
+ export declare function brokerCredentialProvider({ appName, previewId, }: {
17
18
  appName: string;
19
+ previewId?: string;
18
20
  }): AwsCredentialIdentityProvider;
19
21
  //# sourceMappingURL=broker-credential-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"broker-credential-provider.d.ts","sourceRoot":"","sources":["../../../../src/util/auth/broker-credential-provider.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAyB,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAE3F,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAC1D,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AA4C3D;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUzE;AAuBD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,6BAA6B,CAuBxG"}
1
+ {"version":3,"file":"broker-credential-provider.d.ts","sourceRoot":"","sources":["../../../../src/util/auth/broker-credential-provider.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAyB,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAE3F,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAC1D,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AA4C3D;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUzE;AA4DD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,SAAS,GACV,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,6BAA6B,CAIhC"}
@@ -45,31 +45,33 @@ async function fetchDeployVersion(appName) {
45
45
  const body = (await res.json());
46
46
  return body.version ?? 0;
47
47
  }
48
- async function fetchStsOnce(appName) {
49
- const res = await postToBroker(STS_BROKER_PATH, { appName });
48
+ async function requestSts(path, body, errorLabel) {
49
+ const res = await postToBroker(path, body);
50
50
  assertBrokerOk(res);
51
51
  if (!res.ok) {
52
52
  const text = await res.text();
53
- throw new Error(`Z2H deploy broker returned ${res.status}: ${text.slice(0, 1024)}`);
53
+ throw new Error(`Z2H ${errorLabel} broker returned ${res.status}: ${text.slice(0, 1024)}`);
54
54
  }
55
- const body = (await res.json());
56
- if (!body.accessKeyId || !body.secretAccessKey || !body.sessionToken || !body.expiration) {
57
- throw new Error('Z2H deploy broker response missing required credential fields');
55
+ const responseBody = (await res.json());
56
+ if (!responseBody.accessKeyId ||
57
+ !responseBody.secretAccessKey ||
58
+ !responseBody.sessionToken ||
59
+ !responseBody.expiration) {
60
+ throw new Error(`Z2H ${errorLabel} broker response missing required credential fields`);
58
61
  }
59
62
  return {
60
- accessKeyId: body.accessKeyId,
61
- secretAccessKey: body.secretAccessKey,
62
- sessionToken: body.sessionToken,
63
- expiration: new Date(body.expiration),
63
+ accessKeyId: responseBody.accessKeyId,
64
+ secretAccessKey: responseBody.secretAccessKey,
65
+ sessionToken: responseBody.sessionToken,
66
+ expiration: new Date(responseBody.expiration),
64
67
  };
65
68
  }
66
69
  /**
67
- * AWS SDK credential provider that fetches short-lived deploy credentials
68
- * from the bigbrain-zth STS broker for a specific version. Credentials are
69
- * cached in memory until 5 minutes before expiry; concurrent calls share a
70
- * single in-flight fetch.
70
+ * Wraps a raw STS fetch in the shared cache/coalescing state machine:
71
+ * credentials are cached in memory until 5 minutes before expiry, and
72
+ * concurrent calls share a single in-flight fetch.
71
73
  */
72
- function brokerCredentialProvider({ appName }) {
74
+ function createCachedCredentialProvider(fetchOnce) {
73
75
  let cached;
74
76
  let inflight;
75
77
  return async () => {
@@ -80,7 +82,7 @@ function brokerCredentialProvider({ appName }) {
80
82
  if (inflight) {
81
83
  return inflight;
82
84
  }
83
- inflight = fetchStsOnce(appName)
85
+ inflight = fetchOnce()
84
86
  .then(credentials => {
85
87
  cached = credentials;
86
88
  return credentials;
@@ -91,5 +93,15 @@ function brokerCredentialProvider({ appName }) {
91
93
  return inflight;
92
94
  };
93
95
  }
96
+ /**
97
+ * AWS SDK credential provider that fetches short-lived credentials from the
98
+ * bigbrain-zth STS broker, scoped to `appName` — or, when `previewId` is
99
+ * given, to a single `previews/<appName>/<previewId>/*` slot instead of the
100
+ * full deploy path. Credentials are cached in memory until 5 minutes before
101
+ * expiry; concurrent calls share a single in-flight fetch.
102
+ */
103
+ function brokerCredentialProvider({ appName, previewId, }) {
104
+ return createCachedCredentialProvider(() => requestSts(STS_BROKER_PATH, { appName, previewId }, previewId ? 'preview' : 'deploy'));
105
+ }
94
106
 
95
107
  export { DEPLOY_VERSION_PATH, STS_BROKER_PATH, brokerCredentialProvider, fetchDeployVersion };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=app.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.test.d.ts","sourceRoot":"","sources":["../../../../../src/util/broker/__tests__/app.test.ts"],"names":[],"mappings":""}
@@ -1,3 +1,4 @@
1
+ import type { ManifestEntry } from '../manifest';
1
2
  export interface AppInfo {
2
3
  appName: string;
3
4
  version: number;
@@ -11,4 +12,18 @@ export interface AppInfo {
11
12
  * Returns null if the app has never been deployed (404).
12
13
  */
13
14
  export declare function fetchApp(appName: string): Promise<AppInfo | null>;
15
+ export interface RegisterOrUpdateOptions {
16
+ appName: string;
17
+ token: string;
18
+ nextVersion: number;
19
+ entry: ManifestEntry;
20
+ gitRemote: string;
21
+ }
22
+ /**
23
+ * v1 creates the app row (+ owner grant, prompting for visibility); later
24
+ * versions release onto it via update instead — the two are separate
25
+ * endpoints, so this picks based on the version it's handed. Shared by
26
+ * deploy and preview so this branching logic lives in exactly one place.
27
+ */
28
+ export declare function registerOrUpdateApp(opts: RegisterOrUpdateOptions): Promise<void>;
14
29
  //# sourceMappingURL=app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../../src/util/broker/app.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,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,CAiBvE"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../../src/util/broker/app.ts"],"names":[],"mappings":"AAIA,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;IAC/B,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,CAiBvE;AAwCD,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;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtF"}