@mondaydotcomorg/z2h-cli 0.4.0 → 0.5.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.
Files changed (65) hide show
  1. package/dist/commands/create-workspace.d.ts.map +1 -1
  2. package/dist/commands/create-workspace.js +3 -30
  3. package/dist/commands/migrate.d.ts +6 -0
  4. package/dist/commands/migrate.d.ts.map +1 -0
  5. package/dist/commands/migrate.js +13 -0
  6. package/dist/constants.d.ts +0 -1
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +0 -7
  9. package/dist/esm/commands/create-workspace.d.ts.map +1 -1
  10. package/dist/esm/commands/create-workspace.mjs +5 -32
  11. package/dist/esm/commands/migrate.d.ts +6 -0
  12. package/dist/esm/commands/migrate.d.ts.map +1 -0
  13. package/dist/esm/commands/migrate.mjs +11 -0
  14. package/dist/esm/constants.d.ts +0 -1
  15. package/dist/esm/constants.d.ts.map +1 -1
  16. package/dist/esm/constants.mjs +1 -7
  17. package/dist/esm/index.mjs +14 -0
  18. package/dist/esm/migrations/001-remove-per-consumer-yarnrc.d.ts +3 -0
  19. package/dist/esm/migrations/001-remove-per-consumer-yarnrc.d.ts.map +1 -0
  20. package/dist/esm/migrations/001-remove-per-consumer-yarnrc.mjs +15 -0
  21. package/dist/esm/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts +3 -0
  22. package/dist/esm/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts.map +1 -0
  23. package/dist/esm/migrations/002-remove-z2h-cli-from-workspace-deps.mjs +41 -0
  24. package/dist/esm/migrations/auto-migrate.d.ts +2 -0
  25. package/dist/esm/migrations/auto-migrate.d.ts.map +1 -0
  26. package/dist/esm/migrations/auto-migrate.mjs +166 -0
  27. package/dist/esm/shadow/prepare.d.ts.map +1 -1
  28. package/dist/esm/shadow/prepare.mjs +0 -2
  29. package/dist/esm/templates/workspace/nx.mjs +5 -0
  30. package/dist/index.js +14 -0
  31. package/dist/migrations/001-remove-per-consumer-yarnrc.d.ts +3 -0
  32. package/dist/migrations/001-remove-per-consumer-yarnrc.d.ts.map +1 -0
  33. package/dist/migrations/001-remove-per-consumer-yarnrc.js +17 -0
  34. package/dist/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts +3 -0
  35. package/dist/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts.map +1 -0
  36. package/dist/migrations/002-remove-z2h-cli-from-workspace-deps.js +43 -0
  37. package/dist/migrations/auto-migrate.d.ts +2 -0
  38. package/dist/migrations/auto-migrate.d.ts.map +1 -0
  39. package/dist/migrations/auto-migrate.js +174 -0
  40. package/dist/shadow/prepare.d.ts.map +1 -1
  41. package/dist/shadow/prepare.js +0 -2
  42. package/dist/templates/workspace/nx.js +7 -0
  43. package/migrations.json +14 -0
  44. package/package.json +6 -2
  45. package/src/commands/__tests__/create-workspace.test.ts +2 -4
  46. package/src/commands/__tests__/create.test.ts +0 -1
  47. package/src/commands/create-workspace.ts +5 -33
  48. package/src/commands/migrate.ts +15 -0
  49. package/src/constants.ts +0 -7
  50. package/src/index.ts +14 -0
  51. package/src/migrations/001-remove-per-consumer-yarnrc.ts +15 -0
  52. package/src/migrations/002-remove-z2h-cli-from-workspace-deps.ts +47 -0
  53. package/src/migrations/auto-migrate.ts +214 -0
  54. package/src/shadow/prepare.ts +0 -2
  55. package/src/templates/app/package.json.tmpl +1 -4
  56. package/src/templates/workspace/nx.json +3 -0
  57. package/src/templates/workspace/package.json.tmpl +4 -1
  58. package/src/util/__tests__/workspace-templates-shipped.test.ts +1 -0
  59. package/dist/esm/shadow/yarnrc.d.ts +0 -10
  60. package/dist/esm/shadow/yarnrc.d.ts.map +0 -1
  61. package/dist/esm/shadow/yarnrc.mjs +0 -27
  62. package/dist/shadow/yarnrc.d.ts +0 -10
  63. package/dist/shadow/yarnrc.d.ts.map +0 -1
  64. package/dist/shadow/yarnrc.js +0 -33
  65. package/src/shadow/yarnrc.ts +0 -29
