@mondaydotcomorg/z2h-cli 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/__tests__/deploy.test.d.ts +2 -0
- package/dist/commands/__tests__/deploy.test.d.ts.map +1 -0
- package/dist/commands/build.d.ts +8 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +12 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +1 -12
- package/dist/commands/deploy.d.ts +3 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +69 -72
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +1 -11
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -5
- package/dist/esm/commands/__tests__/deploy.test.d.ts +2 -0
- package/dist/esm/commands/__tests__/deploy.test.d.ts.map +1 -0
- package/dist/esm/commands/build.d.ts +8 -1
- package/dist/esm/commands/build.d.ts.map +1 -1
- package/dist/esm/commands/build.mjs +9 -1
- package/dist/esm/commands/create.d.ts.map +1 -1
- package/dist/esm/commands/create.mjs +2 -13
- package/dist/esm/commands/deploy.d.ts +3 -0
- package/dist/esm/commands/deploy.d.ts.map +1 -1
- package/dist/esm/commands/deploy.mjs +72 -74
- package/dist/esm/commands/doctor.d.ts.map +1 -1
- package/dist/esm/commands/doctor.mjs +1 -11
- package/dist/esm/constants.d.ts +2 -0
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.mjs +9 -6
- package/dist/esm/index.mjs +9 -15
- package/dist/esm/migrations/006-remove-start-script.d.ts +3 -0
- package/dist/esm/migrations/006-remove-start-script.d.ts.map +1 -0
- package/dist/esm/migrations/006-remove-start-script.mjs +22 -0
- package/dist/esm/util/__tests__/app-name.test.d.ts +2 -0
- package/dist/esm/util/__tests__/app-name.test.d.ts.map +1 -0
- package/dist/esm/util/app-name.d.ts +17 -0
- package/dist/esm/util/app-name.d.ts.map +1 -1
- package/dist/esm/util/app-name.mjs +45 -1
- package/dist/esm/util/auth/broker-credential-provider.d.ts +7 -5
- package/dist/esm/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/esm/util/auth/broker-credential-provider.mjs +28 -16
- package/dist/esm/util/broker/__tests__/app.test.d.ts +2 -0
- package/dist/esm/util/broker/__tests__/app.test.d.ts.map +1 -0
- package/dist/esm/util/broker/app.d.ts +15 -0
- package/dist/esm/util/broker/app.d.ts.map +1 -1
- package/dist/esm/util/broker/app.mjs +44 -1
- package/dist/esm/util/prompt-visibility.d.ts +2 -0
- package/dist/esm/util/prompt-visibility.d.ts.map +1 -0
- package/dist/esm/util/prompt-visibility.mjs +19 -0
- package/dist/index.js +9 -15
- package/dist/migrations/006-remove-start-script.d.ts +3 -0
- package/dist/migrations/006-remove-start-script.d.ts.map +1 -0
- package/dist/migrations/006-remove-start-script.js +24 -0
- package/dist/util/__tests__/app-name.test.d.ts +2 -0
- package/dist/util/__tests__/app-name.test.d.ts.map +1 -0
- package/dist/util/app-name.d.ts +17 -0
- package/dist/util/app-name.d.ts.map +1 -1
- package/dist/util/app-name.js +49 -2
- package/dist/util/auth/broker-credential-provider.d.ts +7 -5
- package/dist/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/util/auth/broker-credential-provider.js +28 -16
- package/dist/util/broker/__tests__/app.test.d.ts +2 -0
- package/dist/util/broker/__tests__/app.test.d.ts.map +1 -0
- package/dist/util/broker/app.d.ts +15 -0
- package/dist/util/broker/app.d.ts.map +1 -1
- package/dist/util/broker/app.js +44 -0
- package/dist/util/prompt-visibility.d.ts +2 -0
- package/dist/util/prompt-visibility.d.ts.map +1 -0
- package/dist/util/prompt-visibility.js +21 -0
- package/migrations.json +5 -0
- package/package.json +1 -1
- package/src/commands/__tests__/create.test.ts +5 -0
- package/src/commands/__tests__/deploy.test.ts +215 -0
- package/src/commands/__tests__/doctor.test.ts +0 -12
- package/src/commands/build.ts +20 -2
- package/src/commands/create.ts +2 -18
- package/src/commands/deploy.ts +113 -93
- package/src/commands/doctor.ts +1 -20
- package/src/constants.ts +10 -5
- package/src/index.ts +31 -25
- package/src/migrations/006-remove-start-script.ts +24 -0
- package/src/shadow/__tests__/merge-package-json.test.ts +0 -1
- package/src/templates/app/CLAUDE.md +3 -3
- package/src/templates/app/package.json.tmpl +0 -1
- package/src/templates/workspace/README.md +2 -2
- package/src/util/__tests__/app-name.test.ts +78 -0
- package/src/util/app-name.ts +55 -0
- package/src/util/auth/__tests__/broker-credential-provider.test.ts +53 -1
- package/src/util/auth/broker-credential-provider.ts +41 -16
- package/src/util/broker/__tests__/app.test.ts +104 -0
- package/src/util/broker/app.ts +68 -0
- package/src/util/prompt-visibility.ts +18 -0
- package/dist/commands/dev.d.ts +0 -5
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js +0 -92
- package/dist/esm/commands/dev.d.ts +0 -5
- package/dist/esm/commands/dev.d.ts.map +0 -1
- package/dist/esm/commands/dev.mjs +0 -85
- package/src/commands/dev.ts +0 -93
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { brokerBaseUrl, routingHeaders } from '../../constants.mjs';
|
|
2
2
|
import { readZthAuth } from '../auth/auth-store.mjs';
|
|
3
|
+
import { info } from '../logger.mjs';
|
|
4
|
+
import { resolveAppVisibility } from '../prompt-visibility.mjs';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Fetches a single app's current state from the broker.
|
|
@@ -23,5 +25,46 @@ async function fetchApp(appName) {
|
|
|
23
25
|
}
|
|
24
26
|
return res.json();
|
|
25
27
|
}
|
|
28
|
+
async function registerApp(appName, token, version, entry, visibility, gitRemote) {
|
|
29
|
+
const res = await fetch(`${brokerBaseUrl()}/z2h-cli/apps/register`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...routingHeaders() },
|
|
32
|
+
body: JSON.stringify({ appName, version, entry, visibility, gitRemote }),
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
throw new Error(`App registration failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Releases a new version onto an app that already exists. */
|
|
40
|
+
async function updateApp(appName, token, version, entry, gitRemote) {
|
|
41
|
+
const res = await fetch(`${brokerBaseUrl()}/z2h-cli/apps/update`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...routingHeaders() },
|
|
44
|
+
body: JSON.stringify({ appName, version, entry, gitRemote }),
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const text = await res.text();
|
|
48
|
+
throw new Error(`App update failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* v1 creates the app row (+ owner grant, prompting for visibility); later
|
|
53
|
+
* versions release onto it via update instead — the two are separate
|
|
54
|
+
* endpoints, so this picks based on the version it's handed. Shared by
|
|
55
|
+
* deploy and preview so this branching logic lives in exactly one place.
|
|
56
|
+
*/
|
|
57
|
+
async function registerOrUpdateApp(opts) {
|
|
58
|
+
const { appName, token, nextVersion, entry, gitRemote } = opts;
|
|
59
|
+
if (nextVersion === 1) {
|
|
60
|
+
const visibility = await resolveAppVisibility(appName);
|
|
61
|
+
info(`registering "${appName}" → v${nextVersion} (${visibility})`);
|
|
62
|
+
await registerApp(appName, token, nextVersion, entry, visibility, gitRemote);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
info(`updating "${appName}" → v${nextVersion}`);
|
|
66
|
+
await updateApp(appName, token, nextVersion, entry, gitRemote);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
26
69
|
|
|
27
|
-
export { fetchApp };
|
|
70
|
+
export { fetchApp, registerOrUpdateApp };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-visibility.d.ts","sourceRoot":"","sources":["../../../src/util/prompt-visibility.ts"],"names":[],"mappings":"AAMA,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAWzF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { getOutputMode } from './logger.mjs';
|
|
3
|
+
|
|
4
|
+
// First registration only: decide whether the app is born public or private. The
|
|
5
|
+
// choice is passed to the register endpoint (default private). Non-interactive
|
|
6
|
+
// runs (--json or no TTY) always default to private.
|
|
7
|
+
async function resolveAppVisibility(appName) {
|
|
8
|
+
if (getOutputMode() === 'json' || !process.stdout.isTTY) {
|
|
9
|
+
return 'private';
|
|
10
|
+
}
|
|
11
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
const answer = await new Promise(resolve => {
|
|
13
|
+
rl.question(`Make "${appName}" public to all monday.com employees? (y/N) `, resolve);
|
|
14
|
+
});
|
|
15
|
+
rl.close();
|
|
16
|
+
return answer.trim().toLowerCase() === 'y' ? 'public' : 'private';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { resolveAppVisibility };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ const path = require('node:path');
|
|
|
4
4
|
const commander = require('commander');
|
|
5
5
|
const execa = require('execa');
|
|
6
6
|
const util_workspace = require('./util/workspace.js');
|
|
7
|
-
const commands_dev = require('./commands/dev.js');
|
|
8
7
|
const commands_build = require('./commands/build.js');
|
|
9
8
|
const commands_clean = require('./commands/clean.js');
|
|
10
9
|
const commands_deploy = require('./commands/deploy.js');
|
|
@@ -85,18 +84,6 @@ async function main() {
|
|
|
85
84
|
await util_logger.fail(`build failed: ${err.message}`, { command: 'build' }, 2);
|
|
86
85
|
}
|
|
87
86
|
});
|
|
88
|
-
program
|
|
89
|
-
.command('dev')
|
|
90
|
-
.description('Start the Trident dev server against .zth/ with native Vite HMR')
|
|
91
|
-
.option('--open', 'open the browser automatically when the dev server is ready')
|
|
92
|
-
.action(async (opts) => {
|
|
93
|
-
try {
|
|
94
|
-
await commands_dev.devCommand(opts);
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
await util_logger.fail(`dev failed: ${err.message}`, { command: 'dev' }, 2);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
87
|
program
|
|
101
88
|
.command('clean')
|
|
102
89
|
.description('Delete <consumer>/.zth/ and <consumer>/build/')
|
|
@@ -112,16 +99,23 @@ async function main() {
|
|
|
112
99
|
.command('deploy')
|
|
113
100
|
.description('Build, upload to S3, and register the app with the Z2H backend')
|
|
114
101
|
.option('--dry-run', 'print what would happen without uploading anything')
|
|
102
|
+
.option('--preview [previewId]', 'build an ephemeral, shareable preview instead of deploying — no git required, no deploy lock, never touches the live version; pass an existing previewId to update it instead of minting a new one')
|
|
103
|
+
.option('--open', 'open the resulting app/preview URL in the browser on success')
|
|
115
104
|
.option('--remote <type>', 'First deploy only: source control remote to initialise — s3 (shared storage) or github (your repo). Error if the app already has a git remote.')
|
|
116
105
|
.option('--repo-url <url>', 'First deploy only: GitHub repo URL — required when --remote=github. Error if the app already has a git remote.')
|
|
117
106
|
.action(async (opts) => {
|
|
118
107
|
try {
|
|
119
|
-
await util_tracker.trackDuration('z2h_app_deploy_success', () => commands_deploy.deployCommand(
|
|
108
|
+
await util_tracker.trackDuration(opts.preview ? 'z2h_app_preview_success' : 'z2h_app_deploy_success', () => commands_deploy.deployCommand({
|
|
109
|
+
...opts,
|
|
110
|
+
preview: Boolean(opts.preview),
|
|
111
|
+
previewId: typeof opts.preview === 'string' ? opts.preview : undefined,
|
|
112
|
+
}), {
|
|
120
113
|
app_name: util_appName.getCwdAppName(),
|
|
121
114
|
});
|
|
122
115
|
}
|
|
123
116
|
catch (err) {
|
|
124
|
-
|
|
117
|
+
const label = opts.preview ? 'preview' : 'deploy';
|
|
118
|
+
await util_logger.fail(`${label} failed: ${err.message}`, { command: label }, 2);
|
|
125
119
|
}
|
|
126
120
|
});
|
|
127
121
|
program
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"006-remove-start-script.d.ts","sourceRoot":"","sources":["../../src/migrations/006-remove-start-script.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMvC,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAiB1D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
|
|
2
|
+
|
|
3
|
+
// `dev` is gone from the CLI (superseded by `deploy --preview`), so any
|
|
4
|
+
// consumer's `start` script still pointing at it is now a dangling command.
|
|
5
|
+
// Only removes the exact value the template ever wrote — leaves a
|
|
6
|
+
// consumer-customized `start` script alone.
|
|
7
|
+
function removeStartScript(tree) {
|
|
8
|
+
for (const entry of tree.children('.')) {
|
|
9
|
+
if (entry.startsWith('.')) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const pkgPath = `${entry}/package.json`;
|
|
13
|
+
if (!tree.exists(pkgPath)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const pkg = JSON.parse(tree.read(pkgPath, 'utf-8'));
|
|
17
|
+
if (pkg.scripts?.start === 'z2h-cli dev') {
|
|
18
|
+
delete pkg.scripts.start;
|
|
19
|
+
tree.write(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.default = removeStartScript;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-name.test.d.ts","sourceRoot":"","sources":["../../../src/util/__tests__/app-name.test.ts"],"names":[],"mappings":""}
|
package/dist/util/app-name.d.ts
CHANGED
|
@@ -10,4 +10,21 @@ export declare function getCwdAppName(): string | undefined;
|
|
|
10
10
|
* emitted bundle is `MF-mf-<app>`).
|
|
11
11
|
*/
|
|
12
12
|
export declare function mfAppName(name: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Validates a de-scoped, kebab-case app name against every rule shared by
|
|
15
|
+
* `create` and `deploy` (incl. `deploy --preview`) — pattern, reserved
|
|
16
|
+
* platform names, and the preview-prefix reservation. Throws with a
|
|
17
|
+
* friendly message on the first violation.
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateAppName(appName: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Builds the slug for a preview's synthetic RDS app row:
|
|
22
|
+
* `zth-preview-<shortid>-<realAppName>`, pre-`mf-`-prefixing. Uniqueness
|
|
23
|
+
* comes entirely from `shortId` (derived from the previewId, already a
|
|
24
|
+
* randomUUID() — 32 bits of entropy); truncating the human-readable tail to
|
|
25
|
+
* fit the cap is safe on its own, a shortId collision is astronomically
|
|
26
|
+
* unlikely at POC scale and would just 409 on registerApp for a retry.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildPreviewSlug(appName: string, previewId: string): string;
|
|
29
|
+
export declare function isPreviewAppName(appName: string): boolean;
|
|
13
30
|
//# sourceMappingURL=app-name.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-name.d.ts","sourceRoot":"","sources":["../../src/util/app-name.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app-name.d.ts","sourceRoot":"","sources":["../../src/util/app-name.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAMlD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAOD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAiBrD;AAMD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM3E;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD"}
|
package/dist/util/app-name.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const node_fs = require('node:fs');
|
|
4
|
+
const constants = require('../constants.js');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Strip any npm scope (`@scope/`) from a package name.
|
|
@@ -11,7 +12,7 @@ function cleanAppName(name) {
|
|
|
11
12
|
}
|
|
12
13
|
function getCwdAppName() {
|
|
13
14
|
try {
|
|
14
|
-
return JSON.parse(
|
|
15
|
+
return JSON.parse(node_fs.readFileSync('package.json', 'utf8')).name;
|
|
15
16
|
}
|
|
16
17
|
catch {
|
|
17
18
|
return undefined;
|
|
@@ -25,7 +26,53 @@ function getCwdAppName() {
|
|
|
25
26
|
function mfAppName(name) {
|
|
26
27
|
return `mf-${cleanAppName(name)}`;
|
|
27
28
|
}
|
|
29
|
+
// Names reserved by the host MF (mf-bigbrain-zth) for platform pages under
|
|
30
|
+
// /bigbrain-vibe/<name>. Creating an app with one of these would shadow the
|
|
31
|
+
// platform page in the URL, so we refuse upfront.
|
|
32
|
+
const RESERVED_APP_NAMES = new Set(['generate-z2h-token']);
|
|
33
|
+
/**
|
|
34
|
+
* Validates a de-scoped, kebab-case app name against every rule shared by
|
|
35
|
+
* `create` and `deploy` (incl. `deploy --preview`) — pattern, reserved
|
|
36
|
+
* platform names, and the preview-prefix reservation. Throws with a
|
|
37
|
+
* friendly message on the first violation.
|
|
38
|
+
*/
|
|
39
|
+
function validateAppName(appName) {
|
|
40
|
+
if (RESERVED_APP_NAMES.has(appName)) {
|
|
41
|
+
throw new Error(`App name "${appName}" is reserved by the Z2H host for a platform page under /bigbrain-vibe/${appName}. Pick a different name.`);
|
|
42
|
+
}
|
|
43
|
+
if (!constants.APP_NAME_PATTERN.test(appName)) {
|
|
44
|
+
throw new Error(`App name "${appName}" is invalid. Use lowercase kebab-case (a-z, 0-9, -), ` +
|
|
45
|
+
`2-50 chars, starting with a letter.`);
|
|
46
|
+
}
|
|
47
|
+
if (appName.startsWith(constants.PREVIEW_APP_NAME_PREFIX)) {
|
|
48
|
+
throw new Error(`App names starting with "${constants.PREVIEW_APP_NAME_PREFIX}" are reserved for the ephemeral preview mechanism. Pick a different name.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Preview slugs are capped here so the mf-prefixed name (`mf-` + slug) stays
|
|
52
|
+
// within APP_NAME_PATTERN's 50-char limit (3 chars reserved for `mf-`).
|
|
53
|
+
const PREVIEW_SLUG_MAX_LEN = 47;
|
|
54
|
+
/**
|
|
55
|
+
* Builds the slug for a preview's synthetic RDS app row:
|
|
56
|
+
* `zth-preview-<shortid>-<realAppName>`, pre-`mf-`-prefixing. Uniqueness
|
|
57
|
+
* comes entirely from `shortId` (derived from the previewId, already a
|
|
58
|
+
* randomUUID() — 32 bits of entropy); truncating the human-readable tail to
|
|
59
|
+
* fit the cap is safe on its own, a shortId collision is astronomically
|
|
60
|
+
* unlikely at POC scale and would just 409 on registerApp for a retry.
|
|
61
|
+
*/
|
|
62
|
+
function buildPreviewSlug(appName, previewId) {
|
|
63
|
+
const shortId = previewId.replace(/-/g, '').slice(0, 8);
|
|
64
|
+
const base = `${constants.PREVIEW_APP_NAME_PREFIX}${shortId}-${cleanAppName(appName)}`;
|
|
65
|
+
// Trailing hyphens are stripped post-slice — a cut that lands right after a
|
|
66
|
+
// hyphen in appName would otherwise leave one dangling.
|
|
67
|
+
return base.slice(0, PREVIEW_SLUG_MAX_LEN).replace(/-+$/, '');
|
|
68
|
+
}
|
|
69
|
+
function isPreviewAppName(appName) {
|
|
70
|
+
return appName.startsWith(`mf-${constants.PREVIEW_APP_NAME_PREFIX}`);
|
|
71
|
+
}
|
|
28
72
|
|
|
73
|
+
exports.buildPreviewSlug = buildPreviewSlug;
|
|
29
74
|
exports.cleanAppName = cleanAppName;
|
|
30
75
|
exports.getCwdAppName = getCwdAppName;
|
|
76
|
+
exports.isPreviewAppName = isPreviewAppName;
|
|
31
77
|
exports.mfAppName = mfAppName;
|
|
78
|
+
exports.validateAppName = validateAppName;
|
|
@@ -8,12 +8,14 @@ export declare const DEPLOY_VERSION_PATH = "/z2h-cli/apps/version";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function fetchDeployVersion(appName: string): Promise<number>;
|
|
10
10
|
/**
|
|
11
|
-
* AWS SDK credential provider that fetches short-lived
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* AWS SDK credential provider that fetches short-lived credentials from the
|
|
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.
|
|
15
16
|
*/
|
|
16
|
-
export declare function brokerCredentialProvider({ appName }: {
|
|
17
|
+
export declare function brokerCredentialProvider({ appName, previewId, }: {
|
|
17
18
|
appName: string;
|
|
19
|
+
previewId?: string;
|
|
18
20
|
}): AwsCredentialIdentityProvider;
|
|
19
21
|
//# sourceMappingURL=broker-credential-provider.d.ts.map
|
|
@@ -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;
|
|
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"}
|
|
@@ -47,31 +47,33 @@ async function fetchDeployVersion(appName) {
|
|
|
47
47
|
const body = (await res.json());
|
|
48
48
|
return body.version ?? 0;
|
|
49
49
|
}
|
|
50
|
-
async function
|
|
51
|
-
const res = await postToBroker(
|
|
50
|
+
async function requestSts(path, body, errorLabel) {
|
|
51
|
+
const res = await postToBroker(path, body);
|
|
52
52
|
assertBrokerOk(res);
|
|
53
53
|
if (!res.ok) {
|
|
54
54
|
const text = await res.text();
|
|
55
|
-
throw new Error(`Z2H
|
|
55
|
+
throw new Error(`Z2H ${errorLabel} broker returned ${res.status}: ${text.slice(0, 1024)}`);
|
|
56
56
|
}
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
-
|
|
57
|
+
const responseBody = (await res.json());
|
|
58
|
+
if (!responseBody.accessKeyId ||
|
|
59
|
+
!responseBody.secretAccessKey ||
|
|
60
|
+
!responseBody.sessionToken ||
|
|
61
|
+
!responseBody.expiration) {
|
|
62
|
+
throw new Error(`Z2H ${errorLabel} broker response missing required credential fields`);
|
|
60
63
|
}
|
|
61
64
|
return {
|
|
62
|
-
accessKeyId:
|
|
63
|
-
secretAccessKey:
|
|
64
|
-
sessionToken:
|
|
65
|
-
expiration: new Date(
|
|
65
|
+
accessKeyId: responseBody.accessKeyId,
|
|
66
|
+
secretAccessKey: responseBody.secretAccessKey,
|
|
67
|
+
sessionToken: responseBody.sessionToken,
|
|
68
|
+
expiration: new Date(responseBody.expiration),
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* single in-flight fetch.
|
|
72
|
+
* Wraps a raw STS fetch in the shared cache/coalescing state machine:
|
|
73
|
+
* credentials are cached in memory until 5 minutes before expiry, and
|
|
74
|
+
* concurrent calls share a single in-flight fetch.
|
|
73
75
|
*/
|
|
74
|
-
function
|
|
76
|
+
function createCachedCredentialProvider(fetchOnce) {
|
|
75
77
|
let cached;
|
|
76
78
|
let inflight;
|
|
77
79
|
return async () => {
|
|
@@ -82,7 +84,7 @@ function brokerCredentialProvider({ appName }) {
|
|
|
82
84
|
if (inflight) {
|
|
83
85
|
return inflight;
|
|
84
86
|
}
|
|
85
|
-
inflight =
|
|
87
|
+
inflight = fetchOnce()
|
|
86
88
|
.then(credentials => {
|
|
87
89
|
cached = credentials;
|
|
88
90
|
return credentials;
|
|
@@ -93,6 +95,16 @@ function brokerCredentialProvider({ appName }) {
|
|
|
93
95
|
return inflight;
|
|
94
96
|
};
|
|
95
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* AWS SDK credential provider that fetches short-lived credentials from the
|
|
100
|
+
* 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
|
+
*/
|
|
105
|
+
function brokerCredentialProvider({ appName, previewId, }) {
|
|
106
|
+
return createCachedCredentialProvider(() => requestSts(STS_BROKER_PATH, { appName, previewId }, previewId ? 'preview' : 'deploy'));
|
|
107
|
+
}
|
|
96
108
|
|
|
97
109
|
exports.DEPLOY_VERSION_PATH = DEPLOY_VERSION_PATH;
|
|
98
110
|
exports.STS_BROKER_PATH = STS_BROKER_PATH;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.test.d.ts","sourceRoot":"","sources":["../../../../src/util/broker/__tests__/app.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ManifestEntry } from '../manifest';
|
|
1
2
|
export interface AppInfo {
|
|
2
3
|
appName: string;
|
|
3
4
|
version: number;
|
|
@@ -11,4 +12,18 @@ export interface AppInfo {
|
|
|
11
12
|
* Returns null if the app has never been deployed (404).
|
|
12
13
|
*/
|
|
13
14
|
export declare function fetchApp(appName: string): Promise<AppInfo | null>;
|
|
15
|
+
export interface RegisterOrUpdateOptions {
|
|
16
|
+
appName: string;
|
|
17
|
+
token: string;
|
|
18
|
+
nextVersion: number;
|
|
19
|
+
entry: ManifestEntry;
|
|
20
|
+
gitRemote: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* v1 creates the app row (+ owner grant, prompting for visibility); later
|
|
24
|
+
* versions release onto it via update instead — the two are separate
|
|
25
|
+
* endpoints, so this picks based on the version it's handed. Shared by
|
|
26
|
+
* deploy and preview so this branching logic lives in exactly one place.
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerOrUpdateApp(opts: RegisterOrUpdateOptions): Promise<void>;
|
|
14
29
|
//# sourceMappingURL=app.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/util/broker/app.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/util/broker/app.js
CHANGED
|
@@ -2,6 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
|
|
3
3
|
const constants = require('../../constants.js');
|
|
4
4
|
const util_auth_authStore = require('../auth/auth-store.js');
|
|
5
|
+
const util_logger = require('../logger.js');
|
|
6
|
+
const util_promptVisibility = require('../prompt-visibility.js');
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Fetches a single app's current state from the broker.
|
|
@@ -25,5 +27,47 @@ async function fetchApp(appName) {
|
|
|
25
27
|
}
|
|
26
28
|
return res.json();
|
|
27
29
|
}
|
|
30
|
+
async function registerApp(appName, token, version, entry, visibility, gitRemote) {
|
|
31
|
+
const res = await fetch(`${constants.brokerBaseUrl()}/z2h-cli/apps/register`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...constants.routingHeaders() },
|
|
34
|
+
body: JSON.stringify({ appName, version, entry, visibility, gitRemote }),
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const text = await res.text();
|
|
38
|
+
throw new Error(`App registration failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Releases a new version onto an app that already exists. */
|
|
42
|
+
async function updateApp(appName, token, version, entry, gitRemote) {
|
|
43
|
+
const res = await fetch(`${constants.brokerBaseUrl()}/z2h-cli/apps/update`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...constants.routingHeaders() },
|
|
46
|
+
body: JSON.stringify({ appName, version, entry, gitRemote }),
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
throw new Error(`App update failed (${res.status}): ${text.slice(0, 1024)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* v1 creates the app row (+ owner grant, prompting for visibility); later
|
|
55
|
+
* versions release onto it via update instead — the two are separate
|
|
56
|
+
* endpoints, so this picks based on the version it's handed. Shared by
|
|
57
|
+
* deploy and preview so this branching logic lives in exactly one place.
|
|
58
|
+
*/
|
|
59
|
+
async function registerOrUpdateApp(opts) {
|
|
60
|
+
const { appName, token, nextVersion, entry, gitRemote } = opts;
|
|
61
|
+
if (nextVersion === 1) {
|
|
62
|
+
const visibility = await util_promptVisibility.resolveAppVisibility(appName);
|
|
63
|
+
util_logger.info(`registering "${appName}" → v${nextVersion} (${visibility})`);
|
|
64
|
+
await registerApp(appName, token, nextVersion, entry, visibility, gitRemote);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
util_logger.info(`updating "${appName}" → v${nextVersion}`);
|
|
68
|
+
await updateApp(appName, token, nextVersion, entry, gitRemote);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
28
71
|
|
|
29
72
|
exports.fetchApp = fetchApp;
|
|
73
|
+
exports.registerOrUpdateApp = registerOrUpdateApp;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-visibility.d.ts","sourceRoot":"","sources":["../../src/util/prompt-visibility.ts"],"names":[],"mappings":"AAMA,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAWzF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const node_readline = require('node:readline');
|
|
4
|
+
const util_logger = require('./logger.js');
|
|
5
|
+
|
|
6
|
+
// First registration only: decide whether the app is born public or private. The
|
|
7
|
+
// choice is passed to the register endpoint (default private). Non-interactive
|
|
8
|
+
// runs (--json or no TTY) always default to private.
|
|
9
|
+
async function resolveAppVisibility(appName) {
|
|
10
|
+
if (util_logger.getOutputMode() === 'json' || !process.stdout.isTTY) {
|
|
11
|
+
return 'private';
|
|
12
|
+
}
|
|
13
|
+
const rl = node_readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
const answer = await new Promise(resolve => {
|
|
15
|
+
rl.question(`Make "${appName}" public to all monday.com employees? (y/N) `, resolve);
|
|
16
|
+
});
|
|
17
|
+
rl.close();
|
|
18
|
+
return answer.trim().toLowerCase() === 'y' ? 'public' : 'private';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.resolveAppVisibility = resolveAppVisibility;
|
package/migrations.json
CHANGED
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"version": "0.11.5",
|
|
25
25
|
"description": "Fix shadow .zth/package.json trident-toolkit \"*\" → workspace-root pinned range to prevent react version conflict",
|
|
26
26
|
"migrate": "./dist/migrations/005-fix-shadow-toolkit-wildcard"
|
|
27
|
+
},
|
|
28
|
+
"remove-start-script": {
|
|
29
|
+
"version": "0.15.3",
|
|
30
|
+
"description": "Remove the start script (z2h-cli dev) from consumer package.json — dev is gone, superseded by deploy --preview",
|
|
31
|
+
"migrate": "./dist/migrations/006-remove-start-script"
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
}
|
package/package.json
CHANGED
|
@@ -68,6 +68,11 @@ describe('create', () => {
|
|
|
68
68
|
await expect(createCommand('1starts-with-digit')).rejects.toThrow(/lowercase kebab-case/);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it('rejects app names starting with the reserved preview prefix', async () => {
|
|
72
|
+
await makeWorkspace(tmp);
|
|
73
|
+
await expect(createCommand('zth-preview-my-app')).rejects.toThrow(/reserved for the ephemeral preview mechanism/);
|
|
74
|
+
});
|
|
75
|
+
|
|
71
76
|
it('refuses when the target app dir already exists and is non-empty', async () => {
|
|
72
77
|
await makeWorkspace(tmp);
|
|
73
78
|
const appDir = path.join(tmp, 'demo-app');
|