@mondaydotcomorg/z2h-cli 0.19.0-feat-arielle-fixed-preview-id-77d1729 → 0.19.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.
@@ -4,7 +4,7 @@ export interface DeployOptions {
4
4
  remote?: RemoteType;
5
5
  repoUrl?: string;
6
6
  preview?: boolean;
7
- snapshot?: boolean;
7
+ previewId?: string;
8
8
  open?: boolean;
9
9
  }
10
10
  export declare function deployCommand(opts?: DeployOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAYtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAUtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E"}
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const os = require('node:os');
4
+ const crypto = require('node:crypto');
4
5
  const fs = require('fs-extra');
5
6
  const util_logger = require('../util/logger.js');
6
7
  const shadow_paths = require('../shadow/paths.js');
@@ -17,8 +18,6 @@ const util_s3_client = require('../util/s3/client.js');
17
18
  const util_machineIdentity = require('../util/machine-identity.js');
18
19
  const util_broker_app = require('../util/broker/app.js');
19
20
  const util_openBrowser = require('../util/open-browser.js');
20
- const shadow_metadata = require('../shadow/metadata.js');
21
- const shadow_prepare = require('../shadow/prepare.js');
22
21
  const commands_build = require('./build.js');
23
22
  const commands_tag = require('./tag.js');
24
23
 
@@ -64,11 +63,7 @@ async function buildAndStamp(opts) {
64
63
  };
65
64
  }
