@mondaydotcomorg/z2h-cli 0.17.0 → 0.18.1

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.
@@ -66,14 +66,14 @@ async function deployPreview({ paths, consumerName, appName, token, opts }) {
66
66
  const previewId = opts.previewId ?? crypto.randomUUID().slice(0, 8);
67
67
  const previewSlug = util_appName.buildPreviewSlug(consumerName, previewId);
68
68
  const previewAppName = util_appName.mfAppName(previewSlug);
69
- const keyPrefix = `${constants.S3_PREVIEWS_FOLDER}/${appName}/${previewId}/MF`;
69
+ const keyPrefix = `${constants.S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
70
70
  // Full CDN URL, not a relative path — trident-skeleton uses a manifest
71
71
  // entry's `url` verbatim as the asset base, never joined with its own mfUrl.
72
72
  const url = `${constants.BUCKET_READ_DNS_URL}/${keyPrefix}`;
73
73
  const nextVersion = (await util_auth_brokerCredentialProvider.fetchDeployVersion(previewAppName)) + 1;
74
74
  util_logger.info('building');
75
75
  const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote: '' });
76
- const creds = await util_auth_brokerCredentialProvider.brokerCredentialProvider({ appName, previewId })();
76
+ const creds = await util_auth_brokerCredentialProvider.brokerCredentialProvider({ appName: previewAppName, previewId })();
77
77
  util_s3_client.setupS3Client(creds);
78
78
  util_logger.info(`uploading preview build → s3://${constants.BUCKET_WRITE_NAME}/${keyPrefix}/`);
79
79
  await util_s3_upload.uploadDir(paths.shadowBuild, keyPrefix);
@@ -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,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"}
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;AAKjF,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"}
package/dist/constants.js CHANGED
@@ -34,7 +34,8 @@ const REGION = 'us-east-1';
34
34
  const BUCKET_WRITE_URL = `https://${BUCKET_WRITE_NAME}.s3.${REGION}.amazonaws.com`;
35
35
  const BUCKET_READ_DNS_NAME = 'bigbrain-zth-mf-assets';
36
36
  const BUCKET_READ_DNS_URL = `https://${BUCKET_READ_DNS_NAME}.bigbrain.me`;
37
- // Root prefix for ephemeral preview uploads: `previews/<appName>/<previewId>/MF/...`.
37
+ // Root prefix for ephemeral preview uploads: `previews/<previewAppName>/MF/...`,
38
+ // where previewAppName is the preview's own `mf-zth-preview-<id>-<app>` name.
38
39
  // Shared by the CLI (upload path) and bigbrain-zth (STS policy + manifest overlay).
39
40
  const S3_PREVIEWS_FOLDER = 'previews';
40
41
  // No `.json` extension — TridentSkeleton's `mfUrl` API appends `/mf-assets`
@@ -60,14 +60,14 @@ async function deployPreview({ paths, consumerName, appName, token, opts }) {
60
60
  const previewId = opts.previewId ?? randomUUID().slice(0, 8);
61
61
  const previewSlug = buildPreviewSlug(consumerName, previewId);
62
62
  const previewAppName = mfAppName(previewSlug);
63
- const keyPrefix = `${S3_PREVIEWS_FOLDER}/${appName}/${previewId}/MF`;
63
+ const keyPrefix = `${S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
64
64
  // Full CDN URL, not a relative path — trident-skeleton uses a manifest
65
65
  // entry's `url` verbatim as the asset base, never joined with its own mfUrl.
66
66
  const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
67
67
  const nextVersion = (await fetchDeployVersion(previewAppName)) + 1;
68
68
  info('building');
69
69
  const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote: '' });
70
- const creds = await brokerCredentialProvider({ appName, previewId })();
70
+ const creds = await brokerCredentialProvider({ appName: previewAppName, previewId })();
71
71
  setupS3Client(creds);
72
72
  info(`uploading preview build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
73
73
  await uploadDir(paths.shadowBuild, keyPrefix);
@@ -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,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"}
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;AAKjF,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"}
@@ -32,7 +32,8 @@ const REGION = 'us-east-1';
32
32
  const BUCKET_WRITE_URL = `https://${BUCKET_WRITE_NAME}.s3.${REGION}.amazonaws.com`;
33
33
  const BUCKET_READ_DNS_NAME = 'bigbrain-zth-mf-assets';
34
34
  const BUCKET_READ_DNS_URL = `https://${BUCKET_READ_DNS_NAME}.bigbrain.me`;
35
- // Root prefix for ephemeral preview uploads: `previews/<appName>/<previewId>/MF/...`.
35
+ // Root prefix for ephemeral preview uploads: `previews/<previewAppName>/MF/...`,
36
+ // where previewAppName is the preview's own `mf-zth-preview-<id>-<app>` name.
36
37
  // Shared by the CLI (upload path) and bigbrain-zth (STS policy + manifest overlay).
37
38
  const S3_PREVIEWS_FOLDER = 'previews';
38
39
  // No `.json` extension — TridentSkeleton's `mfUrl` API appends `/mf-assets`
@@ -10,9 +10,10 @@ export declare function fetchDeployVersion(appName: string): Promise<number>;
10
10
  /**
11
11
  * AWS SDK credential provider that fetches short-lived credentials from the
12
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.
13
+ * given, to a single `previews/<appName>/*` slot instead of the full deploy
14
+ * path (`appName` being the preview's own app name, which already embeds the
15
+ * id). Credentials are cached in memory until 5 minutes before expiry;
16
+ * concurrent calls share a single in-flight fetch.
16
17
  */
17
18
  export declare function brokerCredentialProvider({ appName, previewId, }: {
18
19
  appName: string;
@@ -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;;;;;;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"}
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
  /**
@@ -96,9 +99,10 @@ function createCachedCredentialProvider(fetchOnce) {
96
99
  /**
97
100
  * AWS SDK credential provider that fetches short-lived credentials from the
98
101
  * 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
+ * given, to a single `previews/<appName>/*` slot instead of the full deploy
103
+ * path (`appName` being the preview's own app name, which already embeds the
104
+ * id). Credentials are cached in memory until 5 minutes before expiry;
105
+ * concurrent calls share a single in-flight fetch.
102
106
  */
103
107
  function brokerCredentialProvider({ appName, previewId, }) {
104
108
  return createCachedCredentialProvider(() => requestSts(STS_BROKER_PATH, { appName, previewId }, previewId ? 'preview' : 'deploy'));
@@ -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)}`);
@@ -10,9 +10,10 @@ export declare function fetchDeployVersion(appName: string): Promise<number>;
10
10
  /**
11
11
  * AWS SDK credential provider that fetches short-lived credentials from the
12
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.
13
+ * given, to a single `previews/<appName>/*` slot instead of the full deploy
14
+ * path (`appName` being the preview's own app name, which already embeds the
15
+ * id). Credentials are cached in memory until 5 minutes before expiry;
16
+ * concurrent calls share a single in-flight fetch.
16
17
  */
17
18
  export declare function brokerCredentialProvider({ appName, previewId, }: {
18
19
  appName: string;
@@ -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;;;;;;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"}
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
  /**
@@ -98,9 +101,10 @@ function createCachedCredentialProvider(fetchOnce) {
98
101
  /**
99
102
  * AWS SDK credential provider that fetches short-lived credentials from the
100
103
  * bigbrain-zth STS broker, scoped to `appName` — or, when `previewId` is
101
- * given, to a single `previews/<appName>/<previewId>/*` slot instead of the
102
- * full deploy path. Credentials are cached in memory until 5 minutes before
103
- * expiry; concurrent calls share a single in-flight fetch.
104
+ * given, to a single `previews/<appName>/*` slot instead of the full deploy
105
+ * path (`appName` being the preview's own app name, which already embeds the
106
+ * id). Credentials are cached in memory until 5 minutes before expiry;
107
+ * concurrent calls share a single in-flight fetch.
104
108
  */
105
109
  function brokerCredentialProvider({ appName, previewId, }) {
106
110
  return createCachedCredentialProvider(() => requestSts(STS_BROKER_PATH, { appName, previewId }, previewId ? 'preview' : 'deploy'));
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -104,15 +104,18 @@ describe('deploy --preview', () => {
104
104
  await expect(deployCommand({ preview: true })).rejects.toThrow(/must have a "name" field/);
105
105
  });
106
106
 
107
- it('mints a fresh previewId and uploads to previews/<mfApp>/<id>/MF', async () => {
107
+ it('mints a fresh previewId and uploads to previews/<previewApp>/MF', async () => {
108
108
  await deployCommand({ preview: true });
109
109
 
110
110
  expect(brokerProvider.brokerCredentialProvider).toHaveBeenCalledTimes(1);
111
111
  const call = vi.mocked(brokerProvider.brokerCredentialProvider).mock.calls[0][0];
112
- expect(call.appName).toBe('mf-my-app');
113
112
  expect(call.previewId).toMatch(/^[0-9a-f]{8}$/);
114
113
 
115
- expect(uploadDir).toHaveBeenCalledWith(path.join(tmp, '.zth', 'build'), `previews/mf-my-app/${call.previewId}/MF`);
114
+ const previewAppName = `mf-${buildPreviewSlug('my-app', call.previewId as string)}`;
115
+ // Credentials are scoped to the folder the upload actually writes, so the
116
+ // broker is asked for the preview's own name rather than the real app's.
117
+ expect(call.appName).toBe(previewAppName);
118
+ expect(uploadDir).toHaveBeenCalledWith(path.join(tmp, '.zth', 'build'), `previews/${previewAppName}/MF`);
116
119
  });
117
120
 
118
121
  it('calls registerOrUpdateApp for a brand-new preview at version 1 with an empty gitRemote', async () => {
@@ -140,13 +143,11 @@ describe('deploy --preview', () => {
140
143
  const call = vi.mocked(brokerProvider.brokerCredentialProvider).mock.calls[0][0];
141
144
  expect(call.previewId).toBe('existing-preview-id');
142
145
 
143
- expect(uploadDir).toHaveBeenCalledWith(
144
- path.join(tmp, '.zth', 'build'),
145
- 'previews/mf-my-app/existing-preview-id/MF'
146
- );
147
-
148
146
  const previewSlug = buildPreviewSlug('my-app', 'existing-preview-id');
149
147
  const previewAppName = `mf-${previewSlug}`;
148
+
149
+ expect(uploadDir).toHaveBeenCalledWith(path.join(tmp, '.zth', 'build'), `previews/${previewAppName}/MF`);
150
+
150
151
  expect(brokerApp.registerOrUpdateApp).toHaveBeenCalledWith({
151
152
  appName: previewAppName,
152
153
  token: 'jwt-stub',
@@ -159,9 +160,10 @@ describe('deploy --preview', () => {
159
160
  it('passes a full CDN URL (not a relative path) as publicUrl to the build', async () => {
160
161
  await deployCommand({ preview: true, previewId: 'existing-preview-id' });
161
162
 
163
+ const previewAppName = `mf-${buildPreviewSlug('my-app', 'existing-preview-id')}`;
162
164
  expect(buildCommand).toHaveBeenCalledWith(
163
165
  expect.objectContaining({
164
- publicUrl: 'https://bigbrain-zth-mf-assets.bigbrain.me/previews/mf-my-app/existing-preview-id/MF',
166
+ publicUrl: `https://bigbrain-zth-mf-assets.bigbrain.me/previews/${previewAppName}/MF`,
165
167
  })
166
168
  );
167
169
  });
@@ -98,7 +98,7 @@ async function deployPreview({ paths, consumerName, appName, token, opts }: Depl
98
98
  const previewId = opts.previewId ?? randomUUID().slice(0, 8);
99
99
  const previewSlug = buildPreviewSlug(consumerName, previewId);
100
100
  const previewAppName = mfAppName(previewSlug);
101
- const keyPrefix = `${S3_PREVIEWS_FOLDER}/${appName}/${previewId}/MF`;
101
+ const keyPrefix = `${S3_PREVIEWS_FOLDER}/${previewAppName}/MF`;
102
102
  // Full CDN URL, not a relative path — trident-skeleton uses a manifest
103
103
  // entry's `url` verbatim as the asset base, never joined with its own mfUrl.
104
104
  const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
@@ -108,7 +108,7 @@ async function deployPreview({ paths, consumerName, appName, token, opts }: Depl
108
108
  info('building');
109
109
  const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote: '' });
110
110
 
111
- const creds = await brokerCredentialProvider({ appName, previewId })();
111
+ const creds = await brokerCredentialProvider({ appName: previewAppName, previewId })();
112
112
  setupS3Client(creds);
113
113
 
114
114
  info(`uploading preview build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
package/src/constants.ts CHANGED
@@ -41,7 +41,8 @@ export const BUCKET_WRITE_URL = `https://${BUCKET_WRITE_NAME}.s3.${REGION}.amazo
41
41
  export const BUCKET_READ_DNS_NAME = 'bigbrain-zth-mf-assets';
42
42
  export const BUCKET_READ_DNS_URL = `https://${BUCKET_READ_DNS_NAME}.bigbrain.me`;
43
43
 
44
- // Root prefix for ephemeral preview uploads: `previews/<appName>/<previewId>/MF/...`.
44
+ // Root prefix for ephemeral preview uploads: `previews/<previewAppName>/MF/...`,
45
+ // where previewAppName is the preview's own `mf-zth-preview-<id>-<app>` name.
45
46
  // Shared by the CLI (upload path) and bigbrain-zth (STS policy + manifest overlay).
46
47
  export const S3_PREVIEWS_FOLDER = 'previews';
47
48
 
@@ -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
  }
@@ -125,9 +130,10 @@ function createCachedCredentialProvider(
125
130
  /**
126
131
  * AWS SDK credential provider that fetches short-lived credentials from the
127
132
  * bigbrain-zth STS broker, scoped to `appName` — or, when `previewId` is
128
- * given, to a single `previews/<appName>/<previewId>/*` slot instead of the
129
- * full deploy path. Credentials are cached in memory until 5 minutes before
130
- * expiry; concurrent calls share a single in-flight fetch.
133
+ * given, to a single `previews/<appName>/*` slot instead of the full deploy
134
+ * path (`appName` being the preview's own app name, which already embeds the
135
+ * id). Credentials are cached in memory until 5 minutes before expiry;
136
+ * concurrent calls share a single in-flight fetch.
131
137
  */
132
138
  export function brokerCredentialProvider({
133
139
  appName,
@@ -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)}`);