@vzn/vx-migrate 0.0.626 → 0.0.628
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/README.md +2 -2
- package/package.json +2 -2
- package/src/adopt.ts +54 -14
- package/src/migrate.ts +83 -21
- package/src/workspace-plugins.ts +29 -0
package/README.md
CHANGED
|
@@ -219,8 +219,8 @@ bunx @vzn/vx-migrate --help # the usage, exit 0
|
|
|
219
219
|
It is the one command a repo needs (`pnpx @vzn/vx-migrate` in a pnpm repo works the same):
|
|
220
220
|
|
|
221
221
|
- In a terminal it asks which adoption you want: **native** (the default, a `vx.config.ts` per package) or **keep** (the workspace file `vx init` writes, declaring `turbo()` or `nx()`). `--native` / `--keep` answer it; with no terminal (CI, a pipe) it is native.
|
|
222
|
-
- With no `vx.workspace.*` yet, it writes one declaring the plugins the repo calls for (`src/workspace-plugins.ts`): the `@vzn/vx-lockfile` factory for the lockfile (`pnpm()`, `bun()`, `npm()`, `yarn()`), `scheduleHistoryPlugin()`, and `github()` when `.github/workflows` exists, from `@vzn/vx-ci`, each installed beside `@vzn/vx` at vx-migrate's own version. Keep adds them to the file `vx init` writes, or to one already in that shape; any other workspace file is the user's and left alone.
|
|
223
|
-
- It installs what the written files import with the repo's own manager (`packageManager`, else the lockfile): `@vzn/vx`, the declared plugins' packages, and `@vzn/vx-migrate` for keep or when a written task runs its `nx-exec` or `nx-env` bin (`pnpm add -D -w`, `yarn add -D -W` on Yarn 1 and without `-W` on Yarn 2+, `bun add -d`, `npm install -D`). A package the root both lists and has installed at vx-migrate's own version is left alone; any other is installed at that version. `--no-install` skips
|
|
222
|
+
- With no `vx.workspace.*` yet, it writes one declaring the plugins the repo calls for (`src/workspace-plugins.ts`): the `@vzn/vx-lockfile` factory for the lockfile (`pnpm()`, `bun()`, `npm()`, `yarn()`), `scheduleHistoryPlugin()`, and `github()` when `.github/workflows` exists, from `@vzn/vx-ci`, each installed beside `@vzn/vx` at vx-migrate's own version. Keep adds them to the file `vx init` writes, or to one already in that shape; any other workspace file is the user's and left alone. Native over a file vx wrote (a keep adoption's) rewrites it: `turbo()` and `nx()` go, since the configs written replace them, and the other plugins stay.
|
|
223
|
+
- It installs what the written files import with the repo's own manager (`packageManager`, else the lockfile): `@vzn/vx`, the declared plugins' packages, and `@vzn/vx-migrate` for keep or when a written task runs its `nx-exec` or `nx-env` bin (`pnpm add -D -w`, `yarn add -D -W` on Yarn 1 and without `-W` on Yarn 2+, `bun add -d`, `npm install -D`). A package the root both lists and has installed at vx-migrate's own version is left alone; any other is installed at that version. Native removes a listed `@vzn/vx-migrate` when nothing written needs it (no `nx-exec` / `nx-env` task, no `turboCache()` or `nxCache()` left in the workspace file): `pnpm remove -w`, `yarn remove` (`-W` on Yarn 1), `bun remove`, `npm uninstall`. `--no-install` skips both.
|
|
224
224
|
|
|
225
225
|
A Lerna repo is an Nx one: Lerna 6+ runs `lerna run` on Nx's task runner over the graph `nx graph` exports, nx.json or not, so a `lerna.json` with no nx.json and no turbo.json is mapped from that graph, and keep writes the workspace file declaring `nx()` (`vx init` adopts by nx.json, and would map the scripts). Lerna is the installed one, else the root manifest's range; with neither, the lerna.json is another tool's (lerna-lite reads it too, and runs no Nx). A root whose scripts never `lerna run` runs its tasks another way and publishes with Lerna (webdriverio: `run-s`, `pnpm -r`), so its scripts stay the source. Not where Lerna runs its own runner: `useNx: false`, or Lerna 5 without `useNx: true`; those are `vx init`'s scripts mapping. Beside turbo.json, Turbo runs the tasks and Lerna only publishes: turbo.json is the source.
|
|
226
226
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx-migrate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.628",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Adopt @vzn/vx from Turborepo or Nx: run the repo unchanged, migrate it to vx.config.ts, and keep your Turbo or Nx remote cache",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@vzn/vx": "^0.0.
|
|
42
|
+
"@vzn/vx": "^0.0.628"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
package/src/adopt.ts
CHANGED
|
@@ -111,14 +111,9 @@ export function missingPackages(
|
|
|
111
111
|
wanted: readonly string[],
|
|
112
112
|
version: string | null = null,
|
|
113
113
|
): string[] {
|
|
114
|
-
const
|
|
115
|
-
string,
|
|
116
|
-
Record<string, unknown> | undefined
|
|
117
|
-
>
|
|
118
|
-
const listed = (name: string) =>
|
|
119
|
-
['dependencies', 'devDependencies'].some((f) => pj[f]?.[name] !== undefined)
|
|
114
|
+
const listed = new Set(listedPackages(root, wanted))
|
|
120
115
|
return wanted.filter((p) => {
|
|
121
|
-
if (!listed(p)) return true
|
|
116
|
+
if (!listed.has(p)) return true
|
|
122
117
|
const installed = readText(path.join(root, 'node_modules', p, 'package.json'))
|
|
123
118
|
if (installed === '') return true
|
|
124
119
|
if (version === null) return false
|
|
@@ -126,17 +121,64 @@ export function missingPackages(
|
|
|
126
121
|
})
|
|
127
122
|
}
|
|
128
123
|
|
|
124
|
+
/** argv that removes `packages` from the root's dev dependencies. */
|
|
125
|
+
export function removeArgv(
|
|
126
|
+
root: string,
|
|
127
|
+
pm: PackageManager,
|
|
128
|
+
packages: readonly string[],
|
|
129
|
+
): string[] {
|
|
130
|
+
switch (pm) {
|
|
131
|
+
case 'pnpm':
|
|
132
|
+
return ['pnpm', 'remove', '-w', ...packages]
|
|
133
|
+
case 'yarn':
|
|
134
|
+
return readText(path.join(root, 'yarn.lock')).includes('__metadata:')
|
|
135
|
+
? ['yarn', 'remove', ...packages]
|
|
136
|
+
: ['yarn', 'remove', '-W', ...packages]
|
|
137
|
+
case 'bun':
|
|
138
|
+
return ['bun', 'remove', ...packages]
|
|
139
|
+
case 'npm':
|
|
140
|
+
return ['npm', 'uninstall', ...packages]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The `packages` the root package.json lists. */
|
|
145
|
+
export function listedPackages(root: string, packages: readonly string[]): string[] {
|
|
146
|
+
const pj = JSON.parse(readText(path.join(root, 'package.json')) || '{}') as Record<
|
|
147
|
+
string,
|
|
148
|
+
Record<string, unknown> | undefined
|
|
149
|
+
>
|
|
150
|
+
return packages.filter((p) =>
|
|
151
|
+
['dependencies', 'devDependencies'].some((f) => pj[f]?.[p] !== undefined),
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
/** Install `packages` (at `version`, given one) with the repo's manager, its output on the terminal. */
|
|
130
|
-
export
|
|
156
|
+
export function install(
|
|
131
157
|
root: string,
|
|
132
158
|
packages: readonly string[],
|
|
133
159
|
version: string | null = null,
|
|
134
160
|
): Promise<string> {
|
|
135
|
-
|
|
161
|
+
return runManager(
|
|
136
162
|
root,
|
|
137
|
-
|
|
138
|
-
|
|
163
|
+
installArgv(
|
|
164
|
+
root,
|
|
165
|
+
packageManagerOf(root, process.env['npm_config_user_agent']),
|
|
166
|
+
version === null ? packages : packages.map((p) => `${p}@${version}`),
|
|
167
|
+
),
|
|
168
|
+
'installing vx',
|
|
139
169
|
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Remove `packages` with the repo's manager. */
|
|
173
|
+
export function uninstall(root: string, packages: readonly string[]): Promise<string> {
|
|
174
|
+
return runManager(
|
|
175
|
+
root,
|
|
176
|
+
removeArgv(root, packageManagerOf(root, process.env['npm_config_user_agent']), packages),
|
|
177
|
+
`removing ${packages.join(' ')}`,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function runManager(root: string, argv: string[], what: string): Promise<string> {
|
|
140
182
|
const line = argv.join(' ')
|
|
141
183
|
process.stdout.write(`vx-migrate: ${line}\n`)
|
|
142
184
|
let code: number
|
|
@@ -146,9 +188,7 @@ export async function install(
|
|
|
146
188
|
code = -1
|
|
147
189
|
}
|
|
148
190
|
if (code !== 0) {
|
|
149
|
-
throw new UserError(
|
|
150
|
-
`installing vx failed (${line} exited ${code}); run it, then vx-migrate again`,
|
|
151
|
-
)
|
|
191
|
+
throw new UserError(`${what} failed (${line} exited ${code}); run it, then vx-migrate again`)
|
|
152
192
|
}
|
|
153
193
|
return line
|
|
154
194
|
}
|
package/src/migrate.ts
CHANGED
|
@@ -31,17 +31,20 @@ import { readNxJson } from './nx/nx-map.js'
|
|
|
31
31
|
import {
|
|
32
32
|
type AdoptionMode,
|
|
33
33
|
install,
|
|
34
|
+
listedPackages,
|
|
34
35
|
missingPackages,
|
|
35
36
|
MODE_QUESTION,
|
|
36
37
|
ownVersion,
|
|
37
38
|
packageManagerOf,
|
|
38
39
|
parseModeAnswer,
|
|
40
|
+
uninstall,
|
|
39
41
|
} from './adopt.js'
|
|
40
42
|
import { migrateTurbo } from './migrate-turbo.js'
|
|
41
43
|
import { migrateViteTask } from './migrate-vite-task.js'
|
|
42
44
|
import { turboConfigFile } from './turbo/turbo-map.js'
|
|
43
45
|
import {
|
|
44
46
|
extendWorkspaceFile,
|
|
47
|
+
generatedPlugins,
|
|
45
48
|
undeclared,
|
|
46
49
|
renderWorkspaceFile,
|
|
47
50
|
workspaceFileAt,
|
|
@@ -182,9 +185,20 @@ export async function migrateCmd(args: readonly string[]): Promise<number> {
|
|
|
182
185
|
if (mode === 'keep' && runner !== 'vite-task') return keep(root, runner, hasTurbo, lerna, parsed)
|
|
183
186
|
|
|
184
187
|
const format: MigrationFormat = parsed.mjs ? 'mjs' : 'ts'
|
|
185
|
-
// No workspace file yet
|
|
186
|
-
// repo calls for and the run settings
|
|
187
|
-
|
|
188
|
+
// No workspace file yet, or one vx wrote (a `--keep` adoption's): this run
|
|
189
|
+
// writes it, declaring the plugins the repo calls for and the run settings
|
|
190
|
+
// nx.json holds. turbo() and nx() go: the configs written here replace them.
|
|
191
|
+
const existingWorkspace = workspaceFileAt(root)
|
|
192
|
+
const existingFormat = /\.(ts|mjs)$/.exec(existingWorkspace ?? '')?.[1] as
|
|
193
|
+
| MigrationFormat
|
|
194
|
+
| undefined
|
|
195
|
+
const adopted =
|
|
196
|
+
existingWorkspace === undefined
|
|
197
|
+
? []
|
|
198
|
+
: existingFormat === undefined
|
|
199
|
+
? null
|
|
200
|
+
: generatedPlugins(readFileSync(path.join(root, existingWorkspace), 'utf8'))
|
|
201
|
+
const writesWorkspace = adopted !== null
|
|
188
202
|
let source: string
|
|
189
203
|
let plan: MigrationPlan
|
|
190
204
|
if (runner === 'vite-task') {
|
|
@@ -238,8 +252,11 @@ export async function migrateCmd(args: readonly string[]): Promise<number> {
|
|
|
238
252
|
existsSync(path.join(p.dir, configName))),
|
|
239
253
|
) ||
|
|
240
254
|
plan.extraFiles.some((f) => existsSync(path.join(root, f.relPath))))
|
|
241
|
-
|
|
242
|
-
const plugins = writesWorkspace
|
|
255
|
+
const kept = (adopted ?? []).filter((p) => !ADOPTION_PLUGINS.has(`${p.pkg}#${p.factory}`))
|
|
256
|
+
const plugins = writesWorkspace
|
|
257
|
+
? [...kept, ...workspacePlugins(root).filter((p) => !kept.some((k) => k.factory === p.factory))]
|
|
258
|
+
: []
|
|
259
|
+
let workspaceText: string | undefined
|
|
243
260
|
if (writesWorkspace) {
|
|
244
261
|
const fields =
|
|
245
262
|
runner === 'nx'
|
|
@@ -247,16 +264,18 @@ export async function migrateCmd(args: readonly string[]): Promise<number> {
|
|
|
247
264
|
f.source === undefined ? [] : [{ field: f.field, source: f.source }],
|
|
248
265
|
)
|
|
249
266
|
: []
|
|
267
|
+
workspaceText = renderWorkspaceFile(plugins, existingFormat ?? format, fields)
|
|
268
|
+
// Drop the notes that told the user to declare the lockfile plugin and
|
|
269
|
+
// to remove turbo() or nx().
|
|
250
270
|
plan = {
|
|
251
271
|
...plan,
|
|
252
|
-
headerNotes: plan.headerNotes.filter(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
],
|
|
272
|
+
headerNotes: plan.headerNotes.filter(
|
|
273
|
+
(n) => !n.includes('from @vzn/vx-lockfile') && !n.includes(' still declares '),
|
|
274
|
+
),
|
|
275
|
+
extraFiles:
|
|
276
|
+
existingWorkspace === undefined
|
|
277
|
+
? [...plan.extraFiles, { relPath: `vx.workspace.${format}`, contents: workspaceText }]
|
|
278
|
+
: plan.extraFiles,
|
|
260
279
|
}
|
|
261
280
|
}
|
|
262
281
|
// An executor target is an `nx-exec` line and a `.env` one an `nx-env`
|
|
@@ -267,13 +286,26 @@ export async function migrateCmd(args: readonly string[]): Promise<number> {
|
|
|
267
286
|
return typeof cmd === 'string' && /^nx-(exec|env) /.test(cmd)
|
|
268
287
|
}),
|
|
269
288
|
)
|
|
289
|
+
// Nothing written imports vx-migrate or runs its bins: a `--keep`
|
|
290
|
+
// adoption's install of it is dropped with turbo() / nx().
|
|
291
|
+
const needsSelf =
|
|
292
|
+
runsBins ||
|
|
293
|
+
(workspaceText ?? readFileSync(path.join(root, existingWorkspace!), 'utf8')).includes(
|
|
294
|
+
"'@vzn/vx-migrate'",
|
|
295
|
+
)
|
|
270
296
|
const headerNotes = refused
|
|
271
297
|
? []
|
|
272
|
-
:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
298
|
+
: [
|
|
299
|
+
...(existingWorkspace !== undefined && workspaceText !== undefined
|
|
300
|
+
? await rewriteWorkspace(root, existingWorkspace, workspaceText, adopted!, parsed.dry)
|
|
301
|
+
: []),
|
|
302
|
+
...(await prepareRepo(
|
|
303
|
+
root,
|
|
304
|
+
['@vzn/vx', ...(needsSelf ? ['@vzn/vx-migrate'] : []), ...plugins.map((p) => p.pkg)],
|
|
305
|
+
parsed,
|
|
306
|
+
needsSelf ? [] : ['@vzn/vx-migrate'],
|
|
307
|
+
)),
|
|
308
|
+
]
|
|
277
309
|
return applyMigration({
|
|
278
310
|
root,
|
|
279
311
|
metas,
|
|
@@ -312,22 +344,52 @@ async function askMode(
|
|
|
312
344
|
}
|
|
313
345
|
}
|
|
314
346
|
|
|
347
|
+
const ADOPTION_PLUGINS = new Set(['@vzn/vx-migrate#turbo', '@vzn/vx-migrate#nx'])
|
|
348
|
+
|
|
315
349
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
350
|
+
* A `--keep` adoption's workspace file, written over for a native one.
|
|
351
|
+
* Returns the report's line; writes nothing under `--dry`.
|
|
352
|
+
*/
|
|
353
|
+
async function rewriteWorkspace(
|
|
354
|
+
root: string,
|
|
355
|
+
file: string,
|
|
356
|
+
text: string,
|
|
357
|
+
before: readonly WorkspacePlugin[],
|
|
358
|
+
dry: boolean,
|
|
359
|
+
): Promise<string[]> {
|
|
360
|
+
if (readFileSync(path.join(root, file), 'utf8') === text) return []
|
|
361
|
+
const dropped = before.filter((p) => ADOPTION_PLUGINS.has(`${p.pkg}#${p.factory}`))
|
|
362
|
+
const names = dropped.map((p) => `${p.factory}()`).join(', ')
|
|
363
|
+
if (dry)
|
|
364
|
+
return [`would ${dropped.length > 0 ? `drop ${names} from` : 'rewrite'} ${file} (dry run)`]
|
|
365
|
+
await Bun.write(path.join(root, file), text)
|
|
366
|
+
return [`${dropped.length > 0 ? `dropped ${names} from` : 'rewrote'} ${file}`]
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Install what the written files import and remove what they no longer
|
|
371
|
+
* do. Returns the report's lines; changes nothing under `--dry`.
|
|
318
372
|
*/
|
|
319
373
|
async function prepareRepo(
|
|
320
374
|
root: string,
|
|
321
375
|
wanted: readonly string[],
|
|
322
376
|
args: MigrateArgs,
|
|
377
|
+
unwanted: readonly string[] = [],
|
|
323
378
|
): Promise<string[]> {
|
|
379
|
+
if (args.noInstall === true) return []
|
|
324
380
|
const notes: string[] = []
|
|
325
381
|
const version = ownVersion()
|
|
326
|
-
const missing =
|
|
382
|
+
const missing = missingPackages(root, wanted, version)
|
|
327
383
|
if (missing.length > 0) {
|
|
328
384
|
if (args.dry) notes.push(`would install ${missing.join(' ')} (dry run)`)
|
|
329
385
|
else notes.push(`installed ${missing.join(' ')} (${await install(root, missing, version)})`)
|
|
330
386
|
}
|
|
387
|
+
// Last: the package removed may be the one running this.
|
|
388
|
+
const listed = listedPackages(root, unwanted)
|
|
389
|
+
if (listed.length > 0) {
|
|
390
|
+
if (args.dry) notes.push(`would remove ${listed.join(' ')} (dry run)`)
|
|
391
|
+
else notes.push(`removed ${listed.join(' ')} (${await uninstall(root, listed)})`)
|
|
392
|
+
}
|
|
331
393
|
return notes
|
|
332
394
|
}
|
|
333
395
|
|
package/src/workspace-plugins.ts
CHANGED
|
@@ -89,3 +89,32 @@ export function extendWorkspaceFile(
|
|
|
89
89
|
`}${tail}`,
|
|
90
90
|
].join('\n')
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The plugins of a workspace file vx wrote (`vx init`, `--keep`, a native
|
|
95
|
+
* migration): `@vzn` imports and argument-less calls, nothing else. Null
|
|
96
|
+
* for any other file, which is the user's and left alone.
|
|
97
|
+
*/
|
|
98
|
+
export function generatedPlugins(text: string): WorkspacePlugin[] | null {
|
|
99
|
+
const pkgOf = new Map<string, string>()
|
|
100
|
+
const body: string[] = []
|
|
101
|
+
for (const line of text.split('\n')) {
|
|
102
|
+
const m = /^import \{ ([\w, ]+) \} from '(@vzn\/[\w./-]+)'$/.exec(line)
|
|
103
|
+
if (m !== null) for (const name of m[1]!.split(',')) pkgOf.set(name.trim(), m[2]!)
|
|
104
|
+
else if (line !== "import type { WorkspaceConfig } from '@vzn/vx/config'") body.push(line)
|
|
105
|
+
}
|
|
106
|
+
const m =
|
|
107
|
+
/^export default \{\s*plugins:\s*\[((?:\s*\w+\(\)\s*,?)*)\s*\],?\s*\}(?: satisfies WorkspaceConfig)?$/.exec(
|
|
108
|
+
body.join('\n').trim(),
|
|
109
|
+
)
|
|
110
|
+
if (m === null) return null
|
|
111
|
+
const factories = [...m[1]!.matchAll(/(\w+)\(\)/g)].map((c) => c[1]!)
|
|
112
|
+
// No plugin at all is a file vx never wrote.
|
|
113
|
+
if (
|
|
114
|
+
factories.length === 0 ||
|
|
115
|
+
factories.length !== pkgOf.size ||
|
|
116
|
+
factories.some((f) => !pkgOf.has(f))
|
|
117
|
+
)
|
|
118
|
+
return null
|
|
119
|
+
return factories.map((factory) => ({ pkg: pkgOf.get(factory)!, factory }))
|
|
120
|
+
}
|