@mondaydotcomorg/z2h-cli 0.18.0 → 0.19.0-feat-arielle-fixed-preview-id-8f3fc72

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 (43) hide show
  1. package/dist/commands/deploy.d.ts.map +1 -1
  2. package/dist/commands/deploy.js +4 -2
  3. package/dist/esm/commands/deploy.d.ts.map +1 -1
  4. package/dist/esm/commands/deploy.mjs +4 -2
  5. package/dist/esm/migrations/007-proxy-iframe-src.d.ts +3 -0
  6. package/dist/esm/migrations/007-proxy-iframe-src.d.ts.map +1 -0
  7. package/dist/esm/migrations/007-proxy-iframe-src.mjs +49 -0
  8. package/dist/esm/migrations/__tests__/007-proxy-iframe-src.test.d.ts +2 -0
  9. package/dist/esm/migrations/__tests__/007-proxy-iframe-src.test.d.ts.map +1 -0
  10. package/dist/esm/shadow/prepare.d.ts +1 -0
  11. package/dist/esm/shadow/prepare.d.ts.map +1 -1
  12. package/dist/esm/shadow/prepare.mjs +13 -5
  13. package/dist/esm/types.d.ts +1 -0
  14. package/dist/esm/types.d.ts.map +1 -1
  15. package/dist/esm/util/auth/broker-credential-provider.d.ts.map +1 -1
  16. package/dist/esm/util/auth/broker-credential-provider.mjs +5 -2
  17. package/dist/esm/util/broker/app.d.ts.map +1 -1
  18. package/dist/esm/util/broker/app.mjs +3 -0
  19. package/dist/migrations/007-proxy-iframe-src.d.ts +3 -0
  20. package/dist/migrations/007-proxy-iframe-src.d.ts.map +1 -0
  21. package/dist/migrations/007-proxy-iframe-src.js +51 -0
  22. package/dist/migrations/__tests__/007-proxy-iframe-src.test.d.ts +2 -0
  23. package/dist/migrations/__tests__/007-proxy-iframe-src.test.d.ts.map +1 -0
  24. package/dist/shadow/prepare.d.ts +1 -0
  25. package/dist/shadow/prepare.d.ts.map +1 -1
  26. package/dist/shadow/prepare.js +13 -4
  27. package/dist/types.d.ts +1 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/util/auth/broker-credential-provider.d.ts.map +1 -1
  30. package/dist/util/auth/broker-credential-provider.js +5 -2
  31. package/dist/util/broker/app.d.ts.map +1 -1
  32. package/dist/util/broker/app.js +3 -0
  33. package/migrations.json +5 -0
  34. package/package.json +1 -1
  35. package/src/commands/deploy.ts +5 -2
  36. package/src/migrations/007-proxy-iframe-src.ts +58 -0
  37. package/src/migrations/__tests__/007-proxy-iframe-src.test.ts +112 -0
  38. package/src/shadow/prepare.ts +16 -4
  39. package/src/templates/app/html-index.tsx.tmpl +9 -1
  40. package/src/types.ts +1 -0
  41. package/src/util/auth/__tests__/broker-credential-provider.test.ts +20 -0
  42. package/src/util/auth/broker-credential-provider.ts +7 -2
  43. package/src/util/broker/app.ts +5 -0
@@ -1 +1 @@
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
+ {"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,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,7 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const os = require('node:os');
4
- const crypto = require('node:crypto');
5
4
  const fs = require('fs-extra');
6
5
  const util_logger = require('../util/logger.js');
7
6
  const shadow_paths = require('../shadow/paths.js');
@@ -20,6 +19,8 @@ const util_broker_app = require('../util/broker/app.js');
20
19
  const util_openBrowser = require('../util/open-browser.js');
21
20
  const commands_build = require('./build.js');
22
21
  const commands_tag = require('./tag.js');
22
+ const shadow_metadata = require('../shadow/metadata.js');
23
+ const shadow_prepare = require('../shadow/prepare.js');
23
24
 
24
25
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
25
26
 
@@ -63,7 +64,8 @@ async function buildAndStamp(opts) {
63
64
  };
64
65
  }
