@mondaydotcomorg/z2h-cli 0.2.0 → 0.3.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.
Files changed (98) hide show
  1. package/dist/commands/create-workspace.js +3 -3
  2. package/dist/commands/create.d.ts.map +1 -1
  3. package/dist/commands/create.js +10 -3
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +19 -24
  6. package/dist/commands/generate-z2h-token.d.ts +16 -0
  7. package/dist/commands/generate-z2h-token.d.ts.map +1 -0
  8. package/dist/commands/generate-z2h-token.js +107 -0
  9. package/dist/constants.d.ts +8 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +28 -0
  12. package/dist/esm/commands/create-workspace.mjs +3 -3
  13. package/dist/esm/commands/create.d.ts.map +1 -1
  14. package/dist/esm/commands/create.mjs +10 -3
  15. package/dist/esm/commands/deploy.d.ts.map +1 -1
  16. package/dist/esm/commands/deploy.mjs +16 -21
  17. package/dist/esm/commands/generate-z2h-token.d.ts +16 -0
  18. package/dist/esm/commands/generate-z2h-token.d.ts.map +1 -0
  19. package/dist/esm/commands/generate-z2h-token.mjs +99 -0
  20. package/dist/esm/constants.d.ts +8 -0
  21. package/dist/esm/constants.d.ts.map +1 -1
  22. package/dist/esm/constants.mjs +21 -1
  23. package/dist/esm/index.mjs +18 -1
  24. package/dist/esm/util/auth/__tests__/auth-store.test.d.ts +2 -0
  25. package/dist/esm/util/auth/__tests__/auth-store.test.d.ts.map +1 -0
  26. package/dist/esm/util/auth/__tests__/broker-credential-provider.test.d.ts +2 -0
  27. package/dist/esm/util/auth/__tests__/broker-credential-provider.test.d.ts.map +1 -0
  28. package/dist/esm/util/auth/__tests__/loopback-server.test.d.ts +2 -0
  29. package/dist/esm/util/auth/__tests__/loopback-server.test.d.ts.map +1 -0
  30. package/dist/esm/util/auth/__tests__/write-auth.test.d.ts +2 -0
  31. package/dist/esm/util/auth/__tests__/write-auth.test.d.ts.map +1 -0
  32. package/dist/esm/util/auth/auth-store.d.ts +11 -0
  33. package/dist/esm/util/auth/auth-store.d.ts.map +1 -0
  34. package/dist/esm/util/auth/auth-store.mjs +29 -0
  35. package/dist/esm/util/auth/broker-credential-provider.d.ts +12 -0
  36. package/dist/esm/util/auth/broker-credential-provider.d.ts.map +1 -0
  37. package/dist/esm/util/auth/broker-credential-provider.mjs +76 -0
  38. package/dist/esm/util/auth/loopback-server.d.ts +12 -0
  39. package/dist/esm/util/auth/loopback-server.d.ts.map +1 -0
  40. package/dist/esm/util/auth/loopback-server.mjs +87 -0
  41. package/dist/esm/util/auth/write-auth.d.ts +11 -0
  42. package/dist/esm/util/auth/write-auth.d.ts.map +1 -0
  43. package/dist/esm/util/auth/write-auth.mjs +22 -0
  44. package/dist/esm/util/manifest.d.ts.map +1 -1
  45. package/dist/esm/util/manifest.mjs +2 -1
  46. package/dist/esm/util/s3/client.d.ts +7 -8
  47. package/dist/esm/util/s3/client.d.ts.map +1 -1
  48. package/dist/esm/util/s3/client.mjs +15 -30
  49. package/dist/esm/util/s3/upload.d.ts.map +1 -1
  50. package/dist/esm/util/s3/upload.mjs +2 -1
  51. package/dist/index.js +19 -1
  52. package/dist/util/auth/__tests__/auth-store.test.d.ts +2 -0
  53. package/dist/util/auth/__tests__/auth-store.test.d.ts.map +1 -0
  54. package/dist/util/auth/__tests__/broker-credential-provider.test.d.ts +2 -0
  55. package/dist/util/auth/__tests__/broker-credential-provider.test.d.ts.map +1 -0
  56. package/dist/util/auth/__tests__/loopback-server.test.d.ts +2 -0
  57. package/dist/util/auth/__tests__/loopback-server.test.d.ts.map +1 -0
  58. package/dist/util/auth/__tests__/write-auth.test.d.ts +2 -0
  59. package/dist/util/auth/__tests__/write-auth.test.d.ts.map +1 -0
  60. package/dist/util/auth/auth-store.d.ts +11 -0
  61. package/dist/util/auth/auth-store.d.ts.map +1 -0
  62. package/dist/util/auth/auth-store.js +39 -0
  63. package/dist/util/auth/broker-credential-provider.d.ts +12 -0
  64. package/dist/util/auth/broker-credential-provider.d.ts.map +1 -0
  65. package/dist/util/auth/broker-credential-provider.js +80 -0
  66. package/dist/util/auth/loopback-server.d.ts +12 -0
  67. package/dist/util/auth/loopback-server.d.ts.map +1 -0
  68. package/dist/util/auth/loopback-server.js +94 -0
  69. package/dist/util/auth/write-auth.d.ts +11 -0
  70. package/dist/util/auth/write-auth.d.ts.map +1 -0
  71. package/dist/util/auth/write-auth.js +26 -0
  72. package/dist/util/manifest.d.ts.map +1 -1
  73. package/dist/util/manifest.js +5 -4
  74. package/dist/util/s3/client.d.ts +7 -8
  75. package/dist/util/s3/client.d.ts.map +1 -1
  76. package/dist/util/s3/client.js +14 -36
  77. package/dist/util/s3/upload.d.ts.map +1 -1
  78. package/dist/util/s3/upload.js +2 -1
  79. package/package.json +10 -1
  80. package/src/commands/__tests__/create-workspace.test.ts +1 -1
  81. package/src/commands/__tests__/create.test.ts +1 -5
  82. package/src/commands/create-workspace.ts +3 -3
  83. package/src/commands/create.ts +12 -3
  84. package/src/commands/deploy.ts +18 -31
  85. package/src/commands/generate-z2h-token.ts +124 -0
  86. package/src/constants.ts +25 -0
  87. package/src/index.ts +21 -1
  88. package/src/util/auth/__tests__/auth-store.test.ts +50 -0
  89. package/src/util/auth/__tests__/broker-credential-provider.test.ts +115 -0
  90. package/src/util/auth/__tests__/loopback-server.test.ts +85 -0
  91. package/src/util/auth/__tests__/write-auth.test.ts +79 -0
  92. package/src/util/auth/auth-store.ts +31 -0
  93. package/src/util/auth/broker-credential-provider.ts +93 -0
  94. package/src/util/auth/loopback-server.ts +103 -0
  95. package/src/util/auth/write-auth.ts +24 -0
  96. package/src/util/manifest.ts +2 -1
  97. package/src/util/s3/client.ts +15 -32
  98. package/src/util/s3/upload.ts +2 -1
