@mondaydotcomorg/z2h-cli 0.4.1 → 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.
- package/dist/commands/create-workspace.d.ts.map +1 -1
- package/dist/commands/create-workspace.js +3 -30
- package/dist/commands/migrate.d.ts +6 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +13 -0
- package/dist/esm/commands/create-workspace.d.ts.map +1 -1
- package/dist/esm/commands/create-workspace.mjs +5 -32
- package/dist/esm/commands/migrate.d.ts +6 -0
- package/dist/esm/commands/migrate.d.ts.map +1 -0
- package/dist/esm/commands/migrate.mjs +11 -0
- package/dist/esm/index.mjs +14 -0
- package/dist/esm/migrations/001-remove-per-consumer-yarnrc.d.ts +3 -0
- package/dist/esm/migrations/001-remove-per-consumer-yarnrc.d.ts.map +1 -0
- package/dist/esm/migrations/001-remove-per-consumer-yarnrc.mjs +15 -0
- package/dist/esm/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts +3 -0
- package/dist/esm/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts.map +1 -0
- package/dist/esm/migrations/002-remove-z2h-cli-from-workspace-deps.mjs +41 -0
- package/dist/esm/migrations/auto-migrate.d.ts +2 -0
- package/dist/esm/migrations/auto-migrate.d.ts.map +1 -0
- package/dist/esm/migrations/auto-migrate.mjs +166 -0
- package/dist/esm/templates/workspace/nx.mjs +5 -0
- package/dist/index.js +14 -0
- package/dist/migrations/001-remove-per-consumer-yarnrc.d.ts +3 -0
- package/dist/migrations/001-remove-per-consumer-yarnrc.d.ts.map +1 -0
- package/dist/migrations/001-remove-per-consumer-yarnrc.js +17 -0
- package/dist/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts +3 -0
- package/dist/migrations/002-remove-z2h-cli-from-workspace-deps.d.ts.map +1 -0
- package/dist/migrations/002-remove-z2h-cli-from-workspace-deps.js +43 -0
- package/dist/migrations/auto-migrate.d.ts +2 -0
- package/dist/migrations/auto-migrate.d.ts.map +1 -0
- package/dist/migrations/auto-migrate.js +174 -0
- package/dist/templates/workspace/nx.js +7 -0
- package/migrations.json +14 -0
- package/package.json +6 -2
- package/src/commands/__tests__/create-workspace.test.ts +2 -4
- package/src/commands/__tests__/create.test.ts +0 -1
- package/src/commands/create-workspace.ts +5 -33
- package/src/commands/migrate.ts +15 -0
- package/src/index.ts +14 -0
- package/src/migrations/001-remove-per-consumer-yarnrc.ts +15 -0
- package/src/migrations/002-remove-z2h-cli-from-workspace-deps.ts +47 -0
- package/src/migrations/auto-migrate.ts +214 -0
- package/src/templates/app/package.json.tmpl +1 -4
- package/src/templates/workspace/nx.json +3 -0
- package/src/templates/workspace/package.json.tmpl +4 -1
- package/src/util/__tests__/workspace-templates-shipped.test.ts +1 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const node_module = require('node:module');
|
|
5
|
+
const node_url = require('node:url');
|
|
6
|
+
const fsExtra = require('fs-extra');
|
|
7
|
+
const execa = require('execa');
|
|
8
|
+
const semver = require('semver');
|
|
9
|
+
const util_logger = require('../util/logger.js');
|
|
10
|
+
const util_workspace = require('../util/workspace.js');
|
|
11
|
+
|
|
12
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
|
+
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
14
|
+
|
|
15
|
+
const path__default = /*#__PURE__*/_interopDefault(path);
|
|
16
|
+
const semver__default = /*#__PURE__*/_interopDefault(semver);
|
|
17
|
+
|
|
18
|
+
const STATE_FILE = '.z2h-migrations-state.json';
|
|
19
|
+
const NX_JSON_CONTENT = '{\n "extends": "@mondaydotcomorg/trident-monorepo/presets/nx.json"\n}\n';
|
|
20
|
+
// ─── Helpers ───
|
|
21
|
+
async function readState(workspaceHome) {
|
|
22
|
+
const statePath = path__default.default.join(workspaceHome, STATE_FILE);
|
|
23
|
+
if (!(await fsExtra.pathExists(statePath))) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return (await fsExtra.readJson(statePath));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function writeState(workspaceHome, state) {
|
|
34
|
+
const statePath = path__default.default.join(workspaceHome, STATE_FILE);
|
|
35
|
+
await fsExtra.writeJson(statePath, state, { spaces: 2 });
|
|
36
|
+
}
|
|
37
|
+
async function yarnInstall(workspaceHome, context) {
|
|
38
|
+
util_logger.info(`${context} — running yarn install...`);
|
|
39
|
+
try {
|
|
40
|
+
await execa.execa('yarn', ['install'], { cwd: workspaceHome, stdio: 'pipe' });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const stderr = err.stderr || err.message;
|
|
45
|
+
util_logger.warn(`yarn install failed: ${stderr}`);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function getLatestTrientMonorepoVersion() {
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execa.execa('npm', ['view', '@mondaydotcomorg/trident-monorepo', 'version']);
|
|
52
|
+
return `^${stdout.trim()}`;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return '*';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// ─── Workspace bootstrap ───
|
|
59
|
+
function isNxAvailable(workspaceHome) {
|
|
60
|
+
try {
|
|
61
|
+
const wsRequire = node_module.createRequire(path__default.default.join(workspaceHome, 'package.json'));
|
|
62
|
+
wsRequire.resolve('nx/src/generators/tree');
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function ensureWorkspaceNx(workspaceHome) {
|
|
70
|
+
const nxJsonPath = path__default.default.join(workspaceHome, 'nx.json');
|
|
71
|
+
const pkgPath = path__default.default.join(workspaceHome, 'package.json');
|
|
72
|
+
if (!(await fsExtra.pathExists(nxJsonPath))) {
|
|
73
|
+
await fsExtra.writeFile(nxJsonPath, NX_JSON_CONTENT);
|
|
74
|
+
util_logger.info('added nx.json to workspace');
|
|
75
|
+
}
|
|
76
|
+
const pkg = (await fsExtra.readJson(pkgPath));
|
|
77
|
+
if (!pkg.devDependencies?.['@mondaydotcomorg/trident-monorepo']) {
|
|
78
|
+
pkg.devDependencies = pkg.devDependencies ?? {};
|
|
79
|
+
pkg.devDependencies['@mondaydotcomorg/trident-monorepo'] = await getLatestTrientMonorepoVersion();
|
|
80
|
+
}
|
|
81
|
+
if (!pkg.scripts?.nx) {
|
|
82
|
+
pkg.scripts = pkg.scripts ?? {};
|
|
83
|
+
pkg.scripts.nx = 'nx';
|
|
84
|
+
}
|
|
85
|
+
await fsExtra.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
86
|
+
// Only install if NX isn't already importable from the workspace
|
|
87
|
+
if (!isNxAvailable(workspaceHome)) {
|
|
88
|
+
const ok = await yarnInstall(workspaceHome, 'NX not found, installing');
|
|
89
|
+
if (!ok) {
|
|
90
|
+
util_logger.warn('workspace migrations require NX. Fix the install error and run "z2h-cli migrate" to retry.');
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
// ─── Main ───
|
|
97
|
+
async function autoMigrate(cliVersion, dryRun = false) {
|
|
98
|
+
const workspaceHome = util_workspace.getWorkspaceHome();
|
|
99
|
+
if (!(await util_workspace.isZ2hWorkspace(workspaceHome))) {
|
|
100
|
+
util_logger.warn('no Z2H workspace found');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const state = await readState(workspaceHome);
|
|
104
|
+
const lastVersion = state?.lastMigratedVersion ?? '0.0.0';
|
|
105
|
+
if (!semver__default.default.gt(cliVersion, lastVersion)) {
|
|
106
|
+
util_logger.info('workspace is up to date — no migrations pending');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
util_logger.info(`migrating workspace from v${lastVersion} → v${cliVersion}...`);
|
|
110
|
+
// migrations.json is at the package root — two dirs up from dist/migrations/
|
|
111
|
+
const thisDir = path__default.default.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('migrations/auto-migrate.js', document.baseURI).href))));
|
|
112
|
+
const migrationsJsonPath = path__default.default.resolve(thisDir, '..', '..', 'migrations.json');
|
|
113
|
+
if (!(await fsExtra.pathExists(migrationsJsonPath))) {
|
|
114
|
+
util_logger.warn('migrations.json not found — skipping migrations');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const migrationsJson = (await fsExtra.readJson(migrationsJsonPath));
|
|
118
|
+
// Filter to pending migrations (version > lastVersion AND <= cliVersion)
|
|
119
|
+
const pending = Object.entries(migrationsJson.migrations)
|
|
120
|
+
.filter(([, entry]) => semver__default.default.gt(entry.version, lastVersion) && semver__default.default.lte(entry.version, cliVersion))
|
|
121
|
+
.sort(([, a], [, b]) => semver__default.default.compare(a.version, b.version));
|
|
122
|
+
if (dryRun) {
|
|
123
|
+
util_logger.info(`${pending.length} pending migration(s) (dry-run — no changes will be applied):`);
|
|
124
|
+
for (const [, entry] of pending) {
|
|
125
|
+
util_logger.info(` - ${entry.description} (v${entry.version})`);
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (pending.length === 0) {
|
|
130
|
+
await writeState(workspaceHome, { lastMigratedVersion: cliVersion });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const nxReady = await ensureWorkspaceNx(workspaceHome);
|
|
134
|
+
if (!nxReady) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
util_logger.info(`applying ${pending.length} workspace migration(s)...`);
|
|
138
|
+
// Resolve NX from the workspace's node_modules (not from CLI's own deps)
|
|
139
|
+
const wsRequire = node_module.createRequire(path__default.default.join(workspaceHome, 'package.json'));
|
|
140
|
+
const { FsTree, flushChanges } = wsRequire('nx/src/generators/tree');
|
|
141
|
+
let needsInstall = false;
|
|
142
|
+
for (const [name, entry] of pending) {
|
|
143
|
+
const tree = new FsTree(workspaceHome, false);
|
|
144
|
+
try {
|
|
145
|
+
const factoryPath = path__default.default.resolve(path__default.default.dirname(migrationsJsonPath), entry.migrate);
|
|
146
|
+
const mod = require(factoryPath);
|
|
147
|
+
const fn = mod.default || mod;
|
|
148
|
+
fn(tree);
|
|
149
|
+
const changes = tree.listChanges();
|
|
150
|
+
if (changes.length > 0) {
|
|
151
|
+
if (changes.some(c => c.path.endsWith('package.json'))) {
|
|
152
|
+
needsInstall = true;
|
|
153
|
+
}
|
|
154
|
+
flushChanges(workspaceHome, changes);
|
|
155
|
+
util_logger.info(` ✓ ${entry.description}`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
util_logger.info(` ○ ${entry.description} (no changes needed)`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
util_logger.warn(` ✗ migration "${name}" failed: ${err.message}`);
|
|
163
|
+
util_logger.warn(' skipping remaining migrations. Run "z2h-cli migrate" to retry.');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (needsInstall) {
|
|
168
|
+
await yarnInstall(workspaceHome, 'package.json changed');
|
|
169
|
+
}
|
|
170
|
+
await writeState(workspaceHome, { lastMigratedVersion: cliVersion });
|
|
171
|
+
util_logger.info('workspace migrations complete.');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
exports.autoMigrate = autoMigrate;
|
package/migrations.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"migrations": {
|
|
3
|
+
"remove-per-consumer-yarnrc": {
|
|
4
|
+
"version": "0.4.1",
|
|
5
|
+
"description": "Remove per-consumer .yarnrc.yml files (workspace root provides config)",
|
|
6
|
+
"migrate": "./dist/migrations/001-remove-per-consumer-yarnrc"
|
|
7
|
+
},
|
|
8
|
+
"remove-z2h-cli-from-workspace-deps": {
|
|
9
|
+
"version": "0.4.1",
|
|
10
|
+
"description": "Remove z2h-cli from workspace and consumer devDependencies (managed globally)",
|
|
11
|
+
"migrate": "./dist/migrations/002-remove-z2h-cli-from-workspace-deps"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mondaydotcomorg/z2h-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"bin": "./bin/z2h-cli.js",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"files": [
|
|
35
35
|
"bin/",
|
|
36
36
|
"dist/",
|
|
37
|
-
"src/"
|
|
37
|
+
"src/",
|
|
38
|
+
"migrations.json"
|
|
38
39
|
],
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"@aws-sdk/client-s3": "^3.706.0",
|
|
@@ -47,15 +48,18 @@
|
|
|
47
48
|
"mime-types": "^2.1.35",
|
|
48
49
|
"p-map": "^7.0.2",
|
|
49
50
|
"picocolors": "^1.1.1",
|
|
51
|
+
"semver": "^7.6.0",
|
|
50
52
|
"tsx": "^4.19.2"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
55
|
"@mondaydotcomorg/trident-library": "^1.2.7",
|
|
56
|
+
"@nx/devkit": "^21.6.0",
|
|
54
57
|
"@types/cors": "^2.8.19",
|
|
55
58
|
"@types/express": "^5.0.6",
|
|
56
59
|
"@types/fs-extra": "^11.0.4",
|
|
57
60
|
"@types/mime-types": "^2.1.4",
|
|
58
61
|
"@types/node": "^22.19.0",
|
|
62
|
+
"@types/semver": "^7.5.0",
|
|
59
63
|
"typescript": "^5.7.2",
|
|
60
64
|
"vitest": "^2.1.4"
|
|
61
65
|
},
|
|
@@ -61,13 +61,11 @@ describe('create-workspace', () => {
|
|
|
61
61
|
expect(execa).not.toHaveBeenCalled();
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
it('
|
|
64
|
+
it('does not include z2h-cli in workspace devDeps (managed globally)', async () => {
|
|
65
65
|
await createWorkspaceCommand({});
|
|
66
66
|
|
|
67
67
|
const pkg = await readJson(path.join(tmp, 'package.json'));
|
|
68
|
-
|
|
69
|
-
expect(cliDep).toBeTruthy();
|
|
70
|
-
expect(cliDep).not.toBe('__Z2H_CLI_VERSION__'); // should be substituted
|
|
68
|
+
expect(pkg.devDependencies['@mondaydotcomorg/z2h-cli']).toBeUndefined();
|
|
71
69
|
});
|
|
72
70
|
|
|
73
71
|
it('ships the yarn berry release inline so any `yarn` invocation runs 4.x without corepack', async () => {
|
|
@@ -70,7 +70,6 @@ describe('create', () => {
|
|
|
70
70
|
const pkg = await readJson(path.join(appDir, 'package.json'));
|
|
71
71
|
expect(pkg.name).toBe('demo-app');
|
|
72
72
|
expect(pkg.private).toBe(true);
|
|
73
|
-
expect(pkg.scripts.start).toBe('z2h-cli dev');
|
|
74
73
|
|
|
75
74
|
const indexSrc = (await import('node:fs/promises')).readFile;
|
|
76
75
|
const content = await indexSrc(path.join(appDir, 'src', 'index.tsx'), 'utf8');
|
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { ensureDir,
|
|
3
|
+
import { ensureDir, writeFile, pathExists, readdir, copy } from 'fs-extra';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import { info, output, setOutputMode } from '../util/logger';
|
|
6
6
|
import { getWorkspaceHome } from '../util/workspace';
|
|
7
|
-
import { getTemplatesDir
|
|
7
|
+
import { getTemplatesDir } from '../util/templates';
|
|
8
8
|
|
|
9
9
|
export interface CreateWorkspaceOptions {
|
|
10
10
|
json?: boolean;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Read the current z2h-cli version from its own package.json — the version
|
|
15
|
-
* that gets pinned into the workspace's devDependencies. That way the
|
|
16
|
-
* workspace's `@mondaydotcomorg/z2h-cli` always matches the version that
|
|
17
|
-
* bootstrapped it.
|
|
18
|
-
*/
|
|
19
|
-
async function readCurrentCliVersion(): Promise<string> {
|
|
20
|
-
// util/templates.ts already locates `<package-root>/src/templates`. The
|
|
21
|
-
// package's own package.json sits two levels above templates/.
|
|
22
|
-
const templatesDir = getTemplatesDir();
|
|
23
|
-
const pkgPath = path.resolve(templatesDir, '..', '..', 'package.json');
|
|
24
|
-
const pkg = (await readJson(pkgPath)) as { version: string };
|
|
25
|
-
return `^${pkg.version}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
13
|
export async function createWorkspaceCommand(opts: CreateWorkspaceOptions): Promise<void> {
|
|
29
14
|
if (opts.json) {
|
|
30
15
|
setOutputMode('json');
|
|
@@ -34,7 +19,6 @@ export async function createWorkspaceCommand(opts: CreateWorkspaceOptions): Prom
|
|
|
34
19
|
const templatesDir = getTemplatesDir();
|
|
35
20
|
const wsTemplatesDir = path.join(templatesDir, 'workspace');
|
|
36
21
|
|
|
37
|
-
// Refuse non-empty target.
|
|
38
22
|
if (await pathExists(home)) {
|
|
39
23
|
const entries = await readdir(home);
|
|
40
24
|
if (entries.length > 0) {
|
|
@@ -44,24 +28,13 @@ export async function createWorkspaceCommand(opts: CreateWorkspaceOptions): Prom
|
|
|
44
28
|
await ensureDir(home);
|
|
45
29
|
}
|
|
46
30
|
|
|
47
|
-
const
|
|
48
|
-
readCurrentCliVersion(),
|
|
49
|
-
readFile(path.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8'),
|
|
50
|
-
]);
|
|
31
|
+
const pkgRaw = await readFile(path.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8');
|
|
51
32
|
|
|
52
|
-
// Sources have plain names (gitignore, yarnrc.yml, nvmrc, npmrc) so they
|
|
53
|
-
// aren't interpreted as the active config files of *this* package; we
|
|
54
|
-
// rename to dotfiles at the destination.
|
|
55
|
-
//
|
|
56
|
-
// Yarn berry ships inline at .yarn/releases/. The workspace's .yarnrc.yml
|
|
57
|
-
// sets `yarnPath` to point here, so any `yarn` invocation (system classic
|
|
58
|
-
// or otherwise) reads the rc, delegates to this binary, and runs as 4.5.1
|
|
59
|
-
// — no corepack, no global pin needed. Mirrors how bigbrain-z2h ships its
|
|
60
|
-
// own yarn release.
|
|
61
33
|
await Promise.all([
|
|
62
|
-
writeFile(path.join(home, 'package.json'),
|
|
34
|
+
writeFile(path.join(home, 'package.json'), pkgRaw),
|
|
63
35
|
copy(path.join(wsTemplatesDir, 'gitignore'), path.join(home, '.gitignore')),
|
|
64
36
|
copy(path.join(wsTemplatesDir, 'yarnrc.yml'), path.join(home, '.yarnrc.yml')),
|
|
37
|
+
copy(path.join(wsTemplatesDir, 'nx.json'), path.join(home, 'nx.json')),
|
|
65
38
|
copy(path.join(wsTemplatesDir, 'yarnlock'), path.join(home, 'yarn.lock')),
|
|
66
39
|
copy(path.join(wsTemplatesDir, 'nvmrc'), path.join(home, '.nvmrc')),
|
|
67
40
|
copy(path.join(wsTemplatesDir, 'npmrc'), path.join(home, '.npmrc')),
|
|
@@ -81,6 +54,5 @@ export async function createWorkspaceCommand(opts: CreateWorkspaceOptions): Prom
|
|
|
81
54
|
output(`Your workspace is ready! 🚀 Apps will live at ${home}`, {
|
|
82
55
|
ok: true,
|
|
83
56
|
workspace: home,
|
|
84
|
-
z2hCliVersion: cliVersion,
|
|
85
57
|
});
|
|
86
58
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { setOutputMode } from '../util/logger';
|
|
2
|
+
import { autoMigrate } from '../migrations/auto-migrate';
|
|
3
|
+
|
|
4
|
+
export interface MigrateOptions {
|
|
5
|
+
dryRun?: boolean;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function migrateCommand(cliVersion: string, opts: MigrateOptions): Promise<void> {
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
setOutputMode('json');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
await autoMigrate(cliVersion, opts.dryRun);
|
|
15
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { deployCommand } from './commands/deploy';
|
|
|
7
7
|
import { createWorkspaceCommand } from './commands/create-workspace';
|
|
8
8
|
import { createCommand } from './commands/create';
|
|
9
9
|
import { generateZ2HTokenCommand } from './commands/generate-z2h-token';
|
|
10
|
+
import { migrateCommand } from './commands/migrate';
|
|
10
11
|
import { errorLog, fail } from './util/logger';
|
|
11
12
|
|
|
12
13
|
const { version } = createRequire(import.meta.url)('../package.json') as { version: string };
|
|
@@ -125,6 +126,19 @@ async function main(): Promise<void> {
|
|
|
125
126
|
}
|
|
126
127
|
});
|
|
127
128
|
|
|
129
|
+
program
|
|
130
|
+
.command('migrate')
|
|
131
|
+
.description('Run pending workspace migrations')
|
|
132
|
+
.option('--dry-run', 'show pending migrations without applying any changes')
|
|
133
|
+
.option('--json', 'emit structured JSON on stdout')
|
|
134
|
+
.action(async (opts: { dryRun?: boolean; json?: boolean }) => {
|
|
135
|
+
try {
|
|
136
|
+
await migrateCommand(version, opts);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
fail(`migrate failed: ${(err as Error).message}`, { command: 'migrate' }, 1);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
128
142
|
await program.parseAsync(process.argv);
|
|
129
143
|
}
|
|
130
144
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Tree } from '@nx/devkit';
|
|
2
|
+
|
|
3
|
+
export default function removePerConsumerYarnrc(tree: Tree): void {
|
|
4
|
+
for (const entry of tree.children('.')) {
|
|
5
|
+
if (entry.startsWith('.')) {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
const pkgPath = `${entry}/package.json`;
|
|
9
|
+
const yarnrcPath = `${entry}/.yarnrc.yml`;
|
|
10
|
+
if (!tree.exists(pkgPath) || !tree.exists(yarnrcPath)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
tree.delete(yarnrcPath);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Tree } from '@nx/devkit';
|
|
2
|
+
|
|
3
|
+
export default function removeZ2hCliFromWorkspaceDeps(tree: Tree): void {
|
|
4
|
+
// Clean workspace root package.json
|
|
5
|
+
if (tree.exists('package.json')) {
|
|
6
|
+
const pkg = JSON.parse(tree.read('package.json', 'utf-8')!);
|
|
7
|
+
let changed = false;
|
|
8
|
+
|
|
9
|
+
if (pkg.devDependencies?.['@mondaydotcomorg/z2h-cli']) {
|
|
10
|
+
delete pkg.devDependencies['@mondaydotcomorg/z2h-cli'];
|
|
11
|
+
changed = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (pkg.resolutions?.['@mondaydotcomorg/z2h-cli']) {
|
|
15
|
+
delete pkg.resolutions['@mondaydotcomorg/z2h-cli'];
|
|
16
|
+
if (Object.keys(pkg.resolutions).length === 0) {
|
|
17
|
+
delete pkg.resolutions;
|
|
18
|
+
}
|
|
19
|
+
changed = true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (changed) {
|
|
23
|
+
tree.write('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Clean each consumer app's package.json (remove devDep, keep scripts)
|
|
28
|
+
for (const entry of tree.children('.')) {
|
|
29
|
+
if (entry.startsWith('.')) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const pkgPath = `${entry}/package.json`;
|
|
33
|
+
if (!tree.exists(pkgPath)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const pkg = JSON.parse(tree.read(pkgPath, 'utf-8')!);
|
|
38
|
+
|
|
39
|
+
if (pkg.devDependencies?.['@mondaydotcomorg/z2h-cli']) {
|
|
40
|
+
delete pkg.devDependencies['@mondaydotcomorg/z2h-cli'];
|
|
41
|
+
if (Object.keys(pkg.devDependencies).length === 0) {
|
|
42
|
+
delete pkg.devDependencies;
|
|
43
|
+
}
|
|
44
|
+
tree.write(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -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
|
+
}
|