@mondaydotcomorg/z2h-cli 0.15.0 → 0.15.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/build.d.ts +0 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +0 -3
- package/dist/commands/clean.d.ts +1 -4
- package/dist/commands/clean.d.ts.map +1 -1
- package/dist/commands/clean.js +1 -4
- package/dist/commands/create-workspace.d.ts +1 -4
- package/dist/commands/create-workspace.d.ts.map +1 -1
- package/dist/commands/create-workspace.js +1 -4
- package/dist/commands/create.d.ts +0 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +0 -3
- package/dist/commands/delete.d.ts +0 -1
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +1 -4
- package/dist/commands/deploy.d.ts +0 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +4 -7
- package/dist/commands/dev.d.ts +0 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +0 -3
- package/dist/commands/doctor.d.ts +1 -4
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -5
- package/dist/commands/edit.d.ts +1 -4
- package/dist/commands/edit.d.ts.map +1 -1
- package/dist/commands/edit.js +1 -4
- package/dist/commands/generate-z2h-token.d.ts +0 -1
- package/dist/commands/generate-z2h-token.d.ts.map +1 -1
- package/dist/commands/generate-z2h-token.js +0 -3
- package/dist/commands/grant.d.ts +1 -5
- package/dist/commands/grant.d.ts.map +1 -1
- package/dist/commands/grant.js +1 -10
- package/dist/commands/migrate.d.ts +0 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +106 -4
- package/dist/commands/tag.d.ts +0 -1
- package/dist/commands/tag.d.ts.map +1 -1
- package/dist/commands/tag.js +1 -4
- package/dist/esm/commands/build.d.ts +0 -1
- package/dist/esm/commands/build.d.ts.map +1 -1
- package/dist/esm/commands/build.mjs +1 -4
- package/dist/esm/commands/clean.d.ts +1 -4
- package/dist/esm/commands/clean.d.ts.map +1 -1
- package/dist/esm/commands/clean.mjs +2 -5
- package/dist/esm/commands/create-workspace.d.ts +1 -4
- package/dist/esm/commands/create-workspace.d.ts.map +1 -1
- package/dist/esm/commands/create-workspace.mjs +2 -5
- package/dist/esm/commands/create.d.ts +0 -1
- package/dist/esm/commands/create.d.ts.map +1 -1
- package/dist/esm/commands/create.mjs +1 -4
- package/dist/esm/commands/delete.d.ts +0 -1
- package/dist/esm/commands/delete.d.ts.map +1 -1
- package/dist/esm/commands/delete.mjs +2 -5
- package/dist/esm/commands/deploy.d.ts +0 -1
- package/dist/esm/commands/deploy.d.ts.map +1 -1
- package/dist/esm/commands/deploy.mjs +5 -8
- package/dist/esm/commands/dev.d.ts +0 -1
- package/dist/esm/commands/dev.d.ts.map +1 -1
- package/dist/esm/commands/dev.mjs +1 -4
- package/dist/esm/commands/doctor.d.ts +1 -4
- package/dist/esm/commands/doctor.d.ts.map +1 -1
- package/dist/esm/commands/doctor.mjs +3 -6
- package/dist/esm/commands/edit.d.ts +1 -4
- package/dist/esm/commands/edit.d.ts.map +1 -1
- package/dist/esm/commands/edit.mjs +2 -5
- package/dist/esm/commands/generate-z2h-token.d.ts +0 -1
- package/dist/esm/commands/generate-z2h-token.d.ts.map +1 -1
- package/dist/esm/commands/generate-z2h-token.mjs +1 -4
- package/dist/esm/commands/grant.d.ts +1 -5
- package/dist/esm/commands/grant.d.ts.map +1 -1
- package/dist/esm/commands/grant.mjs +2 -11
- package/dist/esm/commands/migrate.d.ts +0 -1
- package/dist/esm/commands/migrate.d.ts.map +1 -1
- package/dist/esm/commands/migrate.mjs +101 -5
- package/dist/esm/commands/tag.d.ts +0 -1
- package/dist/esm/commands/tag.d.ts.map +1 -1
- package/dist/esm/commands/tag.mjs +2 -5
- package/dist/esm/index.mjs +20 -29
- package/dist/esm/util/workspace.d.ts +3 -0
- package/dist/esm/util/workspace.d.ts.map +1 -1
- package/dist/esm/util/workspace.mjs +64 -2
- package/dist/index.js +19 -28
- package/dist/util/workspace.d.ts +3 -0
- package/dist/util/workspace.d.ts.map +1 -1
- package/dist/util/workspace.js +65 -0
- package/package.json +1 -1
- package/src/commands/__tests__/doctor.test.ts +12 -5
- package/src/commands/build.ts +1 -6
- package/src/commands/clean.ts +2 -9
- package/src/commands/create-workspace.ts +2 -10
- package/src/commands/create.ts +1 -6
- package/src/commands/delete.ts +2 -7
- package/src/commands/deploy.ts +5 -10
- package/src/commands/dev.ts +1 -6
- package/src/commands/doctor.ts +3 -11
- package/src/commands/edit.ts +2 -10
- package/src/commands/generate-z2h-token.ts +1 -6
- package/src/commands/grant.ts +2 -16
- package/src/commands/migrate.ts +139 -6
- package/src/commands/tag.ts +2 -7
- package/src/index.ts +29 -41
- package/src/util/workspace.ts +75 -1
- package/dist/esm/migrations/auto-migrate.d.ts +0 -2
- package/dist/esm/migrations/auto-migrate.d.ts.map +0 -1
- package/dist/esm/migrations/auto-migrate.mjs +0 -169
- package/dist/migrations/auto-migrate.d.ts +0 -2
- package/dist/migrations/auto-migrate.d.ts.map +0 -1
- package/dist/migrations/auto-migrate.js +0 -177
- package/src/migrations/auto-migrate.ts +0 -217
package/src/commands/delete.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
|
-
import { output, info,
|
|
2
|
+
import { output, info, getOutputMode } from '../util/logger';
|
|
3
3
|
import { getCwdAppName, cleanAppName, mfAppName } from '../util/app-name';
|
|
4
4
|
import { brokerBaseUrl, routingHeaders } from '../constants';
|
|
5
5
|
import { readZthAuth } from '../util/auth/auth-store';
|
|
6
6
|
|
|
7
7
|
export interface DeleteOptions {
|
|
8
8
|
yes?: boolean;
|
|
9
|
-
json?: boolean;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
// Type-the-app-name confirmation. The caller must retype the de-scoped app
|
|
@@ -26,10 +25,6 @@ async function confirmByName(appSlug: string): Promise<boolean> {
|
|
|
26
25
|
|
|
27
26
|
// Should be run from the consumer app directory — uses process.cwd() to locate it.
|
|
28
27
|
export async function deleteCommand(opts: DeleteOptions = {}): Promise<void> {
|
|
29
|
-
if (opts.json) {
|
|
30
|
-
setOutputMode('json');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
28
|
const cwdName = getCwdAppName();
|
|
34
29
|
if (!cwdName) {
|
|
35
30
|
throw new Error('Run this from inside a consumer app directory (the one with your package.json).');
|
|
@@ -39,7 +34,7 @@ export async function deleteCommand(opts: DeleteOptions = {}): Promise<void> {
|
|
|
39
34
|
|
|
40
35
|
// Confirmation. --json is always non-interactive, so it requires --yes.
|
|
41
36
|
if (!opts.yes) {
|
|
42
|
-
if (
|
|
37
|
+
if (getOutputMode() === 'json' || !process.stdin.isTTY) {
|
|
43
38
|
throw new Error('Refusing to delete without confirmation — pass --yes to delete non-interactively.');
|
|
44
39
|
}
|
|
45
40
|
if (!(await confirmByName(appSlug))) {
|
package/src/commands/deploy.ts
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
4
|
import { pathExists, readJson } from 'fs-extra';
|
|
5
|
-
import { info, warn, output,
|
|
5
|
+
import { info, warn, output, getOutputMode } from '../util/logger';
|
|
6
6
|
import { resolveConsumerPaths } from '../shadow/paths';
|
|
7
7
|
import { fetchAppTags, fetchTagCatalog, setAppTags } from '../util/tags';
|
|
8
8
|
import { type AssetManifestFile, type ManifestEntry } from '../util/manifest';
|
|
@@ -21,17 +21,12 @@ import { buildCommand } from './build';
|
|
|
21
21
|
|
|
22
22
|
export interface DeployOptions {
|
|
23
23
|
dryRun?: boolean;
|
|
24
|
-
json?: boolean;
|
|
25
24
|
remote?: RemoteType;
|
|
26
25
|
repoUrl?: string;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
// Should be run from the consumer app directory — uses process.cwd() to locate it.
|
|
30
29
|
export async function deployCommand(opts: DeployOptions = {}): Promise<void> {
|
|
31
|
-
if (opts.json) {
|
|
32
|
-
setOutputMode('json');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
30
|
const paths = resolveConsumerPaths(process.cwd());
|
|
36
31
|
const consumerPkg = (await readJson(paths.consumerPkg)) as { name?: string };
|
|
37
32
|
if (!consumerPkg.name) {
|
|
@@ -90,7 +85,7 @@ export async function deployCommand(opts: DeployOptions = {}): Promise<void> {
|
|
|
90
85
|
const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
|
|
91
86
|
|
|
92
87
|
info('building');
|
|
93
|
-
await buildCommand({
|
|
88
|
+
await buildCommand({ publicUrl: url });
|
|
94
89
|
|
|
95
90
|
if (!(await pathExists(paths.shadowBuild))) {
|
|
96
91
|
throw new Error(`No build output at ${paths.shadowBuild} after build step.`);
|
|
@@ -149,7 +144,7 @@ export async function deployCommand(opts: DeployOptions = {}): Promise<void> {
|
|
|
149
144
|
// The two are separate endpoints, so the CLI picks based on the version it
|
|
150
145
|
// was handed.
|
|
151
146
|
if (nextVersion === 1) {
|
|
152
|
-
const visibility = await resolveFirstDeployVisibility(appName
|
|
147
|
+
const visibility = await resolveFirstDeployVisibility(appName);
|
|
153
148
|
info(`registering app "${appName}" → v${nextVersion} (${visibility})`);
|
|
154
149
|
await registerApp(appName, token, nextVersion, entry, visibility, gitRemote);
|
|
155
150
|
} else {
|
|
@@ -262,8 +257,8 @@ async function updateApp(
|
|
|
262
257
|
// First deploy only: decide whether the app is born public or private. The
|
|
263
258
|
// choice is passed to the register endpoint (default private). Non-interactive
|
|
264
259
|
// runs (--json or no TTY) always default to private.
|
|
265
|
-
async function resolveFirstDeployVisibility(appName: string
|
|
266
|
-
if (json || !process.stdout.isTTY) {
|
|
260
|
+
async function resolveFirstDeployVisibility(appName: string): Promise<'public' | 'private'> {
|
|
261
|
+
if (getOutputMode() === 'json' || !process.stdout.isTTY) {
|
|
267
262
|
return 'private';
|
|
268
263
|
}
|
|
269
264
|
|
package/src/commands/dev.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import { readJson, writeFile } from 'fs-extra';
|
|
4
4
|
import { watch } from 'chokidar';
|
|
5
5
|
import { execa } from 'execa';
|
|
6
|
-
import { info, warn, errorLog, output
|
|
6
|
+
import { info, warn, errorLog, output } from '../util/logger';
|
|
7
7
|
import { prepareShadow } from '../shadow/prepare';
|
|
8
8
|
import { ensureConsumerPkg } from '../shadow/consumer-pkg';
|
|
9
9
|
import { syncShadow } from '../shadow/sync';
|
|
@@ -11,7 +11,6 @@ import { openInBrowser } from '../util/open-browser';
|
|
|
11
11
|
import type { ConsumerPkg } from '../types';
|
|
12
12
|
|
|
13
13
|
export interface DevOptions {
|
|
14
|
-
json?: boolean;
|
|
15
14
|
open?: boolean;
|
|
16
15
|
}
|
|
17
16
|
|
|
@@ -19,10 +18,6 @@ const TRIDENT_READY_PATTERN = /Server started at/i;
|
|
|
19
18
|
|
|
20
19
|
// Should be run from the consumer app directory — uses process.cwd() to locate it.
|
|
21
20
|
export async function devCommand(opts: DevOptions = {}): Promise<void> {
|
|
22
|
-
if (opts.json) {
|
|
23
|
-
setOutputMode('json');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
21
|
const ctx = await prepareShadow();
|
|
27
22
|
const { port } = ctx;
|
|
28
23
|
const url = `https://bigbrain.me/bigbrain-vibe/${ctx.appName}?zth_local=${port}`;
|
package/src/commands/doctor.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { execa } from 'execa';
|
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
import { pathExists, readJson } from 'fs-extra';
|
|
8
8
|
import pc from 'picocolors';
|
|
9
|
-
import {
|
|
9
|
+
import { output, getOutputMode } from '../util/logger';
|
|
10
10
|
import { isZ2hWorkspace, getWorkspaceHome } from '../util/workspace';
|
|
11
11
|
import { Z2H_AUTH_FILE, tryReadAuthTokenSync } from '../util/auth/auth-store';
|
|
12
12
|
import { brokerNpmRegistryUrl, routingHeaders } from '../constants';
|
|
@@ -26,10 +26,6 @@ const SSL_CERT_PATH = path.join(
|
|
|
26
26
|
'certificate.crt'
|
|
27
27
|
);
|
|
28
28
|
|
|
29
|
-
export interface DoctorOptions {
|
|
30
|
-
json?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
type Status = 'ok' | 'warn' | 'fail';
|
|
34
30
|
|
|
35
31
|
interface CheckResult {
|
|
@@ -317,11 +313,7 @@ function renderCheck(c: CheckResult): void {
|
|
|
317
313
|
|
|
318
314
|
// ── command ───────────────────────────────────────────────────────────────────
|
|
319
315
|
|
|
320
|
-
export async function doctorCommand(
|
|
321
|
-
if (opts.json) {
|
|
322
|
-
setOutputMode('json');
|
|
323
|
-
}
|
|
324
|
-
|
|
316
|
+
export async function doctorCommand(): Promise<void> {
|
|
325
317
|
let pluginsJson: InstalledPluginsJson | null = null;
|
|
326
318
|
try {
|
|
327
319
|
if (await pathExists(CLAUDE_PLUGINS_JSON)) {
|
|
@@ -350,7 +342,7 @@ export async function doctorCommand(opts: DoctorOptions = {}): Promise<void> {
|
|
|
350
342
|
const allChecks = sections.flatMap(s => s.checks);
|
|
351
343
|
const issues = allChecks.filter(c => c.status !== 'ok');
|
|
352
344
|
|
|
353
|
-
if (
|
|
345
|
+
if (getOutputMode() === 'json') {
|
|
354
346
|
output('', {
|
|
355
347
|
ok: issues.filter(c => c.status === 'fail').length === 0,
|
|
356
348
|
checks: allChecks,
|
package/src/commands/edit.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { pathExists } from 'fs-extra';
|
|
3
|
-
import { info, warn, output
|
|
3
|
+
import { info, warn, output } from '../util/logger';
|
|
4
4
|
import { assertWorkspace } from '../util/workspace';
|
|
5
5
|
import { mfAppName } from '../util/app-name';
|
|
6
6
|
import { fetchApp } from '../util/broker/app';
|
|
@@ -9,15 +9,7 @@ import { cloneRepo, pullAndResolve } from '../util/git/repo';
|
|
|
9
9
|
import { runYarnInstall } from '../util/npm/install';
|
|
10
10
|
import { prepareShadow } from '../shadow/prepare';
|
|
11
11
|
|
|
12
|
-
export
|
|
13
|
-
json?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function editCommand(appName: string, opts: EditOptions = {}): Promise<void> {
|
|
17
|
-
if (opts.json) {
|
|
18
|
-
setOutputMode('json');
|
|
19
|
-
}
|
|
20
|
-
|
|
12
|
+
export async function editCommand(appName: string): Promise<void> {
|
|
21
13
|
const workspace = await assertWorkspace();
|
|
22
14
|
const mfName = mfAppName(appName);
|
|
23
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import readline from 'node:readline/promises';
|
|
2
2
|
import { stdin, stdout } from 'node:process';
|
|
3
|
-
import { info, warn, output
|
|
3
|
+
import { info, warn, output } from '../util/logger';
|
|
4
4
|
import { writeZthAuth, validateJwtPattern } from '../util/auth/write-auth';
|
|
5
5
|
import { startLoopbackServer } from '../util/auth/loopback-server';
|
|
6
6
|
import { openInBrowser } from '../util/open-browser';
|
|
@@ -18,7 +18,6 @@ export interface GenerateZ2HTokenOptions {
|
|
|
18
18
|
noBrowser?: boolean;
|
|
19
19
|
/** Override the page URL — useful when pointing at staging. */
|
|
20
20
|
url?: string;
|
|
21
|
-
json?: boolean;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
async function promptForToken(): Promise<string> {
|
|
@@ -76,10 +75,6 @@ async function runLoopbackFlow(pageUrl: string): Promise<string | null> {
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
export async function generateZ2HTokenCommand(opts: GenerateZ2HTokenOptions = {}): Promise<void> {
|
|
79
|
-
if (opts.json) {
|
|
80
|
-
setOutputMode('json');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
78
|
const pageUrl = opts.url ?? process.env.Z2H_TOKEN_PAGE_URL ?? DEFAULT_TOKEN_PAGE_URL;
|
|
84
79
|
|
|
85
80
|
// --no-browser bypasses both auto-open and the loopback round-trip — the
|
package/src/commands/grant.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { output
|
|
1
|
+
import { output } from '../util/logger';
|
|
2
2
|
import { getCwdAppName, mfAppName } from '../util/app-name';
|
|
3
3
|
import { brokerBaseUrl, routingHeaders } from '../constants';
|
|
4
4
|
import { readZthAuth } from '../util/auth/auth-store';
|
|
@@ -6,7 +6,6 @@ import { readZthAuth } from '../util/auth/auth-store';
|
|
|
6
6
|
export interface GrantOptions {
|
|
7
7
|
public?: boolean;
|
|
8
8
|
level?: 'owner' | 'editor' | 'viewer';
|
|
9
|
-
json?: boolean;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
async function postGrant(
|
|
@@ -26,9 +25,6 @@ async function postGrant(
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
export async function grantCommand(email: string | undefined, opts: GrantOptions): Promise<void> {
|
|
29
|
-
if (opts.json) {
|
|
30
|
-
setOutputMode('json');
|
|
31
|
-
}
|
|
32
28
|
if (!opts.public && !email) {
|
|
33
29
|
throw new Error('Specify what to grant — e.g. --public or provide an email');
|
|
34
30
|
}
|
|
@@ -59,9 +55,6 @@ export async function grantCommand(email: string | undefined, opts: GrantOptions
|
|
|
59
55
|
}
|
|
60
56
|
|
|
61
57
|
export async function revokeCommand(email: string | undefined, opts: GrantOptions): Promise<void> {
|
|
62
|
-
if (opts.json) {
|
|
63
|
-
setOutputMode('json');
|
|
64
|
-
}
|
|
65
58
|
if (!opts.public && !email) {
|
|
66
59
|
throw new Error('Specify what to revoke — e.g. --public or provide an email');
|
|
67
60
|
}
|
|
@@ -86,14 +79,7 @@ export async function revokeCommand(email: string | undefined, opts: GrantOption
|
|
|
86
79
|
output(msg, { ok: true, appName, revoked });
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
export
|
|
90
|
-
json?: boolean;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function transferOwnerCommand(email: string | undefined, opts: TransferOwnerOptions): Promise<void> {
|
|
94
|
-
if (opts.json) {
|
|
95
|
-
setOutputMode('json');
|
|
96
|
-
}
|
|
82
|
+
export async function transferOwnerCommand(email: string | undefined): Promise<void> {
|
|
97
83
|
if (!email) {
|
|
98
84
|
throw new Error('Specify the new owner — e.g. z2h-cli transfer-owner someone@monday.com');
|
|
99
85
|
}
|
package/src/commands/migrate.ts
CHANGED
|
@@ -1,15 +1,148 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { pathExists, readJson, writeJson } from 'fs-extra';
|
|
5
|
+
import semver from 'semver';
|
|
6
|
+
import { errorLog, info, warn } from '../util/logger';
|
|
7
|
+
import { getWorkspaceHome, isZ2hWorkspace, ensureWorkspaceNx, runYarnInstall } from '../util/workspace';
|
|
8
|
+
|
|
9
|
+
const STATE_FILE = '.z2h-migrations-state.json';
|
|
10
|
+
|
|
11
|
+
interface MigrationState {
|
|
12
|
+
lastMigratedVersion: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface MigrationEntry {
|
|
16
|
+
version: string;
|
|
17
|
+
description: string;
|
|
18
|
+
migrate: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MigrationsJson {
|
|
22
|
+
migrations: Record<string, MigrationEntry>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Helpers ───
|
|
26
|
+
|
|
27
|
+
async function readMigrationState(workspaceHome: string): Promise<MigrationState | null> {
|
|
28
|
+
const statePath = path.join(workspaceHome, STATE_FILE);
|
|
29
|
+
if (!(await pathExists(statePath))) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return (await readJson(statePath)) as MigrationState;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function writeMigrationState(workspaceHome: string, state: MigrationState): Promise<void> {
|
|
40
|
+
const statePath = path.join(workspaceHome, STATE_FILE);
|
|
41
|
+
await writeJson(statePath, state, { spaces: 2 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Main ───
|
|
3
45
|
|
|
4
46
|
export interface MigrateOptions {
|
|
5
47
|
dryRun?: boolean;
|
|
6
|
-
json?: boolean;
|
|
7
48
|
}
|
|
8
49
|
|
|
9
50
|
export async function migrateCommand(cliVersion: string, opts: MigrateOptions = {}): Promise<void> {
|
|
10
|
-
|
|
11
|
-
|
|
51
|
+
const workspaceHome = getWorkspaceHome();
|
|
52
|
+
if (!(await isZ2hWorkspace(workspaceHome))) {
|
|
53
|
+
warn('no Z2H workspace found');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const state = await readMigrationState(workspaceHome);
|
|
58
|
+
const lastVersion = state?.lastMigratedVersion ?? '0.0.0';
|
|
59
|
+
|
|
60
|
+
if (!semver.gt(cliVersion, lastVersion)) {
|
|
61
|
+
info('workspace is up to date — no migrations pending');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
info(`migrating workspace from v${lastVersion} → v${cliVersion}...`);
|
|
66
|
+
|
|
67
|
+
// migrations.json is at the package root — two dirs up from dist/migrations/
|
|
68
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
69
|
+
const migrationsJsonPath = path.resolve(thisDir, '..', '..', 'migrations.json');
|
|
70
|
+
|
|
71
|
+
if (!(await pathExists(migrationsJsonPath))) {
|
|
72
|
+
warn('migrations.json not found — skipping migrations');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const migrationsJson = (await readJson(migrationsJsonPath)) as MigrationsJson;
|
|
77
|
+
|
|
78
|
+
// Filter to pending migrations (version > lastVersion AND <= cliVersion).
|
|
79
|
+
// CONVENTION: a migration's `version` field must equal the CLI release it
|
|
80
|
+
// ships with (not the feature branch version). This ensures the `gt` check
|
|
81
|
+
// always catches a migration on the exact upgrade that introduces it.
|
|
82
|
+
const pending = Object.entries(migrationsJson.migrations)
|
|
83
|
+
.filter(([, entry]) => semver.gt(entry.version, lastVersion) && semver.lte(entry.version, cliVersion))
|
|
84
|
+
.sort(([, a], [, b]) => semver.compare(a.version, b.version));
|
|
85
|
+
|
|
86
|
+
if (opts.dryRun) {
|
|
87
|
+
info(`${pending.length} pending migration(s) (dry-run — no changes will be applied):`);
|
|
88
|
+
for (const [, entry] of pending) {
|
|
89
|
+
info(` - ${entry.description} (v${entry.version})`);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (pending.length === 0) {
|
|
95
|
+
await writeMigrationState(workspaceHome, { lastMigratedVersion: cliVersion });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const nxReady = await ensureWorkspaceNx(workspaceHome);
|
|
100
|
+
if (!nxReady) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
info(`applying ${pending.length} workspace migration(s)...`);
|
|
105
|
+
|
|
106
|
+
// Resolve NX from the workspace's node_modules (not from CLI's own deps)
|
|
107
|
+
const wsRequire = createRequire(path.join(workspaceHome, 'package.json'));
|
|
108
|
+
const { FsTree, flushChanges } = wsRequire('nx/src/generators/tree') as {
|
|
109
|
+
FsTree: new (root: string, isVerbose: boolean) => import('@nx/devkit').Tree & {
|
|
110
|
+
listChanges(): { type: string; path: string }[];
|
|
111
|
+
};
|
|
112
|
+
flushChanges: (root: string, changes: { type: string; path: string; content?: Buffer | null }[]) => void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
let needsInstall = false;
|
|
116
|
+
|
|
117
|
+
for (const [name, entry] of pending) {
|
|
118
|
+
const tree = new FsTree(workspaceHome, false);
|
|
119
|
+
try {
|
|
120
|
+
const factoryPath = path.resolve(path.dirname(migrationsJsonPath), entry.migrate);
|
|
121
|
+
const mod = require(factoryPath);
|
|
122
|
+
const fn = mod.default || mod;
|
|
123
|
+
fn(tree);
|
|
124
|
+
|
|
125
|
+
const changes = tree.listChanges();
|
|
126
|
+
if (changes.length > 0) {
|
|
127
|
+
if (changes.some(c => c.path.endsWith('package.json'))) {
|
|
128
|
+
needsInstall = true;
|
|
129
|
+
}
|
|
130
|
+
flushChanges(workspaceHome, changes);
|
|
131
|
+
info(` ✓ ${entry.description}`);
|
|
132
|
+
} else {
|
|
133
|
+
info(` ○ ${entry.description} (no changes needed)`);
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
const errorMessage = `migration "${name}" failed: ${(err as Error).message}`;
|
|
137
|
+
errorLog(`${errorMessage}\nSkipping remaining migrations — run "z2h-cli migrate" to retry.`);
|
|
138
|
+
throw new Error(errorMessage);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (needsInstall) {
|
|
143
|
+
await runYarnInstall(workspaceHome, 'package.json changed');
|
|
12
144
|
}
|
|
13
145
|
|
|
14
|
-
await
|
|
146
|
+
await writeMigrationState(workspaceHome, { lastMigratedVersion: cliVersion });
|
|
147
|
+
info('workspace migrations complete.');
|
|
15
148
|
}
|
package/src/commands/tag.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { stdin, stdout } from 'node:process';
|
|
|
3
3
|
import { readJson } from 'fs-extra';
|
|
4
4
|
import { resolveConsumerPaths } from '../shadow/paths';
|
|
5
5
|
import { mfAppName } from '../util/app-name';
|
|
6
|
-
import { info, output,
|
|
6
|
+
import { info, output, getOutputMode } from '../util/logger';
|
|
7
7
|
import {
|
|
8
8
|
fetchAppTags,
|
|
9
9
|
fetchTagCatalog,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
export interface TagOptions {
|
|
18
18
|
/** Repeatable `--tag` — sets the full tag set non-interactively. */
|
|
19
19
|
tag?: string[];
|
|
20
|
-
json?: boolean;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
/** Render an app's tags as `name (Type), ...` — or `(none)`. */
|
|
@@ -100,10 +99,6 @@ export async function promptForTags(appSlug: string, catalog: CatalogEntry[], cu
|
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
export async function tagCommand(appArg: string | undefined, opts: TagOptions): Promise<void> {
|
|
103
|
-
if (opts.json) {
|
|
104
|
-
setOutputMode('json');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
102
|
const appSlug = await resolveAppSlug(appArg);
|
|
108
103
|
|
|
109
104
|
// Non-interactive write: `--tag` flags set the full set directly.
|
|
@@ -118,7 +113,7 @@ export async function tagCommand(appArg: string | undefined, opts: TagOptions):
|
|
|
118
113
|
// Read-only listing: agents (`--json`) and any non-TTY caller get the catalog
|
|
119
114
|
// + current tags without a prompt — then they set tags via `--tag`. This also
|
|
120
115
|
// avoids hanging on `readline` when no human is attached to stdin.
|
|
121
|
-
if (
|
|
116
|
+
if (getOutputMode() === 'json' || !process.stdin.isTTY) {
|
|
122
117
|
output(
|
|
123
118
|
`[z2h-cli] "${appSlug}"\n catalog: ${
|
|
124
119
|
catalog.map(t => `${t.name} (${TAG_TYPE_LABELS[t.type]})`).join(', ') || '(none)'
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { migrateCommand } from './commands/migrate';
|
|
|
17
17
|
import { tagCommand } from './commands/tag';
|
|
18
18
|
import { editCommand } from './commands/edit';
|
|
19
19
|
import { doctorCommand } from './commands/doctor';
|
|
20
|
-
import { errorLog, fail, getOutputMode } from './util/logger';
|
|
20
|
+
import { errorLog, fail, getOutputMode, setOutputMode } from './util/logger';
|
|
21
21
|
import { trackDuration } from './util/tracker';
|
|
22
22
|
import { getCwdAppName } from './util/app-name';
|
|
23
23
|
|
|
@@ -69,15 +69,20 @@ async function main(): Promise<void> {
|
|
|
69
69
|
' 2 system error (yarn install or trident build failed)',
|
|
70
70
|
].join('\n')
|
|
71
71
|
)
|
|
72
|
-
.version(version)
|
|
72
|
+
.version(version)
|
|
73
|
+
.option('--json', 'emit structured JSON on stdout')
|
|
74
|
+
.hook('preAction', (_thisCommand, actionCommand) => {
|
|
75
|
+
if (actionCommand.optsWithGlobals<{ json?: boolean }>().json) {
|
|
76
|
+
setOutputMode('json');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
73
79
|
|
|
74
80
|
program
|
|
75
81
|
.command('build')
|
|
76
82
|
.description('Build the consumer into its .zth/ shadow and copy artifacts to ./build/')
|
|
77
|
-
.
|
|
78
|
-
.action(async (opts: { json?: boolean }) => {
|
|
83
|
+
.action(async () => {
|
|
79
84
|
try {
|
|
80
|
-
await trackDuration('z2h_app_build_success', () => buildCommand(
|
|
85
|
+
await trackDuration('z2h_app_build_success', () => buildCommand(), { app_name: getCwdAppName() });
|
|
81
86
|
} catch (err) {
|
|
82
87
|
await fail(`build failed: ${(err as Error).message}`, { command: 'build' }, 2);
|
|
83
88
|
}
|
|
@@ -87,8 +92,7 @@ async function main(): Promise<void> {
|
|
|
87
92
|
.command('dev')
|
|
88
93
|
.description('Start the Trident dev server against .zth/ with native Vite HMR')
|
|
89
94
|
.option('--open', 'open the browser automatically when the dev server is ready')
|
|
90
|
-
.
|
|
91
|
-
.action(async (opts: { open?: boolean; json?: boolean }) => {
|
|
95
|
+
.action(async (opts: { open?: boolean }) => {
|
|
92
96
|
try {
|
|
93
97
|
await devCommand(opts);
|
|
94
98
|
} catch (err) {
|
|
@@ -99,10 +103,9 @@ async function main(): Promise<void> {
|
|
|
99
103
|
program
|
|
100
104
|
.command('clean')
|
|
101
105
|
.description('Delete <consumer>/.zth/ and <consumer>/build/')
|
|
102
|
-
.
|
|
103
|
-
.action(async (opts: { json?: boolean }) => {
|
|
106
|
+
.action(async () => {
|
|
104
107
|
try {
|
|
105
|
-
await cleanCommand(
|
|
108
|
+
await cleanCommand();
|
|
106
109
|
} catch (err) {
|
|
107
110
|
await fail(`clean failed: ${(err as Error).message}`, { command: 'clean' }, 2);
|
|
108
111
|
}
|
|
@@ -112,7 +115,6 @@ async function main(): Promise<void> {
|
|
|
112
115
|
.command('deploy')
|
|
113
116
|
.description('Build, upload to S3, and register the app with the Z2H backend')
|
|
114
117
|
.option('--dry-run', 'print what would happen without uploading anything')
|
|
115
|
-
.option('--json', 'emit structured JSON on stdout')
|
|
116
118
|
.option(
|
|
117
119
|
'--remote <type>',
|
|
118
120
|
'First deploy only: source control remote to initialise — s3 (shared storage) or github (your repo). Error if the app already has a git remote.'
|
|
@@ -121,7 +123,7 @@ async function main(): Promise<void> {
|
|
|
121
123
|
'--repo-url <url>',
|
|
122
124
|
'First deploy only: GitHub repo URL — required when --remote=github. Error if the app already has a git remote.'
|
|
123
125
|
)
|
|
124
|
-
.action(async (opts: { dryRun?: boolean;
|
|
126
|
+
.action(async (opts: { dryRun?: boolean; remote?: string; repoUrl?: string }) => {
|
|
125
127
|
try {
|
|
126
128
|
await trackDuration(
|
|
127
129
|
'z2h_app_deploy_success',
|
|
@@ -139,8 +141,7 @@ async function main(): Promise<void> {
|
|
|
139
141
|
.command('tag [app-name]')
|
|
140
142
|
.description("Set an app's category tags — app type & team (interactive unless --tag or --json is given)")
|
|
141
143
|
.option('--tag <tag>', 'set a tag (repeatable); full-replaces the tag set non-interactively', collectTag, [])
|
|
142
|
-
.
|
|
143
|
-
.action(async (appName: string | undefined, opts: { tag?: string[]; json?: boolean }) => {
|
|
144
|
+
.action(async (appName: string | undefined, opts: { tag?: string[] }) => {
|
|
144
145
|
try {
|
|
145
146
|
await trackDuration('z2h_app_tag_success', () => tagCommand(appName, opts), {
|
|
146
147
|
app_name: appName ?? getCwdAppName(),
|
|
@@ -153,10 +154,9 @@ async function main(): Promise<void> {
|
|
|
153
154
|
program
|
|
154
155
|
.command('create-workspace')
|
|
155
156
|
.description(`Bootstrap the ${getWorkspaceHome()}/ yarn workspace (one-time setup)`)
|
|
156
|
-
.
|
|
157
|
-
.action(async (opts: { json?: boolean }) => {
|
|
157
|
+
.action(async () => {
|
|
158
158
|
try {
|
|
159
|
-
await createWorkspaceCommand(
|
|
159
|
+
await createWorkspaceCommand();
|
|
160
160
|
} catch (err) {
|
|
161
161
|
await fail(`create-workspace failed: ${(err as Error).message}`, { command: 'create-workspace' }, 1);
|
|
162
162
|
}
|
|
@@ -169,8 +169,7 @@ async function main(): Promise<void> {
|
|
|
169
169
|
)
|
|
170
170
|
.option('--no-browser', "don't auto-open the browser; print the URL and prompt for manual paste")
|
|
171
171
|
.option('--url <url>', 'Override the token page URL (e.g. for staging)')
|
|
172
|
-
.
|
|
173
|
-
.action(async (opts: { noBrowser?: boolean; url?: string; json?: boolean }) => {
|
|
172
|
+
.action(async (opts: { noBrowser?: boolean; url?: string }) => {
|
|
174
173
|
try {
|
|
175
174
|
await generateZ2HTokenCommand(opts);
|
|
176
175
|
} catch (err) {
|
|
@@ -185,12 +184,8 @@ async function main(): Promise<void> {
|
|
|
185
184
|
.option('--html-file <path>', 'Path to an HTML file to embed (implies --template html-embed)')
|
|
186
185
|
.option('--remote <type>', 'Source control remote: s3 (shared storage) or github (your repo)')
|
|
187
186
|
.option('--repo-url <url>', 'GitHub repo URL — required when --remote=github')
|
|
188
|
-
.option('--json', 'emit structured JSON on stdout')
|
|
189
187
|
.action(
|
|
190
|
-
async (
|
|
191
|
-
appName: string,
|
|
192
|
-
opts: { template?: string; htmlFile?: string; remote?: string; repoUrl?: string; json?: boolean }
|
|
193
|
-
) => {
|
|
188
|
+
async (appName: string, opts: { template?: string; htmlFile?: string; remote?: string; repoUrl?: string }) => {
|
|
194
189
|
try {
|
|
195
190
|
await trackDuration('z2h_app_created', () => createCommand(appName, opts as CreateOptions), {
|
|
196
191
|
app_name: appName,
|
|
@@ -206,10 +201,9 @@ async function main(): Promise<void> {
|
|
|
206
201
|
program
|
|
207
202
|
.command('edit <app-name>')
|
|
208
203
|
.description(`Pull the latest version of an app into ${getWorkspaceHome()}/<app-name>, cloning it first if needed`)
|
|
209
|
-
.
|
|
210
|
-
.action(async (appName: string, opts: { json?: boolean }) => {
|
|
204
|
+
.action(async (appName: string) => {
|
|
211
205
|
try {
|
|
212
|
-
await trackDuration('z2h_app_edit_pulled', () => editCommand(appName
|
|
206
|
+
await trackDuration('z2h_app_edit_pulled', () => editCommand(appName), {
|
|
213
207
|
app_name: appName,
|
|
214
208
|
});
|
|
215
209
|
} catch (err) {
|
|
@@ -222,8 +216,7 @@ async function main(): Promise<void> {
|
|
|
222
216
|
.description('Grant access to the current app (email, --public, or both)')
|
|
223
217
|
.option('--public', 'make the app visible to all monday.com employees')
|
|
224
218
|
.option('--level <level>', 'access level for user grants: owner, editor, or viewer (default: viewer)')
|
|
225
|
-
.
|
|
226
|
-
.action(async (email: string | undefined, opts: { public?: boolean; level?: string; json?: boolean }) => {
|
|
219
|
+
.action(async (email: string | undefined, opts: { public?: boolean; level?: string }) => {
|
|
227
220
|
try {
|
|
228
221
|
await grantCommand(email, opts as Parameters<typeof grantCommand>[1]);
|
|
229
222
|
} catch (err) {
|
|
@@ -235,8 +228,7 @@ async function main(): Promise<void> {
|
|
|
235
228
|
.command('revoke [email]')
|
|
236
229
|
.description('Revoke access to the current app (email or --public; owners/admins only)')
|
|
237
230
|
.option('--public', 'remove the public grant, making the app visible to owners/admins only')
|
|
238
|
-
.
|
|
239
|
-
.action(async (email: string | undefined, opts: { public?: boolean; json?: boolean }) => {
|
|
231
|
+
.action(async (email: string | undefined, opts: { public?: boolean }) => {
|
|
240
232
|
try {
|
|
241
233
|
await revokeCommand(email, opts as Parameters<typeof revokeCommand>[1]);
|
|
242
234
|
} catch (err) {
|
|
@@ -247,10 +239,9 @@ async function main(): Promise<void> {
|
|
|
247
239
|
program
|
|
248
240
|
.command('transfer-owner [email]')
|
|
249
241
|
.description('Transfer ownership of the current app to another monday.com user')
|
|
250
|
-
.
|
|
251
|
-
.action(async (email: string | undefined, opts: { json?: boolean }) => {
|
|
242
|
+
.action(async (email: string | undefined) => {
|
|
252
243
|
try {
|
|
253
|
-
await transferOwnerCommand(email
|
|
244
|
+
await transferOwnerCommand(email);
|
|
254
245
|
} catch (err) {
|
|
255
246
|
await fail(`transfer-owner failed: ${(err as Error).message}`, { command: 'transfer-owner' }, 1);
|
|
256
247
|
}
|
|
@@ -260,8 +251,7 @@ async function main(): Promise<void> {
|
|
|
260
251
|
.command('delete')
|
|
261
252
|
.description('Permanently delete the current app and take it offline (owners/admins only)')
|
|
262
253
|
.option('--yes', 'skip the confirmation prompt (required for --json / non-interactive)')
|
|
263
|
-
.
|
|
264
|
-
.action(async (opts: { yes?: boolean; json?: boolean }) => {
|
|
254
|
+
.action(async (opts: { yes?: boolean }) => {
|
|
265
255
|
try {
|
|
266
256
|
await trackDuration('z2h_app_delete_success', () => deleteCommand(opts), {
|
|
267
257
|
app_name: getCwdAppName(),
|
|
@@ -275,8 +265,7 @@ async function main(): Promise<void> {
|
|
|
275
265
|
.command('migrate')
|
|
276
266
|
.description('Run pending workspace migrations')
|
|
277
267
|
.option('--dry-run', 'show pending migrations without applying any changes')
|
|
278
|
-
.
|
|
279
|
-
.action(async (opts: { dryRun?: boolean; json?: boolean }) => {
|
|
268
|
+
.action(async (opts: { dryRun?: boolean }) => {
|
|
280
269
|
try {
|
|
281
270
|
await migrateCommand(version, opts);
|
|
282
271
|
} catch (err) {
|
|
@@ -287,10 +276,9 @@ async function main(): Promise<void> {
|
|
|
287
276
|
program
|
|
288
277
|
.command('doctor')
|
|
289
278
|
.description('Check that all Z2H tools, auth, workspace, and plugins are correctly set up')
|
|
290
|
-
.
|
|
291
|
-
.action(async (opts: { json?: boolean }) => {
|
|
279
|
+
.action(async () => {
|
|
292
280
|
try {
|
|
293
|
-
await doctorCommand(
|
|
281
|
+
await doctorCommand();
|
|
294
282
|
} catch (err) {
|
|
295
283
|
await fail(`doctor failed: ${(err as Error).message}`, { command: 'doctor' }, 1);
|
|
296
284
|
}
|