@@ -0,0 +1,39 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const path = require('node:path');
4
+ const os = require('node:os');
5
+ const fsExtra = require('fs-extra');
6
+
7
+ const _interopDefault = e => e && e.__esModule ? e : { default: e };
8
+
9
+ const path__default = /*#__PURE__*/_interopDefault(path);
10
+ const os__default = /*#__PURE__*/_interopDefault(os);
11
+
12
+ // npm-style convention: per-user config under ~/.zth (mirrors ~/.npmrc, ~/.aws/).
13
+ // Kept out of the workspace dir so accidental `git init` in ~/zth-projects can't
14
+ // stage it.
15
+ const Z2H_AUTH_DIR = path__default.default.join(os__default.default.homedir(), '.zth');
16
+ const Z2H_AUTH_FILE = path__default.default.join(Z2H_AUTH_DIR, 'auth.json');
17
+ class Z2hAuthMissingError extends Error {
18
+ authFilePath;
19
+ constructor(authFilePath) {
20
+ super(`No Z2H deploy token found at ${authFilePath}. Please run 'z2h-cli generate-z2h-token' command.`);
21
+ this.authFilePath = authFilePath;
22
+ this.name = 'Z2hAuthMissingError';
23
+ }
24
+ }
25
+ async function readZthAuth() {
26
+ if (!(await fsExtra.pathExists(Z2H_AUTH_FILE))) {
27
+ throw new Z2hAuthMissingError(Z2H_AUTH_FILE);
28
+ }
29
+ const parsed = (await fsExtra.readJson(Z2H_AUTH_FILE));
30
+ if (!parsed.token) {
31
+ throw new Error(`${Z2H_AUTH_FILE} is missing a "token" string field.`);
32
+ }
33
+ return { token: parsed.token };
34
+ }
35
+
36
+ exports.Z2H_AUTH_DIR = Z2H_AUTH_DIR;
37
+ exports.Z2H_AUTH_FILE = Z2H_AUTH_FILE;
38
+ exports.Z2hAuthMissingError = Z2hAuthMissingError;
39
+ exports.readZthAuth = readZthAuth;
@@ -0,0 +1,12 @@
1
+ import type { AwsCredentialIdentityProvider } from '@aws-sdk/types';
2
+ export declare const DEFAULT_BROKER_BASE_URL = "https://bigbrain-zth.bigbrain.me";
3
+ export declare const STS_BROKER_PATH = "/z2h-cli/sts/credentials";
4
+ /**
5
+ * AWS SDK credential provider that fetches short-lived deploy credentials
6
+ * from the bigbrain-zth STS broker. Credentials are cached in memory until
7
+ * 5 minutes before expiry; concurrent calls share a single in-flight fetch.
8
+ */
9
+ export declare function brokerCredentialProvider({ appName }: {
10
+ appName: string;
11
+ }): AwsCredentialIdentityProvider;
12
+ //# sourceMappingURL=broker-credential-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker-credential-provider.d.ts","sourceRoot":"","sources":["../../../src/util/auth/broker-credential-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAyB,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAE3F,eAAO,MAAM,uBAAuB,qCAAqC,CAAC;AAC1E,eAAO,MAAM,eAAe,6BAA6B,CAAC;AA4D1D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,6BAA6B,CAuBxG"}
@@ -0,0 +1,80 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const util_auth_authStore = require('./auth-store.js');
4
+
5
+ const DEFAULT_BROKER_BASE_URL = 'https://bigbrain-zth.bigbrain.me';
6
+ const STS_BROKER_PATH = '/z2h-cli/sts/credentials';
7
+ // Refresh the cached credentials this many ms before they expire so the next
8
+ // AWS API call doesn't race the broker round-trip against a hard expiry.
9
+ const REFRESH_BUFFER_MS = 5 * 60 * 1000;
10
+ function brokerBaseUrl() {
11
+ return process.env.Z2H_BROKER_BASE_URL || DEFAULT_BROKER_BASE_URL;
12
+ }
13
+ async function fetchCredentialsOnce(appName) {
14
+ const { token } = await util_auth_authStore.readZthAuth();
15
+ const url = `${brokerBaseUrl()}${STS_BROKER_PATH}`;
16
+ let res;
17
+ try {
18
+ res = await fetch(url, {
19
+ method: 'POST',
20
+ headers: {
21
+ Authorization: `Bearer ${token}`,
22
+ 'Content-Type': 'application/json',
23
+ Accept: 'application/json',
24
+ },
25
+ body: JSON.stringify({ appName }),
26
+ });
27
+ }
28
+ catch (err) {
29
+ const message = err instanceof Error ? err.message : String(err);
30
+ throw new Error(`Z2H deploy broker request failed: ${message}`);
31
+ }
32
+ if (res.status === 401 || res.status === 403) {
33
+ throw new Error(`Z2H deploy token rejected by the broker (${res.status}). Refresh user token by running 'z2h-cli generate-z2h-token'.`);
34
+ }
35
+ if (!res.ok) {
36
+ const text = await res.text();
37
+ throw new Error(`Z2H deploy broker returned ${res.status}: ${text.slice(0, 1024)}`);
38
+ }
39
+ const body = (await res.json());
40
+ if (!body.accessKeyId || !body.secretAccessKey || !body.sessionToken || !body.expiration) {
41
+ throw new Error('Z2H deploy broker response missing required credential fields');
42
+ }
43
+ return {
44
+ accessKeyId: body.accessKeyId,
45
+ secretAccessKey: body.secretAccessKey,
46
+ sessionToken: body.sessionToken,
47
+ expiration: new Date(body.expiration),
48
+ };
49
+ }
50
+ /**
51
+ * AWS SDK credential provider that fetches short-lived deploy credentials
52
+ * from the bigbrain-zth STS broker. Credentials are cached in memory until
53
+ * 5 minutes before expiry; concurrent calls share a single in-flight fetch.
54
+ */
55
+ function brokerCredentialProvider({ appName }) {
56
+ let cached;
57
+ let inflight;
58
+ return async () => {
59
+ const now = Date.now();
60
+ if (cached && cached.expiration && cached.expiration.getTime() - now > REFRESH_BUFFER_MS) {
61
+ return cached;
62
+ }
63
+ if (inflight) {
64
+ return inflight;
65
+ }
66
+ inflight = fetchCredentialsOnce(appName)
67
+ .then(creds => {
68
+ cached = creds;
69
+ return creds;
70
+ })
71
+ .finally(() => {
72
+ inflight = undefined;
73
+ });
74
+ return inflight;
75
+ };
76
+ }
77
+
78
+ exports.DEFAULT_BROKER_BASE_URL = DEFAULT_BROKER_BASE_URL;
79
+ exports.STS_BROKER_PATH = STS_BROKER_PATH;
80
+ exports.brokerCredentialProvider = brokerCredentialProvider;
@@ -0,0 +1,12 @@
1
+ export interface LoopbackHandle {
2
+ /** Fully-qualified URL the browser should POST to (e.g. http://127.0.0.1:53811/token). */
3
+ callbackUrl: string;
4
+ /**
5
+ * Resolves with the token once the page POSTs it back. Rejects on timeout
6
+ * or if the server is closed before a token arrives.
7
+ */
8
+ waitForToken(timeoutMs: number): Promise<string>;
9
+ close(): void;
10
+ }
11
+ export declare function startLoopbackServer(): Promise<LoopbackHandle>;
12
+ //# sourceMappingURL=loopback-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback-server.d.ts","sourceRoot":"","sources":["../../../src/util/auth/loopback-server.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAAC,cAAc,CAAC,CAkF7D"}
@@ -0,0 +1,94 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const cors = require('cors');
4
+ const express = require('express');
5
+
6
+ const _interopDefault = e => e && e.__esModule ? e : { default: e };
7
+
8
+ const cors__default = /*#__PURE__*/_interopDefault(cors);
9
+ const express__default = /*#__PURE__*/_interopDefault(express);
10
+
11
+ const ALLOWED_ORIGINS = new Set(['https://bigbrain.me', 'https://bigbrainstaging.me']);
12
+ const CALLBACK_PATH = '/token';
13
+ const REQUEST_BODY_LIMIT = '16kb';
14
+ const PREFLIGHT_MAX_AGE_SECONDS = 600;
15
+ function startLoopbackServer() {
16
+ return new Promise((resolve, reject) => {
17
+ let resolveToken = null;
18
+ let rejectToken = null;
19
+ const app = express__default.default();
20
+ // Reflect the origin if it's one of ours; otherwise wildcard. The
21
+ // endpoint is loopback-bound and single-shot, so the CORS surface is
22
+ // moot — we keep allowlist behavior so the test contract stays exact.
23
+ app.use(cors__default.default({
24
+ origin: (origin, cb) => {
25
+ if (origin && ALLOWED_ORIGINS.has(origin)) {
26
+ cb(null, origin);
27
+ }
28
+ else {
29
+ cb(null, '*');
30
+ }
31
+ },
32
+ methods: ['POST', 'OPTIONS'],
33
+ allowedHeaders: ['Content-Type'],
34
+ maxAge: PREFLIGHT_MAX_AGE_SECONDS,
35
+ }));
36
+ app.use(express.json({ limit: REQUEST_BODY_LIMIT }));
37
+ app.post(CALLBACK_PATH, (req, res) => {
38
+ const body = req.body;
39
+ const token = typeof body?.token === 'string' ? body.token.trim() : '';
40
+ if (!token) {
41
+ res.status(400).json({ error: 'missing token' });
42
+ return;
43
+ }
44
+ // Ack the page so it can transition to "Token sent ✓" and auto-close.
45
+ // The bigbrain.me page never displays this body — it just inspects res.ok.
46
+ res.status(200).json({ ok: true });
47
+ if (resolveToken) {
48
+ const fn = resolveToken;
49
+ resolveToken = null;
50
+ rejectToken = null;
51
+ fn(token);
52
+ }
53
+ });
54
+ const server = app.listen(0, '127.0.0.1', () => {
55
+ const address = server.address();
56
+ if (!address || typeof address === 'string') {
57
+ server.close();
58
+ reject(new Error('Failed to determine loopback port'));
59
+ return;
60
+ }
61
+ resolve({
62
+ callbackUrl: `http://127.0.0.1:${address.port}${CALLBACK_PATH}`,
63
+ waitForToken(timeoutMs) {
64
+ return new Promise((res, rej) => {
65
+ resolveToken = res;
66
+ rejectToken = rej;
67
+ const timer = setTimeout(() => {
68
+ if (rejectToken) {
69
+ const reject = rejectToken;
70
+ resolveToken = null;
71
+ rejectToken = null;
72
+ reject(new Error('Loopback callback timed out'));
73
+ }
74
+ }, timeoutMs);
75
+ // Don't keep the event loop alive on the timer alone.
76
+ timer.unref?.();
77
+ });
78
+ },
79
+ close() {
80
+ if (rejectToken) {
81
+ const reject = rejectToken;
82
+ resolveToken = null;
83
+ rejectToken = null;
84
+ reject(new Error('Loopback server closed before token arrived'));
85
+ }
86
+ server.close();
87
+ },
88
+ });
89
+ });
90
+ server.on('error', reject);
91
+ });
92
+ }
93
+
94
+ exports.startLoopbackServer = startLoopbackServer;
@@ -0,0 +1,11 @@
1
+ import { type ZthAuth } from './auth-store';
2
+ export declare function writeZthAuth(auth: ZthAuth): Promise<{
3
+ filePath: string;
4
+ }>;
5
+ /**
6
+ * Three base64url segments separated by dots — a structural sanity check,
7
+ * not a real verification. Server-side validation happens at the STS broker.
8
+ */
9
+ export declare function validateJwtPattern(token: string): boolean;
10
+ export { Z2H_AUTH_FILE } from './auth-store';
11
+ //# sourceMappingURL=write-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-auth.d.ts","sourceRoot":"","sources":["../../../src/util/auth/write-auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAA+B,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAMzE,wBAAsB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAK/E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,26 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const promises = require('node:fs/promises');
4
+ const fsExtra = require('fs-extra');
5
+ const util_auth_authStore = require('./auth-store.js');
6
+
7
+ // 0o600 — owner read/write only. The file holds a long-lived bearer token,
8
+ // so other local users on the machine must not be able to read it.
9
+ const OWNER_READ_WRITE_FILE_MODE = 0o600;
10
+ async function writeZthAuth(auth) {
11
+ await fsExtra.ensureDir(util_auth_authStore.Z2H_AUTH_DIR);
12
+ await fsExtra.writeFile(util_auth_authStore.Z2H_AUTH_FILE, `${JSON.stringify(auth, null, 2)}\n`);
13
+ await promises.chmod(util_auth_authStore.Z2H_AUTH_FILE, OWNER_READ_WRITE_FILE_MODE);
14
+ return { filePath: util_auth_authStore.Z2H_AUTH_FILE };
15
+ }
16
+ /**
17
+ * Three base64url segments separated by dots — a structural sanity check,
18
+ * not a real verification. Server-side validation happens at the STS broker.
19
+ */
20
+ function validateJwtPattern(token) {
21
+ return /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token);
22
+ }
23
+
24
+ exports.Z2H_AUTH_FILE = util_auth_authStore.Z2H_AUTH_FILE;
25
+ exports.validateJwtPattern = validateJwtPattern;
26
+ exports.writeZthAuth = writeZthAuth;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/util/manifest.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAErD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQtD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,UAI1D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAE3F;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/util/manifest.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAErD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQtD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,UAI1D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAE3F;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE"}
@@ -1,13 +1,14 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const clientS3 = require('@aws-sdk/client-s3');
4
+ const constants = require('../constants.js');
4
5
  const util_s3_client = require('./s3/client.js');