66
65
  async function deployPreview({ paths, consumerName, appName, token, opts }) {
67
- const meta = await shadow_metadata.readMetadata(paths);
68
- let previewId = meta?.previewId;
69
- if (!previewId || opts.snapshot) {
70
- previewId = shadow_prepare.generatePreviewId();
71
- }
66
+ const previewId = opts.previewId ?? crypto.randomUUID().slice(0, 8);
72
67
  const previewSlug = util_appName.buildPreviewSlug(consumerName, previewId);
73
68
  const previewAppName = util_appName.mfAppName(previewSlug);
74
69
  const keyPrefix = `${constants.S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
@@ -4,7 +4,7 @@ export interface DeployOptions {
4
4
  remote?: RemoteType;
5
5
  repoUrl?: string;
6
6
  preview?: boolean;
7
- snapshot?: boolean;
7
+ previewId?: string;
8
8
  open?: boolean;
9
9
  }
10
10
  export declare function deployCommand(opts?: DeployOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAYtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAUtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E"}
@@ -1,4 +1,5 @@
1
1
  import os from 'node:os';
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { readJson } from 'fs-extra';
3
4
  import { info, output, getOutputMode, warn } from '../util/logger.mjs';
4
5
  import { resolveConsumerPaths } from '../shadow/paths.mjs';
@@ -15,8 +16,6 @@ import { setupS3Client } from '../util/s3/client.mjs';
15
16
  import { getMachineAppName } from '../util/machine-identity.mjs';
16
17
  import { registerOrUpdateApp } from '../util/broker/app.mjs';
17
18
  import { openInBrowser } from '../util/open-browser.mjs';
18
- import { readMetadata } from '../shadow/metadata.mjs';
19
- import { generatePreviewId } from '../shadow/prepare.mjs';
20
19
  import { buildCommand } from './build.mjs';
21
20
  import { promptForTags } from './tag.mjs';
22
21
 
@@ -58,11 +57,7 @@ async function buildAndStamp(opts) {
58
57
  };
59
58
  }
60
59
  async function deployPreview({ paths, consumerName, appName, token, opts }) {
61
- const meta = await readMetadata(paths);
62
- let previewId = meta?.previewId;
63
- if (!previewId || opts.snapshot) {
64
- previewId = generatePreviewId();
65
- }
60
+ const previewId = opts.previewId ?? randomUUID().slice(0, 8);
66
61
  const previewSlug = buildPreviewSlug(consumerName, previewId);
67
62
  const previewAppName = mfAppName(previewSlug);
68
63
  const keyPrefix = `${S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
@@ -93,14 +93,17 @@ async function main() {
93
93
  .command('deploy')
94
94
  .description('Build, upload to S3, and register the app with the Z2H backend')
95
95
  .option('--dry-run', 'print what would happen without uploading anything')
96
- .option('--preview', 'build an ephemeral, shareable preview instead of deploying — no git required, no deploy lock, never touches the live version; reuses the same preview URL across runs (stable link)')
97
- .option('--snapshot', 'with --preview: deploy to a fresh previewId without touching the sticky URL — produces a permanent point-in-time link')
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')
98
97
  .option('--open', 'open the resulting app/preview URL in the browser on success')
99
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.')
100
99
  .option('--repo-url <url>', 'First deploy only: GitHub repo URL — required when --remote=github. Error if the app already has a git remote.')
101
100
  .action(async (opts) => {
102
101
  try {
103
- await trackDuration(opts.preview ? 'z2h_app_preview_success' : '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
+ }), {
104
107
  app_name: getCwdAppName(),
105
108
  });
106
109
  }
@@ -1,4 +1,3 @@
1
1
  import type { PreparedShadow } from '../types';
2
2
  export declare function prepareShadow(consumerDir?: string): Promise<PreparedShadow>;
3
- export declare function generatePreviewId(): string;
4
3
  //# sourceMappingURL=prepare.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../../../src/shadow/prepare.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAA8B,cAAc,EAAkB,MAAM,UAAU,CAAC;AAE3F,wBAAsB,aAAa,CAAC,WAAW,GAAE,MAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgChG;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
1
+ {"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../../../src/shadow/prepare.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAA8B,cAAc,EAAkB,MAAM,UAAU,CAAC;AAE3F,wBAAsB,aAAa,CAAC,WAAW,GAAE,MAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgChG"}
@@ -1,4 +1,3 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import { pathExists, readJson, ensureDir } from 'fs-extra';
3
2
  import { info } from '../util/logger.mjs';
4
3
  import { cleanAppName } from '../util/app-name.mjs';
@@ -36,9 +35,6 @@ async function prepareShadow(consumerDir = process.cwd()) {
36
35
  await syncShadow(paths, consumerPkg);
37
36
  return { appName, port, paths };
38
37
  }
39
- function generatePreviewId() {
40
- return randomUUID().slice(0, 8);
41
- }
42
38
  /**
43
39
  * Resolve the dev server port. Prefer the persisted port if it's still free
44
40
  * (URL stays stable across restarts). Otherwise pick a fresh one. The
@@ -47,20 +43,16 @@ function generatePreviewId() {
47
43
  */
48
44
  async function resolvePortAndMetadata(paths, appName) {
49
45
  const existing = await readMetadata(paths);
50
- let port = existing?.port;
51
- if (!port || !(await isPortFree(port))) {
52
- port = await pickPort();
46
+ if (existing?.port && (await isPortFree(existing.port))) {
47
+ return existing;
53
48
  }
54
49
  const next = {
55
50
  createdAt: existing?.createdAt ?? new Date().toISOString(),
56
51
  appName,
57
- port,
58
- previewId: existing?.previewId ?? generatePreviewId(),
52
+ port: await pickPort(),
59
53
  };
60
- if (JSON.stringify(next) !== JSON.stringify(existing)) {
61
- await writeMetadata(paths, next);
62
- }
54
+ await writeMetadata(paths, next);
63
55
  return next;
64
56
  }
65
57
 
66
- export { generatePreviewId, prepareShadow };
58
+ export { prepareShadow };
@@ -26,7 +26,6 @@ export interface ShadowMetadata {
26
26
  createdAt: string;
27
27
  appName: string;
28
28
  port: number;
29
- previewId: string;
30
29
  }
31
30
  export interface ConsumerPaths {
32
31
  consumerDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAOD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAGD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAGD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;CACtB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAOD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAGD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAGD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;CACtB"}
package/dist/index.js CHANGED
@@ -99,14 +99,17 @@ async function main() {
99
99
  .command('deploy')
100
100
  .description('Build, upload to S3, and register the app with the Z2H backend')
101
101
  .option('--dry-run', 'print what would happen without uploading anything')
102
- .option('--preview', 'build an ephemeral, shareable preview instead of deploying — no git required, no deploy lock, never touches the live version; reuses the same preview URL across runs (stable link)')
103
- .option('--snapshot', 'with --preview: deploy to a fresh previewId without touching the sticky URL — produces a permanent point-in-time link')
102
+ .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')
104
103
  .option('--open', 'open the resulting app/preview URL in the browser on success')
105
104
  .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.')
106
105
  .option('--repo-url <url>', 'First deploy only: GitHub repo URL — required when --remote=github. Error if the app already has a git remote.')
107
106
  .action(async (opts) => {
108
107
  try {
109
- await util_tracker.trackDuration(opts.preview ? 'z2h_app_preview_success' : 'z2h_app_deploy_success', () => commands_deploy.deployCommand(opts), {
108
+ await util_tracker.trackDuration(opts.preview ? 'z2h_app_preview_success' : 'z2h_app_deploy_success', () => commands_deploy.deployCommand({
109
+ ...opts,
110
+ preview: Boolean(opts.preview),
111
+ previewId: typeof opts.preview === 'string' ? opts.preview : undefined,
112
+ }), {
110
113
  app_name: util_appName.getCwdAppName(),
111
114
  });
112
115
  }
@@ -1,4 +1,3 @@
1
1
  import type { PreparedShadow } from '../types';
2
2
  export declare function prepareShadow(consumerDir?: string): Promise<PreparedShadow>;
3
- export declare function generatePreviewId(): string;
4
3
  //# sourceMappingURL=prepare.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../../src/shadow/prepare.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAA8B,cAAc,EAAkB,MAAM,UAAU,CAAC;AAE3F,wBAAsB,aAAa,CAAC,WAAW,GAAE,MAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgChG;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
1
+ {"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../../src/shadow/prepare.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAA8B,cAAc,EAAkB,MAAM,UAAU,CAAC;AAE3F,wBAAsB,aAAa,CAAC,WAAW,GAAE,MAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgChG"}
@@ -1,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
- const crypto = require('node:crypto');
4
3
  const fs = require('fs-extra');
5
4
  const util_logger = require('../util/logger.js');
6
5
  const util_appName = require('../util/app-name.js');
@@ -38,9 +37,6 @@ async function prepareShadow(consumerDir = process.cwd()) {
38
37
  await shadow_sync.syncShadow(paths, consumerPkg);
39
38
  return { appName, port, paths };
40
39
  }
41
- function generatePreviewId() {
42
- return crypto.randomUUID().slice(0, 8);
43
- }
44
40
  /**
45
41
  * Resolve the dev server port. Prefer the persisted port if it's still free
46
42
  * (URL stays stable across restarts). Otherwise pick a fresh one. The
@@ -49,21 +45,16 @@ function generatePreviewId() {
49
45
  */
50
46
  async function resolvePortAndMetadata(paths, appName) {
51
47
  const existing = await shadow_metadata.readMetadata(paths);
52
- let port = existing?.port;
53
- if (!port || !(await shadow_port.isPortFree(port))) {
54
- port = await shadow_port.pickPort();
48
+ if (existing?.port && (await shadow_port.isPortFree(existing.port))) {
49
+ return existing;
55
50
  }
56
51
  const next = {
57
52
  createdAt: existing?.createdAt ?? new Date().toISOString(),
58
53
  appName,
59
- port,
60
- previewId: existing?.previewId ?? generatePreviewId(),
54
+ port: await shadow_port.pickPort(),
61
55
  };
62
- if (JSON.stringify(next) !== JSON.stringify(existing)) {
63
- await shadow_metadata.writeMetadata(paths, next);
64
- }
56
+ await shadow_metadata.writeMetadata(paths, next);
65
57
  return next;
66
58
  }
67
59
 
68
- exports.generatePreviewId = generatePreviewId;
69
60
  exports.prepareShadow = prepareShadow;
package/dist/types.d.ts CHANGED
@@ -26,7 +26,6 @@ export interface ShadowMetadata {
26
26
  createdAt: string;
27
27
  appName: string;
28
28
  port: number;
29
- previewId: string;
30
29
  }
31
30
  export interface ConsumerPaths {
32
31
  consumerDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAOD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAGD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAGD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;CACtB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAOD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAGD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAGD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.19.0-feat-arielle-fixed-preview-id-77d1729",
3
+ "version": "0.19.0",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -57,8 +57,6 @@ const ASSET_MANIFEST = {
57
57
  entrypoints: ['main.js'],
58
58
  };
59
59
 
60
- const STUB_PREVIEW_ID = 'existing-preview-id' as const;
61
-
62
60
  describe('deploy --preview', () => {
63
61
  let tmp: string;
64
62
  let cwd: string;
@@ -101,14 +99,6 @@ describe('deploy --preview', () => {
101
99
  await rm(tmp, { recursive: true, force: true });
102
100
  });
103
101
 
104
- const writeMetadata = (previewId: string) =>
105
- writeJson(path.join(tmp, '.zth', 'metadata.json'), {
106
- createdAt: '2026-01-01T00:00:00.000Z',
107
- appName: 'my-app',
108
- port: 13000,
109
- previewId,
110
- });
111
-
112
102
  it('throws when the consumer package.json has no name', async () => {
113
103
  await writeJson(path.join(tmp, 'package.json'), {});
114
104
  await expect(deployCommand({ preview: true })).rejects.toThrow(/must have a "name" field/);
@@ -128,58 +118,49 @@ describe('deploy --preview', () => {
128
118
  expect(uploadDir).toHaveBeenCalledWith(path.join(tmp, '.zth', 'build'), `previews/${previewAppName}/MF`);
129
119
  });
130
120
 
131
- it('reuses the sticky previewId from metadata', async () => {
132
- await writeMetadata(STUB_PREVIEW_ID);
133
- vi.mocked(brokerProvider.fetchDeployVersion).mockResolvedValue(2);
134
-
135
- await deployCommand({ preview: true });
121
+ it('calls registerOrUpdateApp for a brand-new preview at version 1 with an empty gitRemote', async () => {
122
+ vi.mocked(brokerProvider.fetchDeployVersion).mockResolvedValue(0);
136
123
 
137
- const call = vi.mocked(brokerProvider.brokerCredentialProvider).mock.calls[0][0];
138
- expect(call.previewId).toBe(STUB_PREVIEW_ID);
124
+ await deployCommand({ preview: true, previewId: 'existing-preview-id' });
139
125
 
140
- const previewAppName = `mf-${buildPreviewSlug('my-app', STUB_PREVIEW_ID)}`;
141
- expect(uploadDir).toHaveBeenCalledWith(path.join(tmp, '.zth', 'build'), `previews/${previewAppName}/MF`);
126
+ const previewSlug = buildPreviewSlug('my-app', 'existing-preview-id');
127
+ const previewAppName = `mf-${previewSlug}`;
142
128
 
143
129
  expect(brokerApp.registerOrUpdateApp).toHaveBeenCalledWith({
144
130
  appName: previewAppName,
145
131
  token: 'jwt-stub',
146
- nextVersion: 3,
147
- entry: expect.objectContaining({ ...ASSET_MANIFEST, version: 3, gitRemote: '' }),
132
+ nextVersion: 1,
133
+ entry: expect.objectContaining({ ...ASSET_MANIFEST, version: 1, gitRemote: '' }),
148
134
  gitRemote: '',
149
135
  });
150
136
  });
151
137
 
152
- it('calls registerOrUpdateApp for a brand-new preview at version 1 with an empty gitRemote', async () => {
153
- await writeMetadata(STUB_PREVIEW_ID);
154
- vi.mocked(brokerProvider.fetchDeployVersion).mockResolvedValue(0);
138
+ it('reuses a supplied previewId and calls registerOrUpdateApp at version+1', async () => {
139
+ vi.mocked(brokerProvider.fetchDeployVersion).mockResolvedValue(2);
155
140
 
156
- await deployCommand({ preview: true });
141
+ await deployCommand({ preview: true, previewId: 'existing-preview-id' });
142
+
143
+ const call = vi.mocked(brokerProvider.brokerCredentialProvider).mock.calls[0][0];
144
+ expect(call.previewId).toBe('existing-preview-id');
145
+
146
+ const previewSlug = buildPreviewSlug('my-app', 'existing-preview-id');
147
+ const previewAppName = `mf-${previewSlug}`;
148
+
149
+ expect(uploadDir).toHaveBeenCalledWith(path.join(tmp, '.zth', 'build'), `previews/${previewAppName}/MF`);
157
150
 
158
- const previewAppName = `mf-${buildPreviewSlug('my-app', STUB_PREVIEW_ID)}`;
159
151
  expect(brokerApp.registerOrUpdateApp).toHaveBeenCalledWith({
160
152
  appName: previewAppName,
161
153
  token: 'jwt-stub',
162
- nextVersion: 1,
163
- entry: expect.objectContaining({ ...ASSET_MANIFEST, version: 1, gitRemote: '' }),
154
+ nextVersion: 3,
155
+ entry: expect.objectContaining({ ...ASSET_MANIFEST, version: 3, gitRemote: '' }),
164
156
  gitRemote: '',
165
157
  });
166
158
  });
167
159
 
168
- it('--snapshot mints a fresh previewId even when metadata has an existing one', async () => {
169
- await writeMetadata(STUB_PREVIEW_ID);
170
-
171
- await deployCommand({ preview: true, snapshot: true });
172
-
173
- const call = vi.mocked(brokerProvider.brokerCredentialProvider).mock.calls[0][0];
174
- expect(call.previewId).not.toBe(STUB_PREVIEW_ID);
175
- expect(call.previewId).toMatch(/^[0-9a-f]{8}$/);
176
- });
177
-
178
160
  it('passes a full CDN URL (not a relative path) as publicUrl to the build', async () => {
179
- await writeMetadata(STUB_PREVIEW_ID);
180
- await deployCommand({ preview: true });
161
+ await deployCommand({ preview: true, previewId: 'existing-preview-id' });
181
162
 
182
- const previewAppName = `mf-${buildPreviewSlug('my-app', STUB_PREVIEW_ID)}`;
163
+ const previewAppName = `mf-${buildPreviewSlug('my-app', 'existing-preview-id')}`;
183
164
  expect(buildCommand).toHaveBeenCalledWith(
184
165
  expect.objectContaining({
185
166
  publicUrl: `https://bigbrain-zth-mf-assets.bigbrain.me/previews/${previewAppName}/MF`,
@@ -197,10 +178,9 @@ describe('deploy --preview', () => {
197
178
  });
198
179
 
199
180
  it('--open opens the previewUrl in the browser on success', async () => {
200
- await writeMetadata(STUB_PREVIEW_ID);
201
- await deployCommand({ preview: true, open: true });
181
+ await deployCommand({ preview: true, previewId: 'existing-preview-id', open: true });
202
182
 
203
- const previewSlug = buildPreviewSlug('my-app', STUB_PREVIEW_ID);
183
+ const previewSlug = buildPreviewSlug('my-app', 'existing-preview-id');
204
184
  expect(openInBrowser).toHaveBeenCalledWith(`https://bigbrain.me/bigbrain-vibe/${previewSlug}`);
205
185
  });
206
186
 
@@ -215,20 +195,19 @@ describe('deploy --preview', () => {
215
195
  });
216
196
 
217
197
  it('--json emits a machine-readable payload with an app-shaped previewUrl (not ?z2h_preview=)', async () => {
218
- await writeMetadata(STUB_PREVIEW_ID);
219
198
  const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
220
199
  setOutputMode('json');
221
200
 
222
- await deployCommand({ preview: true });
201
+ await deployCommand({ preview: true, previewId: 'existing-preview-id' });
223
202
 
224
203
  const jsonLine = writeSpy.mock.calls.map(c => c[0]).find(line => typeof line === 'string' && line.includes('"ok"'));
225
204
  expect(jsonLine).toBeDefined();
226
205
  const payload = JSON.parse(jsonLine as string);
227
- const previewSlug = buildPreviewSlug('my-app', STUB_PREVIEW_ID);
206
+ const previewSlug = buildPreviewSlug('my-app', 'existing-preview-id');
228
207
  expect(payload).toMatchObject({
229
208
  ok: true,
230
209
  appName: 'mf-my-app',
231
- previewId: STUB_PREVIEW_ID,
210
+ previewId: 'existing-preview-id',
232
211
  previewAppName: `mf-${previewSlug}`,
233
212
  previewUrl: `https://bigbrain.me/bigbrain-vibe/${previewSlug}`,
234
213
  });
@@ -1,4 +1,5 @@
1
1
  import os from 'node:os';
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { readJson } from 'fs-extra';
3
4
  import { info, warn, output, getOutputMode } from '../util/logger';
4
5
  import { resolveConsumerPaths } from '../shadow/paths';
@@ -21,8 +22,6 @@ import { setupS3Client } from '../util/s3/client';
21
22
  import { getMachineAppName } from '../util/machine-identity';
22
23
  import { registerOrUpdateApp } from '../util/broker/app';
23
24
  import { openInBrowser } from '../util/open-browser';
24
- import { readMetadata } from '../shadow/metadata';
25
- import { generatePreviewId } from '../shadow/prepare';
26
25
  import { buildCommand } from './build';
27
26
  import { promptForTags } from './tag';
28
27
  import type { ConsumerPaths } from '../types';
@@ -35,8 +34,8 @@ export interface DeployOptions {
35
34
  // Registers a real, disposable `apps` row under a synthetic name instead of
36
35
  // deploying — no git, no deploy lock (each previewId's slug is unique).
37
36
  preview?: boolean;
38
- // With --preview: deploy to a new previewId without touching metadata — the sticky --preview URL stays unchanged.
39
- snapshot?: boolean;
37
+ // With --preview: update an existing preview instead of minting a new one.
38
+ previewId?: string;
40
39
  open?: boolean;
41
40
  }
42
41
 
@@ -96,12 +95,7 @@ async function buildAndStamp(opts: {
96
95
  }
97
96
 
98
97
  async function deployPreview({ paths, consumerName, appName, token, opts }: DeployContext): Promise<void> {
99
- const meta = await readMetadata(paths);
100
- let previewId = meta?.previewId;
101
- if (!previewId || opts.snapshot) {
102
- previewId = generatePreviewId();
103
- }
104
-
98
+ const previewId = opts.previewId ?? randomUUID().slice(0, 8);
105
99
  const previewSlug = buildPreviewSlug(consumerName, previewId);
106
100
  const previewAppName = mfAppName(previewSlug);
107
101
  const keyPrefix = `${S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
package/src/index.ts CHANGED
@@ -103,12 +103,8 @@ async function main(): Promise<void> {
103
103
  .description('Build, upload to S3, and register the app with the Z2H backend')
104
104
  .option('--dry-run', 'print what would happen without uploading anything')
105
105
  .option(
106
- '--preview',
107
- 'build an ephemeral, shareable preview instead of deploying — no git required, no deploy lock, never touches the live version; reuses the same preview URL across runs (stable link)'
108
- )
109
- .option(
110
- '--snapshot',
111
- 'with --preview: deploy to a fresh previewId without touching the sticky URL — produces a permanent point-in-time link'
106
+ '--preview [previewId]',
107
+ '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'
112
108
  )
113
109
  .option('--open', 'open the resulting app/preview URL in the browser on success')
114
110
  .option(
@@ -122,8 +118,7 @@ async function main(): Promise<void> {
122
118
  .action(
123
119
  async (opts: {
124
120
  dryRun?: boolean;
125
- preview?: boolean;
126
- snapshot?: boolean;
121
+ preview?: boolean | string;
127
122
  open?: boolean;
128
123
  remote?: string;
129
124
  repoUrl?: string;
@@ -131,7 +126,12 @@ async function main(): Promise<void> {
131
126
  try {
132
127
  await trackDuration(
133
128
  opts.preview ? 'z2h_app_preview_success' : 'z2h_app_deploy_success',
134
- () => deployCommand(opts as Parameters<typeof deployCommand>[0]),
129
+ () =>
130
+ deployCommand({
131
+ ...opts,
132
+ preview: Boolean(opts.preview),
133
+ previewId: typeof opts.preview === 'string' ? opts.preview : undefined,
134
+ } as Parameters<typeof deployCommand>[0]),
135
135
  {
136
136
  app_name: getCwdAppName(),
137
137
  }
@@ -1,4 +1,3 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import { ensureDir, pathExists, readJson } from 'fs-extra';
3
2
  import { info } from '../util/logger';
4
3
  import { cleanAppName } from '../util/app-name';
@@ -45,10 +44,6 @@ export async function prepareShadow(consumerDir: string = process.cwd()): Promis
45
44
  return { appName, port, paths };
46
45
  }
47
46
 
48
- export function generatePreviewId(): string {
49
- return randomUUID().slice(0, 8);
50
- }
51
-
52
47
  /**
53
48
  * Resolve the dev server port. Prefer the persisted port if it's still free
54
49
  * (URL stays stable across restarts). Otherwise pick a fresh one. The
@@ -57,22 +52,15 @@ export function generatePreviewId(): string {
57
52
  */
58
53
  async function resolvePortAndMetadata(paths: ConsumerPaths, appName: string): Promise<ShadowMetadata> {
59
54
  const existing = await readMetadata(paths);
60
-
61
- let port = existing?.port;
62
- if (!port || !(await isPortFree(port))) {
63
- port = await pickPort();
55
+ if (existing?.port && (await isPortFree(existing.port))) {
56
+ return existing;
64
57
  }
65
58
 
66
59
  const next: ShadowMetadata = {
67
60
  createdAt: existing?.createdAt ?? new Date().toISOString(),
68
61
  appName,
69
- port,
70
- previewId: existing?.previewId ?? generatePreviewId(),
62
+ port: await pickPort(),
71
63
  };
72
-
73
- if (JSON.stringify(next) !== JSON.stringify(existing)) {
74
- await writeMetadata(paths, next);
75
- }
76
-
64
+ await writeMetadata(paths, next);
77
65
  return next;
78
66
  }
package/src/types.ts CHANGED
@@ -34,7 +34,6 @@ export interface ShadowMetadata {
34
34
  createdAt: string;
35
35
  appName: string;
36
36
  port: number;
37
- previewId: string;
38
37
  }
39
38
 
40
39
  // ─────────── Filesystem paths, resolved once per invocation ─────