65
66
  async function deployPreview({ paths, consumerName, appName, token, opts }) {
66
- const previewId = opts.previewId ?? crypto.randomUUID().slice(0, 8);
67
+ const meta = await shadow_metadata.readMetadata(paths);
68
+ const previewId = opts.previewId ?? meta?.previewId ?? shadow_prepare.generatePreviewId();
67
69
  const previewSlug = util_appName.buildPreviewSlug(consumerName, previewId);
68
70
  const previewAppName = util_appName.mfAppName(previewSlug);
69
71
  const keyPrefix = `${constants.S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
@@ -1 +1 @@
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
+ {"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,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,5 +1,4 @@
1
1
  import os from 'node:os';
2
- import { randomUUID } from 'node:crypto';
3
2
  import { readJson } from 'fs-extra';
4
3
  import { info, output, getOutputMode, warn } from '../util/logger.mjs';
5
4
  import { resolveConsumerPaths } from '../shadow/paths.mjs';
@@ -18,6 +17,8 @@ import { registerOrUpdateApp } from '../util/broker/app.mjs';
18
17
  import { openInBrowser } from '../util/open-browser.mjs';
19
18
  import { buildCommand } from './build.mjs';
20
19
  import { promptForTags } from './tag.mjs';
20
+ import { readMetadata } from '../shadow/metadata.mjs';
21
+ import { generatePreviewId } from '../shadow/prepare.mjs';
21
22
 
22
23
  // Should be run from the consumer app directory — uses process.cwd() to locate it.
23
24
  async function deployCommand(opts = {}) {
@@ -57,7 +58,8 @@ async function buildAndStamp(opts) {
57
58
  };
58
59
  }
59
60
  async function deployPreview({ paths, consumerName, appName, token, opts }) {
60
- const previewId = opts.previewId ?? randomUUID().slice(0, 8);
61
+ const meta = await readMetadata(paths);
62
+ const previewId = opts.previewId ?? meta?.previewId ?? generatePreviewId();
61
63
  const previewSlug = buildPreviewSlug(consumerName, previewId);
62
64
  const previewAppName = mfAppName(previewSlug);
63
65
  const keyPrefix = `${S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
@@ -0,0 +1,3 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ export default function proxyIframeSrc(tree: Tree): void;
3
+ //# sourceMappingURL=007-proxy-iframe-src.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"007-proxy-iframe-src.d.ts","sourceRoot":"","sources":["../../../src/migrations/007-proxy-iframe-src.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAqBvC,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAoCvD"}
@@ -0,0 +1,49 @@
1
+ // Backfills the html-embed template's `toProxiedUrl` fix onto apps scaffolded before it
2
+ // existed. Matches any relative `.html?url` import, not just `./page.html` — apps are
3
+ // free to name/nest their embedded HTML file however they like (e.g. `./pages/foo.html`).
4
+ const IFRAME_URL_IMPORT = /import\s+(\w+)\s+from\s+(['"])(\.\/[^'"]+?\.html)\?url\2;?/;
5
+ function toProxiedUrlBlock() {
6
+ return [
7
+ '',
8
+ "const Z2H_MFS_BASE_URL = 'https://bigbrain-zth-mf-assets.bigbrain.me';",
9
+ "const Z2H_ASSETS_PROXY_PREFIX = '/z2h-assets';",
10
+ '',
11
+ 'function toProxiedUrl(url: string): string {',
12
+ ' if (!url.startsWith(Z2H_MFS_BASE_URL)) return url;',
13
+ ' return `${window.location.origin}${Z2H_ASSETS_PROXY_PREFIX}${url.slice(Z2H_MFS_BASE_URL.length)}`;',
14
+ '}',
15
+ ].join('\n');
16
+ }
17
+ function proxyIframeSrc(tree) {
18
+ for (const entry of tree.children('.')) {
19
+ if (entry.startsWith('.')) {
20
+ continue;
21
+ }
22
+ const indexPath = `${entry}/src/index.tsx`;
23
+ if (!tree.exists(indexPath)) {
24
+ continue;
25
+ }
26
+ const source = tree.read(indexPath, 'utf-8');
27
+ if (!source.includes('<iframe') || source.includes('toProxiedUrl')) {
28
+ continue;
29
+ }
30
+ const importMatch = source.match(IFRAME_URL_IMPORT);
31
+ if (!importMatch) {
32
+ continue;
33
+ }
34
+ const [importLine, varName, , relativePath] = importMatch;
35
+ const htmlPath = `${entry}/src/${relativePath.replace(/^\.\//, '')}`;
36
+ if (!tree.exists(htmlPath)) {
37
+ continue;
38
+ }
39
+ const srcAttr = `src={${varName}}`;
40
+ if (!source.includes(srcAttr)) {
41
+ continue;
42
+ }
43
+ const withBlock = source.replace(importLine, `${importLine}\n${toProxiedUrlBlock()}`);
44
+ const rewritten = withBlock.replace(srcAttr, `src={toProxiedUrl(${varName})}`);
45
+ tree.write(indexPath, rewritten);
46
+ }
47
+ }
48
+
49
+ export { proxyIframeSrc as default };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=007-proxy-iframe-src.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"007-proxy-iframe-src.test.d.ts","sourceRoot":"","sources":["../../../../src/migrations/__tests__/007-proxy-iframe-src.test.ts"],"names":[],"mappings":""}
@@ -1,3 +1,4 @@
1
1
  import type { PreparedShadow } from '../types';
2
2
  export declare function prepareShadow(consumerDir?: string): Promise<PreparedShadow>;
3
+ export declare function generatePreviewId(): string;
3
4
  //# sourceMappingURL=prepare.d.ts.map
@@ -1 +1 @@
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
+ {"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,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { pathExists, readJson, ensureDir } from 'fs-extra';
2
3
  import { info } from '../util/logger.mjs';
3
4
  import { cleanAppName } from '../util/app-name.mjs';
@@ -35,6 +36,9 @@ async function prepareShadow(consumerDir = process.cwd()) {
35
36
  await syncShadow(paths, consumerPkg);
36
37
  return { appName, port, paths };
37
38
  }
39
+ function generatePreviewId() {
40
+ return randomUUID().slice(0, 8);
41
+ }
38
42
  /**
39
43
  * Resolve the dev server port. Prefer the persisted port if it's still free
40
44
  * (URL stays stable across restarts). Otherwise pick a fresh one. The
@@ -43,16 +47,20 @@ async function prepareShadow(consumerDir = process.cwd()) {
43
47
  */
44
48
  async function resolvePortAndMetadata(paths, appName) {
45
49
  const existing = await readMetadata(paths);
46
- if (existing?.port && (await isPortFree(existing.port))) {
47
- return existing;
50
+ let port = existing?.port;
51
+ if (!port || !(await isPortFree(port))) {
52
+ port = await pickPort();
48
53
  }
49
54
  const next = {
50
55
  createdAt: existing?.createdAt ?? new Date().toISOString(),
51
56
  appName,
52
- port: await pickPort(),
57
+ port,
58
+ previewId: existing?.previewId ?? generatePreviewId(),
53
59
  };
54
- await writeMetadata(paths, next);
60
+ if (JSON.stringify(next) !== JSON.stringify(existing)) {
61
+ await writeMetadata(paths, next);
62
+ }
55
63
  return next;
56
64
  }
57
65
 
58
- export { prepareShadow };
66
+ export { generatePreviewId, prepareShadow };
@@ -26,6 +26,7 @@ export interface ShadowMetadata {
26
26
  createdAt: string;
27
27
  appName: string;
28
28
  port: number;
29
+ previewId: string;
29
30
  }
30
31
  export interface ConsumerPaths {
31
32
  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;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"}
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 +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;AA4DD;;;;;;;GAOG;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"}
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;AAiD3D;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUzE;AA4DD;;;;;;;GAOG;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"}
@@ -26,8 +26,11 @@ async function postToBroker(path, body) {
26
26
  }
27
27
  }
28
28
  function assertBrokerOk(res) {
29
- if (res.status === 401 || res.status === 403) {
30
- throw new Error(`Z2H deploy token rejected by the broker (${res.status}). Refresh user token by running 'z2h-cli generate-z2h-token'.`);
29
+ if (res.status === 401) {
30
+ throw new Error(`Z2H deploy token rejected by the broker (401). Refresh user token by running 'z2h-cli generate-z2h-token'.`);
31
+ }
32
+ if (res.status === 403) {
33
+ throw new Error(`Access denied (403). You do not have editor access to this app. Ask the app owner to grant you access via 'z2h-cli grant'.`);
31
34
  }
32
35
  }
33
36
  /**
@@ -1 +1 @@
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"}
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,CAsBvE;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"}
@@ -19,6 +19,9 @@ async function fetchApp(appName) {
19
19
  if (res.status === 404) {
20
20
  return null;
21
21
  }
22
+ if (res.status === 403) {
23
+ throw new Error(`Access denied (403). You do not have access to this app. Ask the app owner to grant you access via 'z2h-cli grant'.`);
24
+ }
22
25
  if (!res.ok) {
23
26
  const text = await res.text();
24
27
  throw new Error(`Z2H app lookup failed (${res.status}): ${text.slice(0, 512)}`);
@@ -0,0 +1,3 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ export default function proxyIframeSrc(tree: Tree): void;
3
+ //# sourceMappingURL=007-proxy-iframe-src.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"007-proxy-iframe-src.d.ts","sourceRoot":"","sources":["../../src/migrations/007-proxy-iframe-src.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAqBvC,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAoCvD"}
@@ -0,0 +1,51 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+
3
+ // Backfills the html-embed template's `toProxiedUrl` fix onto apps scaffolded before it
4
+ // existed. Matches any relative `.html?url` import, not just `./page.html` — apps are
5
+ // free to name/nest their embedded HTML file however they like (e.g. `./pages/foo.html`).
6
+ const IFRAME_URL_IMPORT = /import\s+(\w+)\s+from\s+(['"])(\.\/[^'"]+?\.html)\?url\2;?/;
7
+ function toProxiedUrlBlock() {
8
+ return [
9
+ '',
10
+ "const Z2H_MFS_BASE_URL = 'https://bigbrain-zth-mf-assets.bigbrain.me';",
11
+ "const Z2H_ASSETS_PROXY_PREFIX = '/z2h-assets';",
12
+ '',
13
+ 'function toProxiedUrl(url: string): string {',
14
+ ' if (!url.startsWith(Z2H_MFS_BASE_URL)) return url;',
15
+ ' return `${window.location.origin}${Z2H_ASSETS_PROXY_PREFIX}${url.slice(Z2H_MFS_BASE_URL.length)}`;',
16
+ '}',
17
+ ].join('\n');
18
+ }
19
+ function proxyIframeSrc(tree) {
20
+ for (const entry of tree.children('.')) {
21
+ if (entry.startsWith('.')) {
22
+ continue;
23
+ }
24
+ const indexPath = `${entry}/src/index.tsx`;
25
+ if (!tree.exists(indexPath)) {
26
+ continue;
27
+ }
28
+ const source = tree.read(indexPath, 'utf-8');
29
+ if (!source.includes('<iframe') || source.includes('toProxiedUrl')) {
30
+ continue;
31
+ }
32
+ const importMatch = source.match(IFRAME_URL_IMPORT);
33
+ if (!importMatch) {
34
+ continue;
35
+ }
36
+ const [importLine, varName, , relativePath] = importMatch;
37
+ const htmlPath = `${entry}/src/${relativePath.replace(/^\.\//, '')}`;
38
+ if (!tree.exists(htmlPath)) {
39
+ continue;
40
+ }
41
+ const srcAttr = `src={${varName}}`;
42
+ if (!source.includes(srcAttr)) {
43
+ continue;
44
+ }
45
+ const withBlock = source.replace(importLine, `${importLine}\n${toProxiedUrlBlock()}`);
46
+ const rewritten = withBlock.replace(srcAttr, `src={toProxiedUrl(${varName})}`);
47
+ tree.write(indexPath, rewritten);
48
+ }
49
+ }
50
+
51
+ exports.default = proxyIframeSrc;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=007-proxy-iframe-src.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"007-proxy-iframe-src.test.d.ts","sourceRoot":"","sources":["../../../src/migrations/__tests__/007-proxy-iframe-src.test.ts"],"names":[],"mappings":""}
@@ -1,3 +1,4 @@
1
1
  import type { PreparedShadow } from '../types';
2
2
  export declare function prepareShadow(consumerDir?: string): Promise<PreparedShadow>;
3
+ export declare function generatePreviewId(): string;
3
4
  //# sourceMappingURL=prepare.d.ts.map
@@ -1 +1 @@
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
+ {"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,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
+ const crypto = require('node:crypto');
3
4
  const fs = require('fs-extra');
4
5
  const util_logger = require('../util/logger.js');
5
6
  const util_appName = require('../util/app-name.js');
@@ -37,6 +38,9 @@ async function prepareShadow(consumerDir = process.cwd()) {
37
38
  await shadow_sync.syncShadow(paths, consumerPkg);
38
39
  return { appName, port, paths };
39
40
  }
41
+ function generatePreviewId() {
42
+ return crypto.randomUUID().slice(0, 8);
43
+ }
40
44
  /**
41
45
  * Resolve the dev server port. Prefer the persisted port if it's still free
42
46
  * (URL stays stable across restarts). Otherwise pick a fresh one. The
@@ -45,16 +49,21 @@ async function prepareShadow(consumerDir = process.cwd()) {
45
49
  */
46
50
  async function resolvePortAndMetadata(paths, appName) {
47
51
  const existing = await shadow_metadata.readMetadata(paths);
48
- if (existing?.port && (await shadow_port.isPortFree(existing.port))) {
49
- return existing;
52
+ let port = existing?.port;
53
+ if (!port || !(await shadow_port.isPortFree(port))) {
54
+ port = await shadow_port.pickPort();
50
55
  }
51
56
  const next = {
52
57
  createdAt: existing?.createdAt ?? new Date().toISOString(),
53
58
  appName,
54
- port: await shadow_port.pickPort(),
59
+ port,
60
+ previewId: existing?.previewId ?? generatePreviewId(),
55
61
  };
56
- await shadow_metadata.writeMetadata(paths, next);
62
+ if (JSON.stringify(next) !== JSON.stringify(existing)) {
63
+ await shadow_metadata.writeMetadata(paths, next);
64
+ }
57
65
  return next;
58
66
  }
59
67
 
68
+ exports.generatePreviewId = generatePreviewId;
60
69
  exports.prepareShadow = prepareShadow;
package/dist/types.d.ts CHANGED
@@ -26,6 +26,7 @@ export interface ShadowMetadata {
26
26
  createdAt: string;
27
27
  appName: string;
28
28
  port: number;
29
+ previewId: string;
29
30
  }
30
31
  export interface ConsumerPaths {
31
32
  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;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"}
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 +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;AA4DD;;;;;;;GAOG;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"}
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;AAiD3D;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUzE;AA4DD;;;;;;;GAOG;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"}
@@ -28,8 +28,11 @@ async function postToBroker(path, body) {
28
28
  }
29
29
  }
30
30
  function assertBrokerOk(res) {
31
- if (res.status === 401 || res.status === 403) {
32
- throw new Error(`Z2H deploy token rejected by the broker (${res.status}). Refresh user token by running 'z2h-cli generate-z2h-token'.`);
31
+ if (res.status === 401) {
32
+ throw new Error(`Z2H deploy token rejected by the broker (401). Refresh user token by running 'z2h-cli generate-z2h-token'.`);
33
+ }
34
+ if (res.status === 403) {
35
+ throw new Error(`Access denied (403). You do not have editor access to this app. Ask the app owner to grant you access via 'z2h-cli grant'.`);
33
36
  }
34
37
  }
35
38
  /**
@@ -1 +1 @@
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"}
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,CAsBvE;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"}
@@ -21,6 +21,9 @@ async function fetchApp(appName) {
21
21
  if (res.status === 404) {
22
22
  return null;
23
23
  }
24
+ if (res.status === 403) {
25
+ throw new Error(`Access denied (403). You do not have access to this app. Ask the app owner to grant you access via 'z2h-cli grant'.`);
26
+ }
24
27
  if (!res.ok) {
25
28
  const text = await res.text();
26
29
  throw new Error(`Z2H app lookup failed (${res.status}): ${text.slice(0, 512)}`);
package/migrations.json CHANGED
@@ -29,6 +29,11 @@
29
29
  "version": "0.15.3",
30
30
  "description": "Remove the start script (z2h-cli dev) from consumer package.json — dev is gone, superseded by deploy --preview",
31
31
  "migrate": "./dist/migrations/006-remove-start-script"
32
+ },
33
+ "proxy-iframe-src": {
34
+ "version": "0.19.0",
35
+ "description": "Rewrite html-embed apps' iframe src to go through the host's same-origin /z2h-assets proxy instead of the raw CDN bucket URL",
36
+ "migrate": "./dist/migrations/007-proxy-iframe-src"
32
37
  }
33
38
  }
34
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.18.0",
3
+ "version": "0.19.0-feat-arielle-fixed-preview-id-8f3fc72",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,5 +1,4 @@
1
1
  import os from 'node:os';
2
- import { randomUUID } from 'node:crypto';
3
2
  import { readJson } from 'fs-extra';
4
3
  import { info, warn, output, getOutputMode } from '../util/logger';
5
4
  import { resolveConsumerPaths } from '../shadow/paths';
@@ -24,8 +23,10 @@ import { registerOrUpdateApp } from '../util/broker/app';
24
23
  import { openInBrowser } from '../util/open-browser';
25
24
  import { buildCommand } from './build';
26
25
  import { promptForTags } from './tag';
26
+ import { readMetadata } from '../shadow/metadata';
27
27
  import type { ConsumerPaths } from '../types';
28
28
  import type { ManifestEntry } from '../util/manifest';
29
+ import { generatePreviewId } from '../shadow/prepare';
29
30
 
30
31
  export interface DeployOptions {
31
32
  dryRun?: boolean;
@@ -95,7 +96,9 @@ async function buildAndStamp(opts: {
95
96
  }
96
97
 
97
98
  async function deployPreview({ paths, consumerName, appName, token, opts }: DeployContext): Promise<void> {
98
- const previewId = opts.previewId ?? randomUUID().slice(0, 8);
99
+ const meta = await readMetadata(paths);
100
+ const previewId = opts.previewId ?? meta?.previewId ?? generatePreviewId();
101
+
99
102
  const previewSlug = buildPreviewSlug(consumerName, previewId);
100
103
  const previewAppName = mfAppName(previewSlug);
101
104
  const keyPrefix = `${S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
@@ -0,0 +1,58 @@
1
+ import type { Tree } from '@nx/devkit';
2
+
3
+ // Backfills the html-embed template's `toProxiedUrl` fix onto apps scaffolded before it
4
+ // existed. Matches any relative `.html?url` import, not just `./page.html` — apps are
5
+ // free to name/nest their embedded HTML file however they like (e.g. `./pages/foo.html`).
6
+
7
+ const IFRAME_URL_IMPORT = /import\s+(\w+)\s+from\s+(['"])(\.\/[^'"]+?\.html)\?url\2;?/;
8
+
9
+ function toProxiedUrlBlock(): string {
10
+ return [
11
+ '',
12
+ "const Z2H_MFS_BASE_URL = 'https://bigbrain-zth-mf-assets.bigbrain.me';",
13
+ "const Z2H_ASSETS_PROXY_PREFIX = '/z2h-assets';",
14
+ '',
15
+ 'function toProxiedUrl(url: string): string {',
16
+ ' if (!url.startsWith(Z2H_MFS_BASE_URL)) return url;',
17
+ ' return `${window.location.origin}${Z2H_ASSETS_PROXY_PREFIX}${url.slice(Z2H_MFS_BASE_URL.length)}`;',
18
+ '}',
19
+ ].join('\n');
20
+ }
21
+
22
+ export default function proxyIframeSrc(tree: Tree): void {
23
+ for (const entry of tree.children('.')) {
24
+ if (entry.startsWith('.')) {
25
+ continue;
26
+ }
27
+
28
+ const indexPath = `${entry}/src/index.tsx`;
29
+ if (!tree.exists(indexPath)) {
30
+ continue;
31
+ }
32
+
33
+ const source = tree.read(indexPath, 'utf-8')!;
34
+ if (!source.includes('<iframe') || source.includes('toProxiedUrl')) {
35
+ continue;
36
+ }
37
+
38
+ const importMatch = source.match(IFRAME_URL_IMPORT);
39
+ if (!importMatch) {
40
+ continue;
41
+ }
42
+
43
+ const [importLine, varName, , relativePath] = importMatch;
44
+ const htmlPath = `${entry}/src/${relativePath.replace(/^\.\//, '')}`;
45
+ if (!tree.exists(htmlPath)) {
46
+ continue;
47
+ }
48
+
49
+ const srcAttr = `src={${varName}}`;
50
+ if (!source.includes(srcAttr)) {
51
+ continue;
52
+ }
53
+
54
+ const withBlock = source.replace(importLine, `${importLine}\n${toProxiedUrlBlock()}`);
55
+ const rewritten = withBlock.replace(srcAttr, `src={toProxiedUrl(${varName})}`);
56
+ tree.write(indexPath, rewritten);
57
+ }
58
+ }
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createTree } from '@nx/devkit/testing';
3
+ import proxyIframeSrc from '../007-proxy-iframe-src';
4
+ import type { Tree } from '@nx/devkit';
5
+
6
+ const UNMIGRATED_INDEX = [
7
+ "import React, { useRef } from 'react';",
8
+ "import pageUrl from './page.html?url';",
9
+ '',
10
+ 'export default function App() {',
11
+ ' const iframeRef = useRef<HTMLIFrameElement>(null);',
12
+ ' return <iframe ref={iframeRef} src={pageUrl} title="App" />;',
13
+ '}',
14
+ '',
15
+ ].join('\n');
16
+
17
+ function makeTree(): Tree {
18
+ return createTree();
19
+ }
20
+
21
+ function writeHtmlEmbedApp(tree: Tree, dir: string, indexSource: string): void {
22
+ tree.write(`${dir}/package.json`, `{"name":"${dir}"}`);
23
+ tree.write(`${dir}/src/page.html`, '<!doctype html><html></html>');
24
+ tree.write(`${dir}/src/index.tsx`, indexSource);
25
+ }
26
+
27
+ describe('migration 007 — proxy iframe src', () => {
28
+ it('wraps the iframe src and adds the proxy helper', () => {
29
+ const tree = makeTree();
30
+ writeHtmlEmbedApp(tree, 'my-app', UNMIGRATED_INDEX);
31
+
32
+ proxyIframeSrc(tree);
33
+
34
+ const out = tree.read('my-app/src/index.tsx', 'utf-8')!;
35
+ expect(out).toContain("const Z2H_MFS_BASE_URL = 'https://bigbrain-zth-mf-assets.bigbrain.me';");
36
+ expect(out).toContain("const Z2H_ASSETS_PROXY_PREFIX = '/z2h-assets';");
37
+ expect(out).toContain('function toProxiedUrl(url: string): string {');
38
+ expect(out).toContain('src={toProxiedUrl(pageUrl)}');
39
+ });
40
+
41
+ it('handles a page-url variable named something other than pageUrl', () => {
42
+ const tree = makeTree();
43
+ const source = UNMIGRATED_INDEX.replace(/pageUrl/g, 'businessReviewUrl');
44
+ writeHtmlEmbedApp(tree, 'latam-business-review', source);
45
+
46
+ proxyIframeSrc(tree);
47
+
48
+ const out = tree.read('latam-business-review/src/index.tsx', 'utf-8')!;
49
+ expect(out).toContain('src={toProxiedUrl(businessReviewUrl)}');
50
+ });
51
+
52
+ it('handles an HTML file nested in a subdirectory, not just src/page.html', () => {
53
+ const tree = createTree();
54
+ const source = [
55
+ "import React, { useRef } from 'react';",
56
+ "import businessReviewUrl from './pages/business-review.html?url';",
57
+ '',
58
+ 'export default function App() {',
59
+ ' const iframeRef = useRef<HTMLIFrameElement>(null);',
60
+ ' return <iframe ref={iframeRef} src={businessReviewUrl} title="App" />;',
61
+ '}',
62
+ '',
63
+ ].join('\n');
64
+ tree.write('latam-business-review/package.json', '{"name":"latam-business-review"}');
65
+ tree.write('latam-business-review/src/pages/business-review.html', '<!doctype html><html></html>');
66
+ tree.write('latam-business-review/src/index.tsx', source);
67
+
68
+ proxyIframeSrc(tree);
69
+
70
+ const out = tree.read('latam-business-review/src/index.tsx', 'utf-8')!;
71
+ expect(out).toContain('src={toProxiedUrl(businessReviewUrl)}');
72
+ expect(out).toContain("const Z2H_MFS_BASE_URL = 'https://bigbrain-zth-mf-assets.bigbrain.me';");
73
+ });
74
+
75
+ it('is idempotent — a second run on an already-migrated app is a no-op', () => {
76
+ const tree = makeTree();
77
+ writeHtmlEmbedApp(tree, 'my-app', UNMIGRATED_INDEX);
78
+
79
+ proxyIframeSrc(tree);
80
+ const afterFirst = tree.read('my-app/src/index.tsx', 'utf-8')!;
81
+
82
+ proxyIframeSrc(tree);
83
+ expect(tree.read('my-app/src/index.tsx', 'utf-8')!).toBe(afterFirst);
84
+ });
85
+
86
+ it('leaves a default (non-embed) app untouched — no page.html', () => {
87
+ const tree = makeTree();
88
+ tree.write('default-app/package.json', '{"name":"default-app"}');
89
+ tree.write('default-app/src/index.tsx', 'export default function App() { return <div />; }\n');
90
+
91
+ proxyIframeSrc(tree);
92
+
93
+ expect(tree.read('default-app/src/index.tsx', 'utf-8')!).toBe(
94
+ 'export default function App() { return <div />; }\n'
95
+ );
96
+ });
97
+
98
+ it('leaves an html-embed app untouched if it has no iframe tag', () => {
99
+ const tree = makeTree();
100
+ const source = "import pageUrl from './page.html?url';\nexport default () => null;\n";
101
+ writeHtmlEmbedApp(tree, 'weird-app', source);
102
+
103
+ proxyIframeSrc(tree);
104
+
105
+ expect(tree.read('weird-app/src/index.tsx', 'utf-8')!).toBe(source);
106
+ });
107
+
108
+ it('does nothing when the workspace has no apps', () => {
109
+ const tree = makeTree();
110
+ expect(() => proxyIframeSrc(tree)).not.toThrow();
111
+ });
112
+ });
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { ensureDir, pathExists, readJson } from 'fs-extra';
2
3
  import { info } from '../util/logger';
3
4
  import { cleanAppName } from '../util/app-name';
@@ -44,6 +45,10 @@ export async function prepareShadow(consumerDir: string = process.cwd()): Promis
44
45
  return { appName, port, paths };
45
46
  }
46
47
 
48
+ export function generatePreviewId(): string {
49
+ return randomUUID().slice(0, 8);
50
+ }
51
+
47
52
  /**
48
53
  * Resolve the dev server port. Prefer the persisted port if it's still free
49
54
  * (URL stays stable across restarts). Otherwise pick a fresh one. The
@@ -52,15 +57,22 @@ export async function prepareShadow(consumerDir: string = process.cwd()): Promis
52
57
  */
53
58
  async function resolvePortAndMetadata(paths: ConsumerPaths, appName: string): Promise<ShadowMetadata> {
54
59
  const existing = await readMetadata(paths);
55
- if (existing?.port && (await isPortFree(existing.port))) {
56
- return existing;
60
+
61
+ let port = existing?.port;
62
+ if (!port || !(await isPortFree(port))) {
63
+ port = await pickPort();
57
64
  }
58
65
 
59
66
  const next: ShadowMetadata = {
60
67
  createdAt: existing?.createdAt ?? new Date().toISOString(),
61
68
  appName,
62
- port: await pickPort(),
69
+ port,
70
+ previewId: existing?.previewId ?? generatePreviewId(),
63
71
  };
64
- await writeMetadata(paths, next);
72
+
73
+ if (JSON.stringify(next) !== JSON.stringify(existing)) {
74
+ await writeMetadata(paths, next);
75
+ }
76
+
65
77
  return next;
66
78
  }
@@ -9,6 +9,14 @@ function getBackendBaseUrl(): string | null {
9
9
  return base ? base.replace('//', `//${BACKEND_SUBDOMAIN}.`) : null;
10
10
  }
11
11
 
12
+ const Z2H_MFS_BASE_URL = 'https://bigbrain-zth-mf-assets.bigbrain.me';
13
+ const Z2H_ASSETS_PROXY_PREFIX = '/z2h-assets';
14
+
15
+ function toProxiedUrl(url: string): string {
16
+ if (!url.startsWith(Z2H_MFS_BASE_URL)) return url;
17
+ return `${window.location.origin}${Z2H_ASSETS_PROXY_PREFIX}${url.slice(Z2H_MFS_BASE_URL.length)}`;
18
+ }
19
+
12
20
  export default function __APP_COMPONENT__() {
13
21
  const iframeRef = useRef<HTMLIFrameElement>(null);
14
22
  useEffect(() => {
@@ -25,7 +33,7 @@ export default function __APP_COMPONENT__() {
25
33
  return (
26
34
  <iframe
27
35
  ref={iframeRef}
28
- src={pageUrl}
36
+ src={toProxiedUrl(pageUrl)}
29
37
  title="App"
30
38
  style={{ width: '100vw', height: '100vh', border: 'none', display: 'block' }}
31
39
  />
package/src/types.ts CHANGED
@@ -34,6 +34,7 @@ export interface ShadowMetadata {
34
34
  createdAt: string;
35
35
  appName: string;
36
36
  port: number;
37
+ previewId: string;
37
38
  }
38
39
 
39
40
  // ─────────── Filesystem paths, resolved once per invocation ─────
@@ -62,6 +62,12 @@ describe('fetchDeployVersion', () => {
62
62
  mockFetchOnce({}, { status: 401, text: 'Unauthorized' });
63
63
  await expect(fetchDeployVersion('my-app')).rejects.toThrow(/token rejected by the broker/);
64
64
  });
65
+
66
+ it('surfaces an access-denied hint on 403', async () => {
67
+ mockFetchOnce({}, { status: 403, text: 'Forbidden' });
68
+ await expect(fetchDeployVersion('my-app')).rejects.toThrow(/Access denied/);
69
+ await expect(fetchDeployVersion('my-app')).rejects.toThrow(/z2h-cli grant/);
70
+ });
65
71
  });
66
72
 
67
73
  describe('brokerCredentialProvider', () => {
@@ -132,6 +138,13 @@ describe('brokerCredentialProvider', () => {
132
138
  await expect(provider()).rejects.toThrow(/generate-z2h-token/);
133
139
  });
134
140
 
141
+ it('surfaces an access-denied hint on 403', async () => {
142
+ mockFetchOnce({}, { status: 403, text: 'Forbidden' });
143
+ const provider = brokerCredentialProvider({ appName: 'my-app' });
144
+ await expect(provider()).rejects.toThrow(/Access denied/);
145
+ await expect(provider()).rejects.toThrow(/z2h-cli grant/);
146
+ });
147
+
135
148
  it('honors Z2H_BROKER_BASE_URL override', async () => {
136
149
  vi.stubEnv('Z2H_BROKER_BASE_URL', 'https://staging.example.com');
137
150
  const fetchMock = mockFetchOnce({ ...STUB_CREDS, expiration: FAR_FUTURE_EXPIRY });
@@ -191,6 +204,13 @@ describe('brokerCredentialProvider', () => {
191
204
  await expect(provider()).rejects.toThrow(/generate-z2h-token/);
192
205
  });
193
206
 
207
+ it('surfaces an access-denied hint on 403', async () => {
208
+ mockFetchOnce({}, { status: 403, text: 'Forbidden' });
209
+ const provider = brokerCredentialProvider({ appName: 'mf-my-app', previewId: 'preview-1' });
210
+ await expect(provider()).rejects.toThrow(/Access denied/);
211
+ await expect(provider()).rejects.toThrow(/z2h-cli grant/);
212
+ });
213
+
194
214
  it('rejects responses missing required credential fields', async () => {
195
215
  mockFetchOnce({ accessKeyId: 'AKIA-stub' });
196
216
  const provider = brokerCredentialProvider({ appName: 'mf-my-app', previewId: 'preview-1' });
@@ -40,9 +40,14 @@ async function postToBroker(path: string, body: unknown): Promise<Response> {
40
40
  }
41
41
 
42
42
  function assertBrokerOk(res: Response): void {
43
- if (res.status === 401 || res.status === 403) {
43
+ if (res.status === 401) {
44
44
  throw new Error(
45
- `Z2H deploy token rejected by the broker (${res.status}). Refresh user token by running 'z2h-cli generate-z2h-token'.`
45
+ `Z2H deploy token rejected by the broker (401). Refresh user token by running 'z2h-cli generate-z2h-token'.`
46
+ );
47
+ }
48
+ if (res.status === 403) {
49
+ throw new Error(
50
+ `Access denied (403). You do not have editor access to this app. Ask the app owner to grant you access via 'z2h-cli grant'.`
46
51
  );
47
52
  }
48
53
  }
@@ -29,6 +29,11 @@ export async function fetchApp(appName: string): Promise<AppInfo | null> {
29
29
  if (res.status === 404) {
30
30
  return null;
31
31
  }
32
+ if (res.status === 403) {
33
+ throw new Error(
34
+ `Access denied (403). You do not have access to this app. Ask the app owner to grant you access via 'z2h-cli grant'.`
35
+ );
36
+ }
32
37
  if (!res.ok) {
33
38
  const text = await res.text();
34
39
  throw new Error(`Z2H app lookup failed (${res.status}): ${text.slice(0, 512)}`);