5
6
 
6
7
  async function readManifest() {
7
8
  // Read via the public CDN endpoint, no AWS auth required.
8
- const res = await fetch(util_s3_client.MANIFEST_READ_URL);
9
+ const res = await fetch(constants.MANIFEST_READ_URL);
9
10
  if (!res.ok) {
10
- throw new Error(`failed to read manifest from ${util_s3_client.MANIFEST_READ_URL}: HTTP ${res.status}`);
11
+ throw new Error(`failed to read manifest from ${constants.MANIFEST_READ_URL}: HTTP ${res.status}`);
11
12
  }
12
13
  return (await res.json());
13
14
  }
@@ -22,8 +23,8 @@ function upsertEntry(manifest, app, entry) {
22
23
  async function writeManifest(manifest) {
23
24
  const s3 = util_s3_client.getS3Client();
24
25
  await s3.send(new clientS3.PutObjectCommand({
25
- Bucket: util_s3_client.BUCKET_WRITE_NAME,
26
- Key: util_s3_client.MANIFEST_KEY,
26
+ Bucket: constants.BUCKET_WRITE_NAME,
27
+ Key: constants.MANIFEST_KEY,
27
28
  Body: JSON.stringify(manifest, null, 2),
28
29
  ContentType: 'application/json',
29
30
  CacheControl: 'no-cache',
@@ -1,11 +1,10 @@
1
1
  import { S3Client } from '@aws-sdk/client-s3';
2
- export declare const BUCKET_WRITE_NAME = "prod-use1-bigbrain-zth-mf-assets";
3
- export declare const REGION = "us-east-1";
4
- export declare const BUCKET_WRITE_URL = "https://prod-use1-bigbrain-zth-mf-assets.s3.us-east-1.amazonaws.com";
5
- export declare const BUCKET_READ_DNS_NAME = "bigbrain-zth-mf-assets";
6
- export declare const BUCKET_READ_DNS_URL = "https://bigbrain-zth-mf-assets.bigbrain.me";
7
- export declare const MANIFEST_KEY = "mf-assets";
8
- export declare const MANIFEST_READ_URL = "https://bigbrain-zth-mf-assets.bigbrain.me/mf-assets";
2
+ /**
3
+ * Initialize the per-process S3 client with broker-issued credentials scoped
4
+ * to the given app's S3 prefix. Must be called once before any `getS3Client`.
5
+ */
6
+ export declare function setupS3Client({ appName }: {
7
+ appName: string;
8
+ }): void;
9
9
  export declare function getS3Client(): S3Client;
10
- export declare function assertBucketExists(): Promise<void>;
11
10
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/util/s3/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,QAAQ,EAAsB,MAAM,oBAAoB,CAAC;AAErF,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AACpE,eAAO,MAAM,MAAM,cAAc,CAAC;AAClC,eAAO,MAAM,gBAAgB,wEAA4D,CAAC;AAE1F,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,mBAAmB,+CAAgD,CAAC;AAKjF,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAI1E,wBAAgB,WAAW,IAAI,QAAQ,CAKtC;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAexD"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/util/s3/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAM9C;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKpE;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAKtC"}
@@ -1,48 +1,26 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const clientS3 = require('@aws-sdk/client-s3');
4
+ const util_auth_brokerCredentialProvider = require('../auth/broker-credential-provider.js');
5
+ const constants = require('../../constants.js');
4
6
 
5
- const BUCKET_WRITE_NAME = 'prod-use1-bigbrain-zth-mf-assets';
6
- const REGION = 'us-east-1';
7
- const BUCKET_WRITE_URL = `https://${BUCKET_WRITE_NAME}.s3.${REGION}.amazonaws.com`;
8
- const BUCKET_READ_DNS_NAME = 'bigbrain-zth-mf-assets';
9
- const BUCKET_READ_DNS_URL = `https://${BUCKET_READ_DNS_NAME}.bigbrain.me`;
10
- // No `.json` extension — TridentSkeleton's `mfUrl` API appends `/mf-assets`
11
- // to the bucket base when fetching, so the manifest must live at this exact
12
- // key. See microfrontends/mf-bigbrain-zth/.../ZeroToHeroHost.tsx.
13
- const MANIFEST_KEY = 'mf-assets';
14
- const MANIFEST_READ_URL = `${BUCKET_READ_DNS_URL}/${MANIFEST_KEY}`;
15
7
  let client = null;
8
+ /**
9
+ * Initialize the per-process S3 client with broker-issued credentials scoped
10
+ * to the given app's S3 prefix. Must be called once before any `getS3Client`.
11
+ */
12
+ function setupS3Client({ appName }) {
13
+ client = new clientS3.S3Client({
14
+ region: constants.REGION,
15
+ credentials: util_auth_brokerCredentialProvider.brokerCredentialProvider({ appName }),
16
+ });
17
+ }
16
18
  function getS3Client() {
17
19
  if (!client) {
18
- client = new clientS3.S3Client({ region: REGION });
20
+ throw new Error('S3 client not initialized — call setupS3Client({ appName }) first.');
19
21
  }
20
22
  return client;
21
23
  }
22
- async function assertBucketExists() {
23
- try {
24
- await getS3Client().send(new clientS3.HeadBucketCommand({ Bucket: BUCKET_WRITE_NAME }));
25
- }
26
- catch (err) {
27
- if (err instanceof clientS3.S3ServiceException) {
28
- const status = err.$metadata.httpStatusCode;
29
- if (status === 404 || err.name === 'NotFound' || err.name === 'NoSuchBucket') {
30
- throw new Error(`S3 bucket "${BUCKET_WRITE_NAME}" does not exist (region: ${REGION}).`);
31
- }
32
- if (status === 403) {
33
- throw new Error(`No access to S3 bucket "${BUCKET_WRITE_NAME}" (region: ${REGION}). Check AWS credentials.`);
34
- }
35
- }
36
- throw err;
37
- }
38
- }
39
24
 
40
- exports.BUCKET_READ_DNS_NAME = BUCKET_READ_DNS_NAME;
41
- exports.BUCKET_READ_DNS_URL = BUCKET_READ_DNS_URL;
42
- exports.BUCKET_WRITE_NAME = BUCKET_WRITE_NAME;
43
- exports.BUCKET_WRITE_URL = BUCKET_WRITE_URL;
44
- exports.MANIFEST_KEY = MANIFEST_KEY;
45
- exports.MANIFEST_READ_URL = MANIFEST_READ_URL;
46
- exports.REGION = REGION;
47
- exports.assertBucketExists = assertBucketExists;
48
25
  exports.getS3Client = getS3Client;
26
+ exports.setupS3Client = setupS3Client;
@@ -1 +1 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/util/s3/upload.ts"],"names":[],"mappings":"AAcA,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA0BtF"}
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/util/s3/upload.ts"],"names":[],"mappings":"AAeA,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA0BtF"}
@@ -6,6 +6,7 @@ const fsExtra = require('fs-extra');
6
6
  const mimeTypes = require('mime-types');
7
7
  const pMap = require('p-map');
8
8
  const clientS3 = require('@aws-sdk/client-s3');
9
+ const constants = require('../../constants.js');
9
10
  const util_s3_client = require('./client.js');
10
11
 
11
12
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
@@ -26,7 +27,7 @@ async function uploadDir(localDir, keyPrefix) {
26
27
  const contentType = mimeTypes.lookup(rel) || 'application/octet-stream';
27
28
  const key = `${keyPrefix}/${rel.split(path__default.default.sep).join('/')}`;
28
29
  await s3.send(new clientS3.PutObjectCommand({
29
- Bucket: util_s3_client.BUCKET_WRITE_NAME,
30
+ Bucket: constants.BUCKET_WRITE_NAME,
30
31
  Key: key,
31
32
  Body: body,
32
33
  ContentType: contentType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -10,6 +10,11 @@
10
10
  "require": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts"
12
12
  },
13
+ "./constants": {
14
+ "import": "./dist/esm/constants.mjs",
15
+ "require": "./dist/constants.js",
16
+ "types": "./dist/constants.d.ts"
17
+ },
13
18
  "./package.json": "./package.json"
14
19
  },
15
20
  "nx": {
@@ -35,7 +40,9 @@
35
40
  "@aws-sdk/client-s3": "^3.706.0",
36
41
  "chokidar": "^3.6.0",
37
42
  "commander": "^12.1.0",
43
+ "cors": "^2.8.6",
38
44
  "execa": "^9.5.1",
45
+ "express": "^5.2.1",
39
46
  "fs-extra": "^11.2.0",
40
47
  "mime-types": "^2.1.35",
41
48
  "p-map": "^7.0.2",
@@ -44,6 +51,8 @@
44
51
  },
45
52
  "devDependencies": {
46
53
  "@mondaydotcomorg/trident-library": "^1.2.7",
54
+ "@types/cors": "^2.8.19",
55
+ "@types/express": "^5.0.6",
47
56
  "@types/fs-extra": "^11.0.4",
48
57
  "@types/mime-types": "^2.1.4",
49
58
  "@types/node": "^22.19.0",
@@ -57,7 +57,7 @@ describe('create-workspace', () => {
57
57
  await ensureDir(tmp);
58
58
  await writeJson(path.join(tmp, 'somefile.json'), { hello: 'world' });
59
59
 
60
- await expect(createWorkspaceCommand({})).rejects.toThrow(/non-empty/);
60
+ await expect(createWorkspaceCommand({})).rejects.toThrow(/Workspace already exists/);
61
61
  expect(execa).not.toHaveBeenCalled();
62
62
  });
63
63
 
@@ -77,11 +77,7 @@ describe('create', () => {
77
77
  expect(content).toContain('export default function DemoApp()');
78
78
  expect(content).not.toContain('__APP_COMPONENT__');
79
79
 
80
- expect(execa).toHaveBeenCalledWith(
81
- 'yarn',
82
- expect.arrayContaining(['install']),
83
- expect.objectContaining({ cwd: tmp })
84
- );
80
+ expect(execa).toHaveBeenCalledWith('yarn', ['install'], expect.any(Object));
85
81
  });
86
82
 
87
83
  it('scaffolds an html-embed app and copies the html file', async () => {
@@ -38,7 +38,7 @@ export async function createWorkspaceCommand(opts: CreateWorkspaceOptions): Prom
38
38
  if (await pathExists(home)) {
39
39
  const entries = await readdir(home);
40
40
  if (entries.length > 0) {
41
- throw new Error(`${home} is non-empty. Wipe it before running create-workspace.`);
41
+ throw new Error(`Workspace already exists at ${home}. Remove it and try again.`);
42
42
  }
43
43
  } else {
44
44
  await ensureDir(home);
@@ -72,13 +72,13 @@ export async function createWorkspaceCommand(opts: CreateWorkspaceOptions): Prom
72
72
  ),
73
73
  ]);
74
74
 
75
- info(`bootstrapping workspace at ${home}`);
75
+ info(`setting up your Z2H workspace — this may take a minute...`);
76
76
  await execa('yarn', ['install'], {
77
77
  cwd: home,
78
78
  stdio: 'inherit',
79
79
  });
80
80
 
81
- output(`[z2h-cli] workspace ready at ${home}`, {
81
+ output(`Your workspace is ready! 🚀 Apps will live at ${home}`, {
82
82
  ok: true,
83
83
  workspace: home,
84
84
  z2hCliVersion: cliVersion,
@@ -7,6 +7,7 @@ import { assertWorkspace } from '../util/workspace';
7
7
  import { getTemplatesDir, renderTemplate, APP_TEMPLATES, type AppTemplate } from '../util/templates';
8
8
  import { readManifest } from '../util/manifest';
9
9
  import { mfAppName } from '../util/app-name';
10
+ import { APP_NAME_PATTERN } from '../constants';
10
11
 
11
12
  export interface CreateOptions {
12
13
  template?: string;
@@ -14,7 +15,10 @@ export interface CreateOptions {
14
15
  json?: boolean;
15
16
  }
16
17
 
17
- const APP_NAME_RE = /^[a-z][a-z0-9-]{1,49}$/;
18
+ // Names reserved by the host MF (mf-bigbrain-zth) for platform pages under
19
+ // /bigbrain-vibe/<name>. Creating an app with one of these would shadow the
20
+ // platform page in the URL, so we refuse upfront.
21
+ const RESERVED_APP_NAMES = new Set(['generate-z2h-token']);
18
22
 
19
23
  /**
20
24
  * Convert kebab-case to PascalCase. `demo-app` → `DemoApp`.
@@ -47,7 +51,12 @@ export async function createCommand(appName: string, opts: CreateOptions): Promi
47
51
  const workspace = await assertWorkspace();
48
52
 
49
53
  // Validate app name.
50
- if (!APP_NAME_RE.test(appName)) {
54
+ if (RESERVED_APP_NAMES.has(appName)) {
55
+ throw new Error(
56
+ `App name "${appName}" is reserved by the Z2H host for a platform page under /bigbrain-vibe/${appName}. Pick a different name.`
57
+ );
58
+ }
59
+ if (!APP_NAME_PATTERN.test(appName)) {
51
60
  throw new Error(
52
61
  `App name "${appName}" is invalid. Use lowercase kebab-case (a-z, 0-9, -), ` +
53
62
  `2-50 chars, starting with a letter.`
@@ -106,7 +115,7 @@ export async function createCommand(appName: string, opts: CreateOptions): Promi
106
115
  await copy(opts.htmlFile, path.join(appDir, 'src', 'page.html'));
107
116
  }
108
117
 
109
- info(`installing into workspace at ${workspace}`);
118
+ info(`installing dependencies for ${appName}...`);
110
119
  await execa('yarn', ['install'], {
111
120
  cwd: workspace,
112
121
  stdio: 'inherit',