@@ -0,0 +1,214 @@
1
+ import path from 'node:path';
2
+ import { createRequire } from 'node:module';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { pathExists, readJson, writeJson, writeFile } from 'fs-extra';
5
+ import { execa } from 'execa';
6
+ import semver from 'semver';
7
+ import { info, warn } from '../util/logger';
8
+ import { getWorkspaceHome, isZ2hWorkspace } from '../util/workspace';
9
+
10
+ const STATE_FILE = '.z2h-migrations-state.json';
11
+ const NX_JSON_CONTENT = '{\n "extends": "@mondaydotcomorg/trident-monorepo/presets/nx.json"\n}\n';
12
+
13
+ interface MigrationState {
14
+ lastMigratedVersion: string;
15
+ }
16
+
17
+ interface MigrationEntry {
18
+ version: string;
19
+ description: string;
20
+ migrate: string;
21
+ }
22
+
23
+ interface MigrationsJson {
24
+ migrations: Record<string, MigrationEntry>;
25
+ }
26
+
27
+ // ─── Helpers ───
28
+
29
+ async function readState(workspaceHome: string): Promise<MigrationState | null> {
30
+ const statePath = path.join(workspaceHome, STATE_FILE);
31
+ if (!(await pathExists(statePath))) {
32
+ return null;
33
+ }
34
+ try {
35
+ return (await readJson(statePath)) as MigrationState;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ async function writeState(workspaceHome: string, state: MigrationState): Promise<void> {
42
+ const statePath = path.join(workspaceHome, STATE_FILE);
43
+ await writeJson(statePath, state, { spaces: 2 });
44
+ }
45
+
46
+ async function yarnInstall(workspaceHome: string, context: string): Promise<boolean> {
47
+ info(`${context} — running yarn install...`);
48
+ try {
49
+ await execa('yarn', ['install'], { cwd: workspaceHome, stdio: 'pipe' });
50
+ return true;
51
+ } catch (err) {
52
+ const stderr = (err as { stderr?: string }).stderr || (err as Error).message;
53
+ warn(`yarn install failed: ${stderr}`);
54
+ return false;
55
+ }
56
+ }
57
+
58
+ async function getLatestTrientMonorepoVersion(): Promise<string> {
59
+ try {
60
+ const { stdout } = await execa('npm', ['view', '@mondaydotcomorg/trident-monorepo', 'version']);
61
+ return `^${stdout.trim()}`;
62
+ } catch {
63
+ return '*';
64
+ }
65
+ }
66
+
67
+ // ─── Workspace bootstrap ───
68
+
69
+ function isNxAvailable(workspaceHome: string): boolean {
70
+ try {
71
+ const wsRequire = createRequire(path.join(workspaceHome, 'package.json'));
72
+ wsRequire.resolve('nx/src/generators/tree');
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ async function ensureWorkspaceNx(workspaceHome: string): Promise<boolean> {
80
+ const nxJsonPath = path.join(workspaceHome, 'nx.json');
81
+ const pkgPath = path.join(workspaceHome, 'package.json');
82
+
83
+ if (!(await pathExists(nxJsonPath))) {
84
+ await writeFile(nxJsonPath, NX_JSON_CONTENT);
85
+ info('added nx.json to workspace');
86
+ }
87
+
88
+ const pkg = (await readJson(pkgPath)) as {
89
+ devDependencies?: Record<string, string>;
90
+ scripts?: Record<string, string>;
91
+ };
92
+
93
+ if (!pkg.devDependencies?.['@mondaydotcomorg/trident-monorepo']) {
94
+ pkg.devDependencies = pkg.devDependencies ?? {};
95
+ pkg.devDependencies['@mondaydotcomorg/trident-monorepo'] = await getLatestTrientMonorepoVersion();
96
+ }
97
+
98
+ if (!pkg.scripts?.nx) {
99
+ pkg.scripts = pkg.scripts ?? {};
100
+ pkg.scripts.nx = 'nx';
101
+ }
102
+
103
+ await writeJson(pkgPath, pkg, { spaces: 2 });
104
+
105
+ // Only install if NX isn't already importable from the workspace
106
+ if (!isNxAvailable(workspaceHome)) {
107
+ const ok = await yarnInstall(workspaceHome, 'NX not found, installing');
108
+ if (!ok) {
109
+ warn('workspace migrations require NX. Fix the install error and run "z2h-cli migrate" to retry.');
110
+ return false;
111
+ }
112
+ }
113
+
114
+ return true;
115
+ }
116
+
117
+ // ─── Main ───
118
+
119
+ export async function autoMigrate(cliVersion: string, dryRun = false): Promise<void> {
120
+ const workspaceHome = getWorkspaceHome();
121
+ if (!(await isZ2hWorkspace(workspaceHome))) {
122
+ warn('no Z2H workspace found');
123
+ return;
124
+ }
125
+
126
+ const state = await readState(workspaceHome);
127
+ const lastVersion = state?.lastMigratedVersion ?? '0.0.0';
128
+
129
+ if (!semver.gt(cliVersion, lastVersion)) {
130
+ info('workspace is up to date — no migrations pending');
131
+ return;
132
+ }
133
+
134
+ info(`migrating workspace from v${lastVersion} → v${cliVersion}...`);
135
+
136
+ // migrations.json is at the package root — two dirs up from dist/migrations/
137
+ const thisDir = path.dirname(fileURLToPath(import.meta.url));
138
+ const migrationsJsonPath = path.resolve(thisDir, '..', '..', 'migrations.json');
139
+
140
+ if (!(await pathExists(migrationsJsonPath))) {
141
+ warn('migrations.json not found — skipping migrations');
142
+ return;
143
+ }
144
+
145
+ const migrationsJson = (await readJson(migrationsJsonPath)) as MigrationsJson;
146
+
147
+ // Filter to pending migrations (version > lastVersion AND <= cliVersion)
148
+ const pending = Object.entries(migrationsJson.migrations)
149
+ .filter(([, entry]) => semver.gt(entry.version, lastVersion) && semver.lte(entry.version, cliVersion))
150
+ .sort(([, a], [, b]) => semver.compare(a.version, b.version));
151
+
152
+ if (dryRun) {
153
+ info(`${pending.length} pending migration(s) (dry-run — no changes will be applied):`);
154
+ for (const [, entry] of pending) {
155
+ info(` - ${entry.description} (v${entry.version})`);
156
+ }
157
+ return;
158
+ }
159
+
160
+ if (pending.length === 0) {
161
+ await writeState(workspaceHome, { lastMigratedVersion: cliVersion });
162
+ return;
163
+ }
164
+
165
+ const nxReady = await ensureWorkspaceNx(workspaceHome);
166
+ if (!nxReady) {
167
+ return;
168
+ }
169
+
170
+ info(`applying ${pending.length} workspace migration(s)...`);
171
+
172
+ // Resolve NX from the workspace's node_modules (not from CLI's own deps)
173
+ const wsRequire = createRequire(path.join(workspaceHome, 'package.json'));
174
+ const { FsTree, flushChanges } = wsRequire('nx/src/generators/tree') as {
175
+ FsTree: new (root: string, isVerbose: boolean) => import('@nx/devkit').Tree & {
176
+ listChanges(): { type: string; path: string }[];
177
+ };
178
+ flushChanges: (root: string, changes: { type: string; path: string; content?: Buffer | null }[]) => void;
179
+ };
180
+
181
+ let needsInstall = false;
182
+
183
+ for (const [name, entry] of pending) {
184
+ const tree = new FsTree(workspaceHome, false);
185
+ try {
186
+ const factoryPath = path.resolve(path.dirname(migrationsJsonPath), entry.migrate);
187
+ const mod = require(factoryPath);
188
+ const fn = mod.default || mod;
189
+ fn(tree);
190
+
191
+ const changes = tree.listChanges();
192
+ if (changes.length > 0) {
193
+ if (changes.some(c => c.path.endsWith('package.json'))) {
194
+ needsInstall = true;
195
+ }
196
+ flushChanges(workspaceHome, changes);
197
+ info(` ✓ ${entry.description}`);
198
+ } else {
199
+ info(` ○ ${entry.description} (no changes needed)`);
200
+ }
201
+ } catch (err) {
202
+ warn(` ✗ migration "${name}" failed: ${(err as Error).message}`);
203
+ warn(' skipping remaining migrations. Run "z2h-cli migrate" to retry.');
204
+ return;
205
+ }
206
+ }
207
+
208
+ if (needsInstall) {
209
+ await yarnInstall(workspaceHome, 'package.json changed');
210
+ }
211
+
212
+ await writeState(workspaceHome, { lastMigratedVersion: cliVersion });
213
+ info('workspace migrations complete.');
214
+ }
@@ -3,7 +3,6 @@ import { info } from '../util/logger';
3
3
  import { cleanAppName } from '../util/app-name';
4
4
  import { resolveConsumerPaths } from './paths';
5
5
  import { ensureGitignore } from './gitignore';
6
- import { ensureYarnRc } from './yarnrc';
7
6
  import { ensureConsumerPkg } from './consumer-pkg';
8
7
  import { scaffoldShadow } from './scaffold';
9
8
  import { readMetadata, writeMetadata } from './metadata';
@@ -33,7 +32,6 @@ export async function prepareShadow(): Promise<PreparedShadow> {
33
32
  }
34
33
 
35
34
  await ensureGitignore(paths.consumerDir);
36
- await ensureYarnRc(paths.consumerDir);
37
35
  await ensureDir(paths.shadowDir);
38
36
 
39
37
  const metadata = await resolvePortAndMetadata(paths, appName);
@@ -3,11 +3,8 @@
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "build": "z2h-cli build",
7
6
  "start": "z2h-cli dev",
7
+ "build": "z2h-cli build",
8
8
  "deploy": "z2h-cli deploy"
9
- },
10
- "devDependencies": {
11
- "@mondaydotcomorg/z2h-cli": "*"
12
9
  }
13
10
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@mondaydotcomorg/trident-monorepo/presets/nx.json"
3
+ }
@@ -4,6 +4,9 @@
4
4
  "private": true,
5
5
  "z2hWorkspace": true,
6
6
  "workspaces": ["*", "*/.zth"],
7
+ "scripts": {
8
+ "nx": "nx"
9
+ },
7
10
  "dependencies": {
8
11
  "@mondaydotcomorg/trident-runtime": "^1.24.1",
9
12
  "@vibe/core": "^4.0.1",
@@ -14,8 +17,8 @@
14
17
  "devDependencies": {
15
18
  "@mondaydotcomorg/bigbrain-types": "^0.3.4",
16
19
  "@mondaydotcomorg/mf-api-types": "^0.1.1",
20
+ "@mondaydotcomorg/trident-monorepo": "^0.38.8",
17
21
  "@mondaydotcomorg/trident-toolkit": "^2.9.25",
18
- "@mondaydotcomorg/z2h-cli": "__Z2H_CLI_VERSION__",
19
22
  "@types/react": "^18.0.14",
20
23
  "@types/react-dom": "^18.0.5",
21
24
  "typescript": "^5.7.2"
@@ -8,6 +8,7 @@ describe('workspace templates ship with the package', () => {
8
8
  'workspace/package.json.tmpl',
9
9
  'workspace/gitignore',
10
10
  'workspace/yarnrc.yml',
11
+ 'workspace/nx.json',
11
12
  'workspace/nvmrc',
12
13
  'workspace/npmrc',
13
14
  'workspace/README.md',
@@ -1,10 +0,0 @@
1
- /**
2
- * Ensure the consumer has a `.yarnrc.yml` that pins `nodeLinker: node-modules`.
3
- *
4
- * Yarn 4 defaults to PnP, which trident-toolkit's webpack pipeline doesn't
5
- * support cleanly (webpack expects a flat node_modules). We force the
6
- * classic linker so installs produce a real `node_modules/`. Idempotent —
7
- * leaves any other settings the user added alone.
8
- */
9
- export declare function ensureYarnRc(consumerDir: string): Promise<void>;
10
- //# sourceMappingURL=yarnrc.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"yarnrc.d.ts","sourceRoot":"","sources":["../../../src/shadow/yarnrc.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAerE"}
@@ -1,27 +0,0 @@
1
- import path from 'node:path';
2
- import { pathExists, writeFile, readFile } from 'fs-extra';
3
- import { YARNRC_LINES } from '../constants.mjs';
4
-
5
- /**
6
- * Ensure the consumer has a `.yarnrc.yml` that pins `nodeLinker: node-modules`.
7
- *
8
- * Yarn 4 defaults to PnP, which trident-toolkit's webpack pipeline doesn't
9
- * support cleanly (webpack expects a flat node_modules). We force the
10
- * classic linker so installs produce a real `node_modules/`. Idempotent —
11
- * leaves any other settings the user added alone.
12
- */
13
- async function ensureYarnRc(consumerDir) {
14
- const yarnrcPath = path.join(consumerDir, '.yarnrc.yml');
15
- if (!(await pathExists(yarnrcPath))) {
16
- await writeFile(yarnrcPath, YARNRC_LINES.join('\n\n') + '\n');
17
- return;
18
- }
19
- const body = await readFile(yarnrcPath, 'utf8');
20
- if (/^\s*nodeLinker\s*:/m.test(body)) {
21
- return;
22
- }
23
- const prefix = body.length && !body.endsWith('\n') ? '\n' : '';
24
- await writeFile(yarnrcPath, body + prefix + 'nodeLinker: node-modules\n');
25
- }
26
-
27
- export { ensureYarnRc };
@@ -1,10 +0,0 @@
1
- /**
2
- * Ensure the consumer has a `.yarnrc.yml` that pins `nodeLinker: node-modules`.
3
- *
4
- * Yarn 4 defaults to PnP, which trident-toolkit's webpack pipeline doesn't
5
- * support cleanly (webpack expects a flat node_modules). We force the
6
- * classic linker so installs produce a real `node_modules/`. Idempotent —
7
- * leaves any other settings the user added alone.
8
- */
9
- export declare function ensureYarnRc(consumerDir: string): Promise<void>;
10
- //# sourceMappingURL=yarnrc.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"yarnrc.d.ts","sourceRoot":"","sources":["../../src/shadow/yarnrc.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAerE"}
@@ -1,33 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
-
3
- const path = require('node:path');
4
- const fsExtra = require('fs-extra');
5
- const constants = require('../constants.js');
6
-
7
- const _interopDefault = e => e && e.__esModule ? e : { default: e };
8
-
9
- const path__default = /*#__PURE__*/_interopDefault(path);
10
-
11
- /**
12
- * Ensure the consumer has a `.yarnrc.yml` that pins `nodeLinker: node-modules`.
13
- *
14
- * Yarn 4 defaults to PnP, which trident-toolkit's webpack pipeline doesn't
15
- * support cleanly (webpack expects a flat node_modules). We force the
16
- * classic linker so installs produce a real `node_modules/`. Idempotent —
17
- * leaves any other settings the user added alone.
18
- */
19
- async function ensureYarnRc(consumerDir) {
20
- const yarnrcPath = path__default.default.join(consumerDir, '.yarnrc.yml');
21
- if (!(await fsExtra.pathExists(yarnrcPath))) {
22
- await fsExtra.writeFile(yarnrcPath, constants.YARNRC_LINES.join('\n\n') + '\n');
23
- return;
24
- }
25
- const body = await fsExtra.readFile(yarnrcPath, 'utf8');
26
- if (/^\s*nodeLinker\s*:/m.test(body)) {
27
- return;
28
- }
29
- const prefix = body.length && !body.endsWith('\n') ? '\n' : '';
30
- await fsExtra.writeFile(yarnrcPath, body + prefix + 'nodeLinker: node-modules\n');
31
- }
32
-
33
- exports.ensureYarnRc = ensureYarnRc;
@@ -1,29 +0,0 @@
1
- import path from 'node:path';
2
- import { pathExists, readFile, writeFile } from 'fs-extra';
3
- import { YARNRC_LINES } from '../constants';
4
-
5
- /**
6
- * Ensure the consumer has a `.yarnrc.yml` that pins `nodeLinker: node-modules`.
7
- *
8
- * Yarn 4 defaults to PnP, which trident-toolkit's webpack pipeline doesn't
9
- * support cleanly (webpack expects a flat node_modules). We force the
10
- * classic linker so installs produce a real `node_modules/`. Idempotent —
11
- * leaves any other settings the user added alone.
12
- */
13
-
14
- export async function ensureYarnRc(consumerDir: string): Promise<void> {
15
- const yarnrcPath = path.join(consumerDir, '.yarnrc.yml');
16
-
17
- if (!(await pathExists(yarnrcPath))) {
18
- await writeFile(yarnrcPath, YARNRC_LINES.join('\n\n') + '\n');
19
- return;
20
- }
21
-
22
- const body = await readFile(yarnrcPath, 'utf8');
23
- if (/^\s*nodeLinker\s*:/m.test(body)) {
24
- return;
25
- }
26
-
27
- const prefix = body.length && !body.endsWith('\n') ? '\n' : '';
28
- await writeFile(yarnrcPath, body + prefix + 'nodeLinker: node-modules\n');
29
- }