@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.
- package/dist/commands/deploy.js +2 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/esm/commands/deploy.mjs +2 -2
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.mjs +2 -1
- package/dist/esm/util/auth/broker-credential-provider.d.ts +4 -3
- package/dist/esm/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/esm/util/auth/broker-credential-provider.mjs +9 -5
- package/dist/esm/util/broker/app.d.ts.map +1 -1
- package/dist/esm/util/broker/app.mjs +3 -0
- package/dist/util/auth/broker-credential-provider.d.ts +4 -3
- package/dist/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/util/auth/broker-credential-provider.js +9 -5
- package/dist/util/broker/app.d.ts.map +1 -1
- package/dist/util/broker/app.js +3 -0
- package/package.json +1 -1
- package/src/commands/__tests__/deploy.test.ts +11 -9
- package/src/commands/deploy.ts +2 -2
- package/src/constants.ts +2 -1
- package/src/util/auth/__tests__/broker-credential-provider.test.ts +20 -0
- package/src/util/auth/broker-credential-provider.ts +11 -5
- package/src/util/broker/app.ts +5 -0
package/dist/commands/deploy.js
CHANGED
|
@@ -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}/${
|
|
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);
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -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;
|
|
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/<
|
|
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}/${
|
|
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;
|
|
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/esm/constants.mjs
CHANGED
|
@@ -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/<
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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;
|
|
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
|
|
30
|
-
throw new Error(`Z2H deploy token rejected by the broker (
|
|
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
|
|
100
|
-
*
|
|
101
|
-
*
|
|
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,
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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;
|
|
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
|
|
32
|
-
throw new Error(`Z2H deploy token rejected by the broker (
|
|
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
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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,
|
|
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"}
|
package/dist/util/broker/app.js
CHANGED
|
@@ -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
|
@@ -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/<
|
|
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
|
-
|
|
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:
|
|
166
|
+
publicUrl: `https://bigbrain-zth-mf-assets.bigbrain.me/previews/${previewAppName}/MF`,
|
|
165
167
|
})
|
|
166
168
|
);
|
|
167
169
|
});
|
package/src/commands/deploy.ts
CHANGED
|
@@ -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}/${
|
|
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/<
|
|
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
|
|
43
|
+
if (res.status === 401) {
|
|
44
44
|
throw new Error(
|
|
45
|
-
`Z2H deploy token rejected by the broker (
|
|
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
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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,
|
package/src/util/broker/app.ts
CHANGED
|
@@ -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)}`);
|