@trackunit/migrations 0.2.59 → 0.2.60
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/CHANGELOG.md +13 -0
- package/README.md +29 -10
- package/package.json +3 -2
- package/src/generators/migrate/generator.js +45 -10
- package/src/generators/migrate/generator.js.map +1 -1
- package/src/generators/migrate/schema.json +5 -0
- package/src/generators/utils/upgrade-dependencies.js +200 -0
- package/src/generators/utils/upgrade-dependencies.js.map +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 0.2.60 (2026-08-26)
|
|
2
|
+
|
|
3
|
+
### 🩹 Fixes
|
|
4
|
+
|
|
5
|
+
- **migrations:** fail when nothing can be resolved from the registry [GLU-2058] ([a4d04d40b67](https://github.com/Trackunit/manager/commit/a4d04d40b67))
|
|
6
|
+
- **migrations:** judge each dependency field on its own specifier [GLU-2057] ([80be5ef0ef5](https://github.com/Trackunit/manager/commit/80be5ef0ef5))
|
|
7
|
+
- **migrations:** upgrade every @trackunit dependency, not just ones with codemods [GLU-2054] ([d9b74407648](https://github.com/Trackunit/manager/commit/d9b74407648))
|
|
8
|
+
|
|
9
|
+
### ❤️ Thank You
|
|
10
|
+
|
|
11
|
+
- Cursor @cursoragent
|
|
12
|
+
- Mikkel Thorbjørn Andersen
|
|
13
|
+
|
|
1
14
|
## 0.2.59 (2026-08-25)
|
|
2
15
|
|
|
3
16
|
### 🩹 Fixes
|
package/README.md
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
# @trackunit/migrations
|
|
2
2
|
|
|
3
|
-
Coordinator library that
|
|
3
|
+
Coordinator library that upgrades every `@trackunit/*` dependency to its latest published version and runs pending migrations.
|
|
4
4
|
|
|
5
5
|
## How it works
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`migrate` does two independent things, in this order:
|
|
8
8
|
|
|
9
|
-
1.
|
|
10
|
-
2.
|
|
9
|
+
1. **Upgrade dependencies.** Every `@trackunit/*` entry in the consumer's `dependencies` and `devDependencies` is moved to its latest published version, resolved from the npm registry. This is unconditional -- a package that ships no codemods still gets bumped -- and the range operator the consumer chose is preserved, so `^1.2.3` becomes `^2.0.0` rather than a pin.
|
|
10
|
+
2. **Collect migrations.** Each published `@trackunit/*` package can declare a `"migrations"` field in its `package.json` pointing to a `migrations.json` manifest. The library recursively scans `node_modules` (including nested/transitive dependencies) to find those packages and queues any migration newer than the last one applied.
|
|
11
|
+
|
|
12
|
+
The version bump reads the registry rather than `node_modules` on purpose. Migrations only exist inside published packages, so gating the bump on discovering a `migrations` field means a workspace far enough behind can never upgrade: its installed packages predate the field, nothing is discovered, and nothing is bumped.
|
|
13
|
+
|
|
14
|
+
A package that cannot be resolved is reported and left alone, and the summary says how many were skipped so a partial upgrade is never mistaken for a complete one. If *no* package resolves, `migrate` fails instead of reporting success -- that is an environment problem (no network, a proxying or private registry, an expired token), and treating it as "already up to date" would recreate the silent no-op this generator exists to remove.
|
|
15
|
+
|
|
16
|
+
A package declared in both `dependencies` and `devDependencies` is evaluated once per field against that field's own specifier, so a `workspace:*` devDependency survives an upgrade of the `dependencies` entry beside it. The registry is still queried once per package name.
|
|
17
|
+
|
|
18
|
+
Because `node_modules` is only refreshed after the generator's file tree is flushed, migrations found in a run that changed versions came from the packages being replaced. When versions change, `migrate` installs and asks you to run it again; the second pass sees the new packages. Pass `--skipInstall` to handle the install yourself.
|
|
19
|
+
|
|
20
|
+
Inside the manager monorepo the registry step is skipped -- `@trackunit/*` libs are workspace projects whose versions are owned by CI -- and migrations are discovered from `libs/` and `apps/` instead.
|
|
21
|
+
|
|
22
|
+
Then the second generator applies them:
|
|
23
|
+
|
|
24
|
+
- **`run-migrations`** -- executes the pending migrations from `trackunit-migrations.json`, then clears the list
|
|
11
25
|
|
|
12
26
|
Version state is tracked in `trackunit-migrations.json` so migrations that have already been applied are not re-run.
|
|
13
27
|
|
|
@@ -24,10 +38,13 @@ flowchart TD
|
|
|
24
38
|
filterVersion -->|Yes| collect["Add to pending list"]
|
|
25
39
|
end
|
|
26
40
|
|
|
27
|
-
subgraph updatePkg [Version Updates]
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
subgraph updatePkg [Version Updates: every @trackunit/* dependency]
|
|
42
|
+
listDeps["List @trackunit/* in\nconsumer dependencies +\ndevDependencies"]
|
|
43
|
+
listDeps --> resolveLatest["Resolve latest version\nfrom npm registry"]
|
|
44
|
+
resolveLatest --> compareDeps{"Latest >\npackage.json version?"}
|
|
45
|
+
compareDeps -->|Yes| bumpDep["Rewrite version in\nconsumer package.json,\nkeeping the range operator"]
|
|
30
46
|
compareDeps -->|No| noop[No change]
|
|
47
|
+
bumpDep --> reinstall["Install, then re-run migrate\nto see migrations shipped\nin the new versions"]
|
|
31
48
|
end
|
|
32
49
|
|
|
33
50
|
collect --> writeFile["Write trackunit-migrations.json\nwith pending migrations +\nupdated migratedVersions"]
|
|
@@ -45,10 +62,10 @@ flowchart TD
|
|
|
45
62
|
|
|
46
63
|
```mermaid
|
|
47
64
|
flowchart LR
|
|
48
|
-
subgraph nodeModules ["node_modules (recursive scan)"]
|
|
65
|
+
subgraph nodeModules ["node_modules (recursive scan, for migrations only)"]
|
|
49
66
|
rc["@trackunit/react-components\nv2.0.0\nmigrations: ./migrations.json"]
|
|
50
67
|
rcc["@trackunit/react-chart-components\nv1.20.0\nmigrations: ./migrations.json"]
|
|
51
|
-
su["@trackunit/shared-utils\nv1.13.0\n(no migrations field)"]
|
|
68
|
+
su["@trackunit/shared-utils\nv1.13.0\n(no migrations field --\nstill version-bumped)"]
|
|
52
69
|
nested["some-lib/node_modules/\n@trackunit/react-components\nv1.21.8 (older, deduped out)"]
|
|
53
70
|
end
|
|
54
71
|
|
|
@@ -81,7 +98,9 @@ If a migration needs to detect or rewrite Tailwind/classname strings in source f
|
|
|
81
98
|
nx g @trackunit/migrations:migrate
|
|
82
99
|
```
|
|
83
100
|
|
|
84
|
-
This
|
|
101
|
+
This moves every `@trackunit/*` dependency in your `package.json` to its latest published version and writes a `trackunit-migrations.json` file listing any pending migrations. Review the file before proceeding.
|
|
102
|
+
|
|
103
|
+
If any version changed, the generator installs the new packages and asks you to run it again -- migrations ship inside the packages themselves, so the newly installed versions are only inspectable on a second pass. Add `--skipInstall` to run the install yourself, and `--verbose` to see the per-package decisions, including the dependencies that were left alone.
|
|
85
104
|
|
|
86
105
|
### Step 2: Run pending migrations
|
|
87
106
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/migrations",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.60",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@nx/devkit": "23.1.0",
|
|
13
13
|
"tslib": "^2.6.2",
|
|
14
|
-
"semver": "7.5.4"
|
|
14
|
+
"semver": "7.5.4",
|
|
15
|
+
"pacote": "^21.0.4"
|
|
15
16
|
},
|
|
16
17
|
"migrations": "./migrations.json",
|
|
17
18
|
"types": "./src/index.d.ts",
|
|
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const devkit_1 = require("@nx/devkit");
|
|
6
6
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
7
|
const discover_packages_1 = require("../utils/discover-packages");
|
|
8
|
+
const upgrade_dependencies_1 = require("../utils/upgrade-dependencies");
|
|
8
9
|
const TRACKUNIT_MIGRATIONS_FILE = "trackunit-migrations.json";
|
|
9
10
|
const getLastMigratedVersion = (tree, packageName) => {
|
|
10
11
|
if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE))
|
|
@@ -23,15 +24,18 @@ const getCurrentDepVersion = (tree, packageName) => {
|
|
|
23
24
|
};
|
|
24
25
|
const updateDepVersion = (tree, pkg) => {
|
|
25
26
|
const currentDep = getCurrentDepVersion(tree, pkg.name);
|
|
26
|
-
if (currentDep === undefined
|
|
27
|
+
if (currentDep === undefined)
|
|
28
|
+
return;
|
|
29
|
+
const currentVersion = (0, upgrade_dependencies_1.pinnedVersionOf)(currentDep);
|
|
30
|
+
if (currentVersion === undefined || (0, discover_packages_1.compareVersions)(pkg.version, currentVersion) <= 0) {
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
(0, devkit_1.updateJson)(tree, "package.json", json => {
|
|
30
34
|
if (json.dependencies?.[pkg.name] !== undefined) {
|
|
31
|
-
json.dependencies[pkg.name] = pkg.version;
|
|
35
|
+
json.dependencies[pkg.name] = (0, upgrade_dependencies_1.applyVersionKeepingRange)(json.dependencies[pkg.name], pkg.version);
|
|
32
36
|
}
|
|
33
37
|
if (json.devDependencies?.[pkg.name] !== undefined) {
|
|
34
|
-
json.devDependencies[pkg.name] = pkg.version;
|
|
38
|
+
json.devDependencies[pkg.name] = (0, upgrade_dependencies_1.applyVersionKeepingRange)(json.devDependencies[pkg.name], pkg.version);
|
|
35
39
|
}
|
|
36
40
|
return json;
|
|
37
41
|
});
|
|
@@ -69,17 +73,43 @@ const collectPendingMigrations = (pkg, lastMigrated, migrationsJson, workspaceRo
|
|
|
69
73
|
}
|
|
70
74
|
return pending;
|
|
71
75
|
};
|
|
76
|
+
// node_modules is only refreshed after the generator's tree is flushed, so the
|
|
77
|
+
// migrations discovered in this run came from the versions being replaced. A
|
|
78
|
+
// second pass over the freshly installed packages is what surfaces migrations
|
|
79
|
+
// that only exist in the new versions -- which, for a workspace far enough
|
|
80
|
+
// behind that nothing shipped migrations yet, is all of them.
|
|
81
|
+
const reportUpgradeFollowUp = (upgrades, willInstall) => {
|
|
82
|
+
const pmc = (0, devkit_1.getPackageManagerCommand)((0, devkit_1.detectPackageManager)());
|
|
83
|
+
const rerun = "nx g @trackunit/migrations:migrate";
|
|
84
|
+
if (willInstall) {
|
|
85
|
+
devkit_1.logger.info(`\n${upgrades.length} dependency version(s) changed. Installing, then re-run "${rerun}" to pick up migrations that ship with the new versions.`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
devkit_1.logger.warn(`\n${upgrades.length} dependency version(s) changed, so node_modules is now stale. Run:\n ${pmc.install}\n ${rerun}`);
|
|
89
|
+
};
|
|
72
90
|
/**
|
|
73
|
-
* Step 1:
|
|
74
|
-
*
|
|
75
|
-
*
|
|
91
|
+
* Step 1: Upgrades every `@trackunit/*` dependency to its latest published
|
|
92
|
+
* version, discovers installed @trackunit packages, and writes
|
|
93
|
+
* trackunit-migrations.json with pending migrations.
|
|
94
|
+
*
|
|
95
|
+
* The dependency upgrade is unconditional and independent of migrations:
|
|
96
|
+
* a package without codemods still gets its version bumped.
|
|
76
97
|
*
|
|
77
98
|
* Usage: nx g @trackunit/migrations:migrate
|
|
78
99
|
* Then: nx g @trackunit/migrations:run-migrations
|
|
100
|
+
*
|
|
101
|
+
* @param tree - The NX virtual file system for the workspace being migrated.
|
|
102
|
+
* @param options - Generator options.
|
|
103
|
+
* @returns {Promise<GeneratorCallback | undefined>} An install task when dependency versions changed, otherwise undefined.
|
|
79
104
|
*/
|
|
80
|
-
const migrate = (tree, options) => {
|
|
81
|
-
const
|
|
105
|
+
const migrate = async (tree, options) => {
|
|
106
|
+
const verbose = options.verbose ?? false;
|
|
82
107
|
const workspaceRoot = process.cwd();
|
|
108
|
+
// Inside manager the @trackunit libs are workspace projects whose versions
|
|
109
|
+
// are owned by CI, so resolving them from the registry would fight the
|
|
110
|
+
// release process. Migration discovery still covers them via libs/ and apps/.
|
|
111
|
+
const upgrades = (0, upgrade_dependencies_1.isManagerWorkspace)(tree) ? [] : await (0, upgrade_dependencies_1.upgradeTrackunitDependencies)(tree, verbose);
|
|
112
|
+
const packages = (0, discover_packages_1.discoverTrackunitPackages)();
|
|
83
113
|
devkit_1.logger.info(`Discovered ${packages.length} @trackunit/* package(s) with migrations`);
|
|
84
114
|
const allPending = [];
|
|
85
115
|
const migratedVersions = {};
|
|
@@ -96,14 +126,14 @@ const migrate = (tree, options) => {
|
|
|
96
126
|
updateDepVersion(tree, pkg);
|
|
97
127
|
const migrationsJson = (0, discover_packages_1.loadMigrationsJson)(pkg.migrationsJsonPath);
|
|
98
128
|
if (!migrationsJson?.generators || Object.keys(migrationsJson.generators).length === 0) {
|
|
99
|
-
if (
|
|
129
|
+
if (verbose) {
|
|
100
130
|
devkit_1.logger.info(` ${pkg.name}: no migrations defined`);
|
|
101
131
|
}
|
|
102
132
|
migratedVersions[pkg.name] = pkg.version;
|
|
103
133
|
continue;
|
|
104
134
|
}
|
|
105
135
|
const lastMigrated = getLastMigratedVersion(tree, pkg.name);
|
|
106
|
-
if (
|
|
136
|
+
if (verbose) {
|
|
107
137
|
devkit_1.logger.info(` ${pkg.name}: installed=${pkg.version}, lastMigrated=${lastMigrated ?? "none"}`);
|
|
108
138
|
}
|
|
109
139
|
const pending = collectPendingMigrations(pkg, lastMigrated, migrationsJson, workspaceRoot);
|
|
@@ -129,6 +159,11 @@ const migrate = (tree, options) => {
|
|
|
129
159
|
devkit_1.logger.info(`\nWrote ${allPending.length} pending migration(s) to ${TRACKUNIT_MIGRATIONS_FILE}`);
|
|
130
160
|
devkit_1.logger.info("Review the file, then run: nx g @trackunit/migrations:run-migrations");
|
|
131
161
|
}
|
|
162
|
+
if (upgrades.length === 0)
|
|
163
|
+
return undefined;
|
|
164
|
+
const willInstall = options.skipInstall !== true;
|
|
165
|
+
reportUpgradeFollowUp(upgrades, willInstall);
|
|
166
|
+
return willInstall ? () => (0, devkit_1.installPackagesTask)(tree) : undefined;
|
|
132
167
|
};
|
|
133
168
|
exports.migrate = migrate;
|
|
134
169
|
exports.default = exports.migrate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../libs/migrations/src/generators/migrate/generator.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, logger, readJson, updateJson } from \"@nx/devkit\";\nimport path from \"path\";\nimport {\n type DiscoveredPackage,\n compareVersions,\n discoverTrackunitPackages,\n loadMigrationsJson,\n} from \"../utils/discover-packages\";\n\ntype MigrateSchema = {\n readonly verbose?: boolean;\n};\n\ntype MigrationEntry = {\n readonly version: string;\n readonly description: string;\n readonly implementation: string;\n readonly cli?: string;\n};\n\ntype MigrationsJson = {\n readonly generators?: Record<string, MigrationEntry>;\n};\n\ntype PendingMigration = {\n readonly package: string;\n readonly name: string;\n readonly version: string;\n readonly description: string;\n readonly implementation: string;\n};\n\ntype TrackunitMigrationsFile = {\n readonly migratedVersions: Record<string, string>;\n readonly migrations: ReadonlyArray<PendingMigration>;\n};\n\nconst TRACKUNIT_MIGRATIONS_FILE = \"trackunit-migrations.json\";\n\nconst getLastMigratedVersion = (tree: Tree, packageName: string): string | undefined => {\n if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE)) return undefined;\n\n try {\n const file: TrackunitMigrationsFile = readJson(tree, TRACKUNIT_MIGRATIONS_FILE);\n return file.migratedVersions[packageName];\n } catch {\n return undefined;\n }\n};\n\nconst getCurrentDepVersion = (tree: Tree, packageName: string): string | undefined => {\n const packageJson = readJson(tree, \"package.json\");\n return packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName];\n};\n\nconst updateDepVersion = (tree: Tree, pkg: DiscoveredPackage): void => {\n const currentDep = getCurrentDepVersion(tree, pkg.name);\n\n if (currentDep === undefined || compareVersions(pkg.version, currentDep.replace(/^[~^]/, \"\")) <= 0) {\n return;\n }\n\n updateJson(tree, \"package.json\", json => {\n if (json.dependencies?.[pkg.name] !== undefined) {\n json.dependencies[pkg.name] = pkg.version;\n }\n if (json.devDependencies?.[pkg.name] !== undefined) {\n json.devDependencies[pkg.name] = pkg.version;\n }\n return json;\n });\n\n logger.info(` Updated ${pkg.name} to ${pkg.version} in package.json`);\n};\n\n/**\n * Returns an implementation path that is relative to the workspace root\n * (`process.cwd()`) when the resolved path lives inside the workspace,\n * so that `trackunit-migrations.json` is portable between machines and\n * checkouts. Falls back to the absolute path for migrations sourced from\n * outside the workspace (e.g. node_modules in a non-default location).\n */\nconst portableImplementationPath = (absolutePath: string, workspaceRoot: string): string => {\n const relative = path.relative(workspaceRoot, absolutePath);\n if (relative === \"\" || relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n return absolutePath;\n }\n return relative.split(path.sep).join(\"/\");\n};\n\nconst collectPendingMigrations = (\n pkg: DiscoveredPackage,\n lastMigrated: string | undefined,\n migrationsJson: MigrationsJson,\n workspaceRoot: string\n): ReadonlyArray<PendingMigration> => {\n const pending: Array<PendingMigration> = [];\n const migrationsDir = path.dirname(pkg.migrationsJsonPath);\n\n for (const [name, entry] of Object.entries(migrationsJson.generators ?? {})) {\n if (lastMigrated !== undefined && compareVersions(entry.version, lastMigrated) <= 0) {\n continue;\n }\n\n const absoluteImpl = path.resolve(migrationsDir, entry.implementation);\n\n pending.push({\n package: pkg.name,\n name,\n version: entry.version,\n description: entry.description,\n implementation: portableImplementationPath(absoluteImpl, workspaceRoot),\n });\n }\n\n return pending;\n};\n\n/**\n * Step 1: Discovers installed @trackunit packages, compares versions\n * against the consumer's package.json, updates dependency versions,\n * and writes trackunit-migrations.json with pending migrations.\n *\n * Usage: nx g @trackunit/migrations:migrate\n * Then: nx g @trackunit/migrations:run-migrations\n */\nexport const migrate = (tree: Tree, options: MigrateSchema): void => {\n const packages = discoverTrackunitPackages();\n const workspaceRoot = process.cwd();\n\n logger.info(`Discovered ${packages.length} @trackunit/* package(s) with migrations`);\n\n const allPending: Array<PendingMigration> = [];\n const migratedVersions: Record<string, string> = {};\n\n if (tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {\n try {\n const existing: TrackunitMigrationsFile = readJson(tree, TRACKUNIT_MIGRATIONS_FILE);\n Object.assign(migratedVersions, existing.migratedVersions);\n } catch {\n // start fresh\n }\n }\n\n for (const pkg of packages) {\n updateDepVersion(tree, pkg);\n\n const migrationsJson = loadMigrationsJson(pkg.migrationsJsonPath);\n\n if (!migrationsJson?.generators || Object.keys(migrationsJson.generators).length === 0) {\n if (options.verbose) {\n logger.info(` ${pkg.name}: no migrations defined`);\n }\n migratedVersions[pkg.name] = pkg.version;\n continue;\n }\n\n const lastMigrated = getLastMigratedVersion(tree, pkg.name);\n\n if (options.verbose) {\n logger.info(` ${pkg.name}: installed=${pkg.version}, lastMigrated=${lastMigrated ?? \"none\"}`);\n }\n\n const pending = collectPendingMigrations(pkg, lastMigrated, migrationsJson, workspaceRoot);\n allPending.push(...pending);\n\n if (pending.length > 0) {\n logger.info(`${pkg.name}@${pkg.version}: ${pending.length} pending migration(s)`);\n // Defer advancing migratedVersions until run-migrations has applied\n // these entries. Otherwise a second `migrate` run would read this\n // version back as `lastMigrated` and silently drop the queued migrations.\n continue;\n }\n\n migratedVersions[pkg.name] = pkg.version;\n }\n\n const output: TrackunitMigrationsFile = {\n migratedVersions,\n migrations: allPending,\n };\n\n tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(output, null, 2));\n\n if (allPending.length === 0) {\n logger.info(\"No pending migrations. trackunit-migrations.json updated.\");\n } else {\n logger.info(`\\nWrote ${allPending.length} pending migration(s) to ${TRACKUNIT_MIGRATIONS_FILE}`);\n logger.info(\"Review the file, then run: nx g @trackunit/migrations:run-migrations\");\n }\n};\n\nexport default migrate;\n"]}
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../libs/migrations/src/generators/migrate/generator.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type GeneratorCallback,\n type Tree,\n detectPackageManager,\n getPackageManagerCommand,\n installPackagesTask,\n logger,\n readJson,\n updateJson,\n} from \"@nx/devkit\";\nimport path from \"path\";\nimport {\n type DiscoveredPackage,\n compareVersions,\n discoverTrackunitPackages,\n loadMigrationsJson,\n} from \"../utils/discover-packages\";\nimport {\n type DependencyUpgrade,\n applyVersionKeepingRange,\n isManagerWorkspace,\n pinnedVersionOf,\n upgradeTrackunitDependencies,\n} from \"../utils/upgrade-dependencies\";\n\ntype MigrateSchema = {\n readonly verbose?: boolean;\n readonly skipInstall?: boolean;\n};\n\ntype MigrationEntry = {\n readonly version: string;\n readonly description: string;\n readonly implementation: string;\n readonly cli?: string;\n};\n\ntype MigrationsJson = {\n readonly generators?: Record<string, MigrationEntry>;\n};\n\ntype PendingMigration = {\n readonly package: string;\n readonly name: string;\n readonly version: string;\n readonly description: string;\n readonly implementation: string;\n};\n\ntype TrackunitMigrationsFile = {\n readonly migratedVersions: Record<string, string>;\n readonly migrations: ReadonlyArray<PendingMigration>;\n};\n\nconst TRACKUNIT_MIGRATIONS_FILE = \"trackunit-migrations.json\";\n\nconst getLastMigratedVersion = (tree: Tree, packageName: string): string | undefined => {\n if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE)) return undefined;\n\n try {\n const file: TrackunitMigrationsFile = readJson(tree, TRACKUNIT_MIGRATIONS_FILE);\n return file.migratedVersions[packageName];\n } catch {\n return undefined;\n }\n};\n\nconst getCurrentDepVersion = (tree: Tree, packageName: string): string | undefined => {\n const packageJson = readJson(tree, \"package.json\");\n return packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName];\n};\n\nconst updateDepVersion = (tree: Tree, pkg: DiscoveredPackage): void => {\n const currentDep = getCurrentDepVersion(tree, pkg.name);\n if (currentDep === undefined) return;\n\n const currentVersion = pinnedVersionOf(currentDep);\n if (currentVersion === undefined || compareVersions(pkg.version, currentVersion) <= 0) {\n return;\n }\n\n updateJson(tree, \"package.json\", json => {\n if (json.dependencies?.[pkg.name] !== undefined) {\n json.dependencies[pkg.name] = applyVersionKeepingRange(json.dependencies[pkg.name], pkg.version);\n }\n if (json.devDependencies?.[pkg.name] !== undefined) {\n json.devDependencies[pkg.name] = applyVersionKeepingRange(json.devDependencies[pkg.name], pkg.version);\n }\n return json;\n });\n\n logger.info(` Updated ${pkg.name} to ${pkg.version} in package.json`);\n};\n\n/**\n * Returns an implementation path that is relative to the workspace root\n * (`process.cwd()`) when the resolved path lives inside the workspace,\n * so that `trackunit-migrations.json` is portable between machines and\n * checkouts. Falls back to the absolute path for migrations sourced from\n * outside the workspace (e.g. node_modules in a non-default location).\n */\nconst portableImplementationPath = (absolutePath: string, workspaceRoot: string): string => {\n const relative = path.relative(workspaceRoot, absolutePath);\n if (relative === \"\" || relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n return absolutePath;\n }\n return relative.split(path.sep).join(\"/\");\n};\n\nconst collectPendingMigrations = (\n pkg: DiscoveredPackage,\n lastMigrated: string | undefined,\n migrationsJson: MigrationsJson,\n workspaceRoot: string\n): ReadonlyArray<PendingMigration> => {\n const pending: Array<PendingMigration> = [];\n const migrationsDir = path.dirname(pkg.migrationsJsonPath);\n\n for (const [name, entry] of Object.entries(migrationsJson.generators ?? {})) {\n if (lastMigrated !== undefined && compareVersions(entry.version, lastMigrated) <= 0) {\n continue;\n }\n\n const absoluteImpl = path.resolve(migrationsDir, entry.implementation);\n\n pending.push({\n package: pkg.name,\n name,\n version: entry.version,\n description: entry.description,\n implementation: portableImplementationPath(absoluteImpl, workspaceRoot),\n });\n }\n\n return pending;\n};\n\n// node_modules is only refreshed after the generator's tree is flushed, so the\n// migrations discovered in this run came from the versions being replaced. A\n// second pass over the freshly installed packages is what surfaces migrations\n// that only exist in the new versions -- which, for a workspace far enough\n// behind that nothing shipped migrations yet, is all of them.\nconst reportUpgradeFollowUp = (upgrades: ReadonlyArray<DependencyUpgrade>, willInstall: boolean): void => {\n const pmc = getPackageManagerCommand(detectPackageManager());\n const rerun = \"nx g @trackunit/migrations:migrate\";\n\n if (willInstall) {\n logger.info(\n `\\n${upgrades.length} dependency version(s) changed. Installing, then re-run \"${rerun}\" to pick up migrations that ship with the new versions.`\n );\n return;\n }\n\n logger.warn(\n `\\n${upgrades.length} dependency version(s) changed, so node_modules is now stale. Run:\\n ${pmc.install}\\n ${rerun}`\n );\n};\n\n/**\n * Step 1: Upgrades every `@trackunit/*` dependency to its latest published\n * version, discovers installed @trackunit packages, and writes\n * trackunit-migrations.json with pending migrations.\n *\n * The dependency upgrade is unconditional and independent of migrations:\n * a package without codemods still gets its version bumped.\n *\n * Usage: nx g @trackunit/migrations:migrate\n * Then: nx g @trackunit/migrations:run-migrations\n *\n * @param tree - The NX virtual file system for the workspace being migrated.\n * @param options - Generator options.\n * @returns {Promise<GeneratorCallback | undefined>} An install task when dependency versions changed, otherwise undefined.\n */\nexport const migrate = async (tree: Tree, options: MigrateSchema): Promise<GeneratorCallback | undefined> => {\n const verbose = options.verbose ?? false;\n const workspaceRoot = process.cwd();\n\n // Inside manager the @trackunit libs are workspace projects whose versions\n // are owned by CI, so resolving them from the registry would fight the\n // release process. Migration discovery still covers them via libs/ and apps/.\n const upgrades = isManagerWorkspace(tree) ? [] : await upgradeTrackunitDependencies(tree, verbose);\n\n const packages = discoverTrackunitPackages();\n\n logger.info(`Discovered ${packages.length} @trackunit/* package(s) with migrations`);\n\n const allPending: Array<PendingMigration> = [];\n const migratedVersions: Record<string, string> = {};\n\n if (tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {\n try {\n const existing: TrackunitMigrationsFile = readJson(tree, TRACKUNIT_MIGRATIONS_FILE);\n Object.assign(migratedVersions, existing.migratedVersions);\n } catch {\n // start fresh\n }\n }\n\n for (const pkg of packages) {\n updateDepVersion(tree, pkg);\n\n const migrationsJson = loadMigrationsJson(pkg.migrationsJsonPath);\n\n if (!migrationsJson?.generators || Object.keys(migrationsJson.generators).length === 0) {\n if (verbose) {\n logger.info(` ${pkg.name}: no migrations defined`);\n }\n migratedVersions[pkg.name] = pkg.version;\n continue;\n }\n\n const lastMigrated = getLastMigratedVersion(tree, pkg.name);\n\n if (verbose) {\n logger.info(` ${pkg.name}: installed=${pkg.version}, lastMigrated=${lastMigrated ?? \"none\"}`);\n }\n\n const pending = collectPendingMigrations(pkg, lastMigrated, migrationsJson, workspaceRoot);\n allPending.push(...pending);\n\n if (pending.length > 0) {\n logger.info(`${pkg.name}@${pkg.version}: ${pending.length} pending migration(s)`);\n // Defer advancing migratedVersions until run-migrations has applied\n // these entries. Otherwise a second `migrate` run would read this\n // version back as `lastMigrated` and silently drop the queued migrations.\n continue;\n }\n\n migratedVersions[pkg.name] = pkg.version;\n }\n\n const output: TrackunitMigrationsFile = {\n migratedVersions,\n migrations: allPending,\n };\n\n tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(output, null, 2));\n\n if (allPending.length === 0) {\n logger.info(\"No pending migrations. trackunit-migrations.json updated.\");\n } else {\n logger.info(`\\nWrote ${allPending.length} pending migration(s) to ${TRACKUNIT_MIGRATIONS_FILE}`);\n logger.info(\"Review the file, then run: nx g @trackunit/migrations:run-migrations\");\n }\n\n if (upgrades.length === 0) return undefined;\n\n const willInstall = options.skipInstall !== true;\n reportUpgradeFollowUp(upgrades, willInstall);\n\n return willInstall ? () => installPackagesTask(tree) : undefined;\n};\n\nexport default migrate;\n"]}
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
"type": "boolean",
|
|
7
7
|
"description": "Show detailed version comparison for each discovered package",
|
|
8
8
|
"default": false
|
|
9
|
+
},
|
|
10
|
+
"skipInstall": {
|
|
11
|
+
"type": "boolean",
|
|
12
|
+
"description": "Do not install packages after updating @trackunit/* dependency versions. Migrations that ship with the new versions are only discoverable after an install, so you will need to install and re-run this generator yourself.",
|
|
13
|
+
"default": false
|
|
9
14
|
}
|
|
10
15
|
}
|
|
11
16
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upgradeTrackunitDependencies = exports.collectTrackunitDependencies = exports.isManagerWorkspace = exports.applyVersionKeepingRange = exports.pinnedVersionOf = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const pacote = tslib_1.__importStar(require("pacote"));
|
|
7
|
+
const semver_1 = require("semver");
|
|
8
|
+
const TRACKUNIT_SCOPE = "@trackunit/";
|
|
9
|
+
const MANAGER_REPO_PACKAGE_NAME = "@trackunit/manager";
|
|
10
|
+
const DEPENDENCY_FIELDS = ["dependencies", "devDependencies"];
|
|
11
|
+
/**
|
|
12
|
+
* Splits a specifier into the range operator we can carry over to the
|
|
13
|
+
* replacement and the rest. Deciding whether the rest is actually a version is
|
|
14
|
+
* left entirely to `semver.valid`, so there is one authority on that rather
|
|
15
|
+
* than a pattern here that has to agree with it.
|
|
16
|
+
*/
|
|
17
|
+
const RANGE_OPERATOR = /^(\^|~|>=|>)?\s*(.+)$/;
|
|
18
|
+
// `workspace:*`, `npm:` aliases, `file:`/`link:`/`portal:` paths, git URLs,
|
|
19
|
+
// `*`, `latest` and multi-clause ranges all fall out here: rewriting any of
|
|
20
|
+
// them to a plain version would change what the specifier means.
|
|
21
|
+
const parseSpecifier = (specifier) => {
|
|
22
|
+
const match = RANGE_OPERATOR.exec(specifier.trim());
|
|
23
|
+
if (!match)
|
|
24
|
+
return undefined;
|
|
25
|
+
const [, prefix, version] = match;
|
|
26
|
+
if (version === undefined || (0, semver_1.valid)(version) === null)
|
|
27
|
+
return undefined;
|
|
28
|
+
return { prefix: prefix ?? "", version };
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the concrete version a specifier resolves to, for comparing against
|
|
32
|
+
* a candidate upgrade.
|
|
33
|
+
*
|
|
34
|
+
* @param specifier - The specifier currently in package.json, e.g. `^1.2.3`.
|
|
35
|
+
* @returns {string | undefined} The version without its range operator, or undefined when the specifier is not a pinned version.
|
|
36
|
+
*/
|
|
37
|
+
const pinnedVersionOf = (specifier) => parseSpecifier(specifier)?.version;
|
|
38
|
+
exports.pinnedVersionOf = pinnedVersionOf;
|
|
39
|
+
/**
|
|
40
|
+
* Rewrites a dependency specifier to a new version while keeping the range
|
|
41
|
+
* operator the consumer chose, so a `^`-ranged dependency does not silently
|
|
42
|
+
* become pinned (and vice versa).
|
|
43
|
+
*
|
|
44
|
+
* @param specifier - The specifier currently in package.json, e.g. `^1.2.3`.
|
|
45
|
+
* @param version - The concrete version to move to, e.g. `1.3.0`.
|
|
46
|
+
* @returns {string} The new specifier, or the original when it is not a pinned version.
|
|
47
|
+
*/
|
|
48
|
+
const applyVersionKeepingRange = (specifier, version) => {
|
|
49
|
+
const parsed = parseSpecifier(specifier);
|
|
50
|
+
return parsed === undefined ? specifier : `${parsed.prefix}${version}`;
|
|
51
|
+
};
|
|
52
|
+
exports.applyVersionKeepingRange = applyVersionKeepingRange;
|
|
53
|
+
/**
|
|
54
|
+
* Reports whether the tree being generated into is the manager monorepo itself,
|
|
55
|
+
* where `@trackunit/*` versions are workspace-internal and CI-managed rather
|
|
56
|
+
* than resolved from the registry.
|
|
57
|
+
*
|
|
58
|
+
* @param tree - The NX virtual file system for the workspace being migrated.
|
|
59
|
+
* @returns {boolean} True when the workspace root package.json is `@trackunit/manager`.
|
|
60
|
+
*/
|
|
61
|
+
const isManagerWorkspace = (tree) => {
|
|
62
|
+
try {
|
|
63
|
+
const packageJson = (0, devkit_1.readJson)(tree, "package.json");
|
|
64
|
+
return packageJson.name === MANAGER_REPO_PACKAGE_NAME;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.isManagerWorkspace = isManagerWorkspace;
|
|
71
|
+
/**
|
|
72
|
+
* Collects every `@trackunit/*` declaration in the consumer's `dependencies`
|
|
73
|
+
* and `devDependencies`. package.json is the source of truth rather than
|
|
74
|
+
* node_modules, so this still reports the full set when the install is stale or
|
|
75
|
+
* missing entirely -- the state an outdated app is in before its first upgrade.
|
|
76
|
+
*
|
|
77
|
+
* A package declared in both fields yields one entry per field, because the two
|
|
78
|
+
* can carry different specifiers and each has to be judged on its own: a
|
|
79
|
+
* `workspace:*` devDependency must survive an upgrade of the `dependencies`
|
|
80
|
+
* entry beside it.
|
|
81
|
+
*
|
|
82
|
+
* @param tree - The NX virtual file system for the workspace being migrated.
|
|
83
|
+
* @returns {ReadonlyArray<DeclaredDependency>} One entry per declaration, in field order.
|
|
84
|
+
*/
|
|
85
|
+
const collectTrackunitDependencies = (tree) => {
|
|
86
|
+
const packageJson = (0, devkit_1.readJson)(tree, "package.json");
|
|
87
|
+
const found = [];
|
|
88
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
89
|
+
for (const [name, specifier] of Object.entries(packageJson[field] ?? {})) {
|
|
90
|
+
if (!name.startsWith(TRACKUNIT_SCOPE) || typeof specifier !== "string")
|
|
91
|
+
continue;
|
|
92
|
+
found.push({ name, field, specifier });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return found;
|
|
96
|
+
};
|
|
97
|
+
exports.collectTrackunitDependencies = collectTrackunitDependencies;
|
|
98
|
+
const registryOptions = () => process.env.TU_NPM_REGISTRY ? { registry: process.env.TU_NPM_REGISTRY } : undefined;
|
|
99
|
+
// A single unreachable or unpublished package must not abort the upgrade of the
|
|
100
|
+
// other 30, so a failed lookup degrades to "leave this one alone" and says so.
|
|
101
|
+
const resolveLatestVersion = async (name) => {
|
|
102
|
+
try {
|
|
103
|
+
const manifest = await pacote.manifest(name, registryOptions());
|
|
104
|
+
return manifest.version;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
108
|
+
devkit_1.logger.warn(` Could not resolve the latest version of ${name}: ${reason}. Left unchanged.`);
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Moves every `@trackunit/*` dependency in the consumer's package.json to its
|
|
114
|
+
* latest published version.
|
|
115
|
+
*
|
|
116
|
+
* This is deliberately independent of migration discovery. Migrations only
|
|
117
|
+
* exist inside published packages, so gating the version bump on finding a
|
|
118
|
+
* `migrations` field means an outdated workspace can never upgrade: its
|
|
119
|
+
* installed packages predate the field, nothing is discovered, and nothing is
|
|
120
|
+
* bumped. Resolving from the registry instead of from node_modules is what
|
|
121
|
+
* makes the first upgrade of a stale workspace possible at all.
|
|
122
|
+
*
|
|
123
|
+
* @param tree - The NX virtual file system for the workspace being migrated.
|
|
124
|
+
* @param verbose - Log per-package decisions, including packages left alone.
|
|
125
|
+
* @returns {Promise<ReadonlyArray<DependencyUpgrade>>} One entry per declaration whose specifier changed.
|
|
126
|
+
* @throws {Error} When not one dependency could be resolved from the registry, which means the upgrade never happened.
|
|
127
|
+
*/
|
|
128
|
+
const upgradeTrackunitDependencies = async (tree, verbose) => {
|
|
129
|
+
const declared = (0, exports.collectTrackunitDependencies)(tree);
|
|
130
|
+
const names = [...new Set(declared.map(declaration => declaration.name))];
|
|
131
|
+
if (names.length === 0) {
|
|
132
|
+
devkit_1.logger.info("No @trackunit/* dependencies declared in package.json");
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
devkit_1.logger.info(`Resolving latest versions for ${names.length} @trackunit/* dependency(ies)`);
|
|
136
|
+
// Keyed by name, not by declaration, so a package declared in both fields
|
|
137
|
+
// still costs one registry round trip.
|
|
138
|
+
const latestByName = new Map();
|
|
139
|
+
await Promise.all(names.map(async (name) => {
|
|
140
|
+
const latest = await resolveLatestVersion(name);
|
|
141
|
+
if (latest !== undefined)
|
|
142
|
+
latestByName.set(name, latest);
|
|
143
|
+
}));
|
|
144
|
+
// Resolving nothing at all is an environment failure -- no network, a
|
|
145
|
+
// proxying or private registry, an expired token -- not a workspace that
|
|
146
|
+
// happens to be current. Reporting it as success would recreate the exact
|
|
147
|
+
// symptom this generator exists to remove: a green run that changed nothing.
|
|
148
|
+
const unresolved = names.filter(name => !latestByName.has(name));
|
|
149
|
+
if (unresolved.length === names.length) {
|
|
150
|
+
throw new Error(`Could not resolve any of the ${names.length} @trackunit/* dependency(ies) from the registry, so nothing was upgraded. ` +
|
|
151
|
+
`Check your registry access -- network, npm authentication, and TU_NPM_REGISTRY if you set it -- then run this generator again. ` +
|
|
152
|
+
`The per-package reasons are in the warnings above.`);
|
|
153
|
+
}
|
|
154
|
+
if (unresolved.length > 0) {
|
|
155
|
+
devkit_1.logger.warn(`${unresolved.length} of ${names.length} @trackunit/* dependency(ies) could not be resolved and were left unchanged ` +
|
|
156
|
+
`(${unresolved.join(", ")}). This upgrade is incomplete.`);
|
|
157
|
+
}
|
|
158
|
+
const upgrades = [];
|
|
159
|
+
for (const { name, field, specifier } of declared) {
|
|
160
|
+
const latest = latestByName.get(name);
|
|
161
|
+
if (latest === undefined)
|
|
162
|
+
continue;
|
|
163
|
+
// Each declaration is judged against its own specifier. Reusing one field's
|
|
164
|
+
// result for the other would overwrite a specifier that was never read --
|
|
165
|
+
// turning a `workspace:*` devDependency into a plain version, or swapping
|
|
166
|
+
// one field's range operator for the other's.
|
|
167
|
+
const parsed = parseSpecifier(specifier);
|
|
168
|
+
if (parsed === undefined) {
|
|
169
|
+
if (verbose)
|
|
170
|
+
devkit_1.logger.info(` ${name} (${field}): left alone, "${specifier}" is not a pinned version`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!(0, semver_1.gt)(latest, parsed.version)) {
|
|
174
|
+
if (verbose)
|
|
175
|
+
devkit_1.logger.info(` ${name} (${field}): already at ${specifier}`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
upgrades.push({ name, field, from: specifier, to: `${parsed.prefix}${latest}` });
|
|
179
|
+
}
|
|
180
|
+
if (upgrades.length === 0) {
|
|
181
|
+
devkit_1.logger.info("All @trackunit/* dependencies are already at their latest version");
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
(0, devkit_1.updateJson)(tree, "package.json", json => {
|
|
185
|
+
for (const upgrade of upgrades) {
|
|
186
|
+
const dependencies = json[upgrade.field];
|
|
187
|
+
if (dependencies?.[upgrade.name] !== undefined) {
|
|
188
|
+
dependencies[upgrade.name] = upgrade.to;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return json;
|
|
192
|
+
});
|
|
193
|
+
for (const upgrade of upgrades) {
|
|
194
|
+
devkit_1.logger.info(` ${upgrade.name}: ${upgrade.from} -> ${upgrade.to}`);
|
|
195
|
+
}
|
|
196
|
+
devkit_1.logger.info(`Updated ${upgrades.length} @trackunit/* dependency(ies) in package.json`);
|
|
197
|
+
return upgrades;
|
|
198
|
+
};
|
|
199
|
+
exports.upgradeTrackunitDependencies = upgradeTrackunitDependencies;
|
|
200
|
+
//# sourceMappingURL=upgrade-dependencies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade-dependencies.js","sourceRoot":"","sources":["../../../../../../libs/migrations/src/generators/utils/upgrade-dependencies.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, logger, readJson, updateJson } from \"@nx/devkit\";\nimport * as pacote from \"pacote\";\nimport { gt, valid } from \"semver\";\n\nconst TRACKUNIT_SCOPE = \"@trackunit/\";\nconst MANAGER_REPO_PACKAGE_NAME = \"@trackunit/manager\";\nconst DEPENDENCY_FIELDS = [\"dependencies\", \"devDependencies\"] as const;\n\n/**\n * Splits a specifier into the range operator we can carry over to the\n * replacement and the rest. Deciding whether the rest is actually a version is\n * left entirely to `semver.valid`, so there is one authority on that rather\n * than a pattern here that has to agree with it.\n */\nconst RANGE_OPERATOR = /^(\\^|~|>=|>)?\\s*(.+)$/;\n\ntype ConsumerPackageJson = {\n readonly name?: string;\n readonly dependencies?: Record<string, string>;\n readonly devDependencies?: Record<string, string>;\n};\n\ntype MutablePackageJson = {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n};\n\ntype DependencyField = (typeof DEPENDENCY_FIELDS)[number];\n\nexport type DeclaredDependency = {\n readonly name: string;\n readonly field: DependencyField;\n readonly specifier: string;\n};\n\nexport type DependencyUpgrade = {\n readonly name: string;\n readonly field: DependencyField;\n readonly from: string;\n readonly to: string;\n};\n\ntype PinnedSpecifier = {\n readonly prefix: string;\n readonly version: string;\n};\n\n// `workspace:*`, `npm:` aliases, `file:`/`link:`/`portal:` paths, git URLs,\n// `*`, `latest` and multi-clause ranges all fall out here: rewriting any of\n// them to a plain version would change what the specifier means.\nconst parseSpecifier = (specifier: string): PinnedSpecifier | undefined => {\n const match = RANGE_OPERATOR.exec(specifier.trim());\n if (!match) return undefined;\n\n const [, prefix, version] = match;\n if (version === undefined || valid(version) === null) return undefined;\n\n return { prefix: prefix ?? \"\", version };\n};\n\n/**\n * Extracts the concrete version a specifier resolves to, for comparing against\n * a candidate upgrade.\n *\n * @param specifier - The specifier currently in package.json, e.g. `^1.2.3`.\n * @returns {string | undefined} The version without its range operator, or undefined when the specifier is not a pinned version.\n */\nexport const pinnedVersionOf = (specifier: string): string | undefined => parseSpecifier(specifier)?.version;\n\n/**\n * Rewrites a dependency specifier to a new version while keeping the range\n * operator the consumer chose, so a `^`-ranged dependency does not silently\n * become pinned (and vice versa).\n *\n * @param specifier - The specifier currently in package.json, e.g. `^1.2.3`.\n * @param version - The concrete version to move to, e.g. `1.3.0`.\n * @returns {string} The new specifier, or the original when it is not a pinned version.\n */\nexport const applyVersionKeepingRange = (specifier: string, version: string): string => {\n const parsed = parseSpecifier(specifier);\n return parsed === undefined ? specifier : `${parsed.prefix}${version}`;\n};\n\n/**\n * Reports whether the tree being generated into is the manager monorepo itself,\n * where `@trackunit/*` versions are workspace-internal and CI-managed rather\n * than resolved from the registry.\n *\n * @param tree - The NX virtual file system for the workspace being migrated.\n * @returns {boolean} True when the workspace root package.json is `@trackunit/manager`.\n */\nexport const isManagerWorkspace = (tree: Tree): boolean => {\n try {\n const packageJson: ConsumerPackageJson = readJson(tree, \"package.json\");\n return packageJson.name === MANAGER_REPO_PACKAGE_NAME;\n } catch {\n return false;\n }\n};\n\n/**\n * Collects every `@trackunit/*` declaration in the consumer's `dependencies`\n * and `devDependencies`. package.json is the source of truth rather than\n * node_modules, so this still reports the full set when the install is stale or\n * missing entirely -- the state an outdated app is in before its first upgrade.\n *\n * A package declared in both fields yields one entry per field, because the two\n * can carry different specifiers and each has to be judged on its own: a\n * `workspace:*` devDependency must survive an upgrade of the `dependencies`\n * entry beside it.\n *\n * @param tree - The NX virtual file system for the workspace being migrated.\n * @returns {ReadonlyArray<DeclaredDependency>} One entry per declaration, in field order.\n */\nexport const collectTrackunitDependencies = (tree: Tree): ReadonlyArray<DeclaredDependency> => {\n const packageJson: ConsumerPackageJson = readJson(tree, \"package.json\");\n const found: Array<DeclaredDependency> = [];\n\n for (const field of DEPENDENCY_FIELDS) {\n for (const [name, specifier] of Object.entries(packageJson[field] ?? {})) {\n if (!name.startsWith(TRACKUNIT_SCOPE) || typeof specifier !== \"string\") continue;\n found.push({ name, field, specifier });\n }\n }\n\n return found;\n};\n\nconst registryOptions = (): { readonly registry: string } | undefined =>\n process.env.TU_NPM_REGISTRY ? { registry: process.env.TU_NPM_REGISTRY } : undefined;\n\n// A single unreachable or unpublished package must not abort the upgrade of the\n// other 30, so a failed lookup degrades to \"leave this one alone\" and says so.\nconst resolveLatestVersion = async (name: string): Promise<string | undefined> => {\n try {\n const manifest = await pacote.manifest(name, registryOptions());\n return manifest.version;\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logger.warn(` Could not resolve the latest version of ${name}: ${reason}. Left unchanged.`);\n return undefined;\n }\n};\n\n/**\n * Moves every `@trackunit/*` dependency in the consumer's package.json to its\n * latest published version.\n *\n * This is deliberately independent of migration discovery. Migrations only\n * exist inside published packages, so gating the version bump on finding a\n * `migrations` field means an outdated workspace can never upgrade: its\n * installed packages predate the field, nothing is discovered, and nothing is\n * bumped. Resolving from the registry instead of from node_modules is what\n * makes the first upgrade of a stale workspace possible at all.\n *\n * @param tree - The NX virtual file system for the workspace being migrated.\n * @param verbose - Log per-package decisions, including packages left alone.\n * @returns {Promise<ReadonlyArray<DependencyUpgrade>>} One entry per declaration whose specifier changed.\n * @throws {Error} When not one dependency could be resolved from the registry, which means the upgrade never happened.\n */\nexport const upgradeTrackunitDependencies = async (\n tree: Tree,\n verbose: boolean\n): Promise<ReadonlyArray<DependencyUpgrade>> => {\n const declared = collectTrackunitDependencies(tree);\n const names = [...new Set(declared.map(declaration => declaration.name))];\n\n if (names.length === 0) {\n logger.info(\"No @trackunit/* dependencies declared in package.json\");\n return [];\n }\n\n logger.info(`Resolving latest versions for ${names.length} @trackunit/* dependency(ies)`);\n\n // Keyed by name, not by declaration, so a package declared in both fields\n // still costs one registry round trip.\n const latestByName = new Map<string, string>();\n await Promise.all(\n names.map(async name => {\n const latest = await resolveLatestVersion(name);\n if (latest !== undefined) latestByName.set(name, latest);\n })\n );\n\n // Resolving nothing at all is an environment failure -- no network, a\n // proxying or private registry, an expired token -- not a workspace that\n // happens to be current. Reporting it as success would recreate the exact\n // symptom this generator exists to remove: a green run that changed nothing.\n const unresolved = names.filter(name => !latestByName.has(name));\n if (unresolved.length === names.length) {\n throw new Error(\n `Could not resolve any of the ${names.length} @trackunit/* dependency(ies) from the registry, so nothing was upgraded. ` +\n `Check your registry access -- network, npm authentication, and TU_NPM_REGISTRY if you set it -- then run this generator again. ` +\n `The per-package reasons are in the warnings above.`\n );\n }\n\n if (unresolved.length > 0) {\n logger.warn(\n `${unresolved.length} of ${names.length} @trackunit/* dependency(ies) could not be resolved and were left unchanged ` +\n `(${unresolved.join(\", \")}). This upgrade is incomplete.`\n );\n }\n\n const upgrades: Array<DependencyUpgrade> = [];\n\n for (const { name, field, specifier } of declared) {\n const latest = latestByName.get(name);\n if (latest === undefined) continue;\n\n // Each declaration is judged against its own specifier. Reusing one field's\n // result for the other would overwrite a specifier that was never read --\n // turning a `workspace:*` devDependency into a plain version, or swapping\n // one field's range operator for the other's.\n const parsed = parseSpecifier(specifier);\n if (parsed === undefined) {\n if (verbose) logger.info(` ${name} (${field}): left alone, \"${specifier}\" is not a pinned version`);\n continue;\n }\n\n if (!gt(latest, parsed.version)) {\n if (verbose) logger.info(` ${name} (${field}): already at ${specifier}`);\n continue;\n }\n\n upgrades.push({ name, field, from: specifier, to: `${parsed.prefix}${latest}` });\n }\n\n if (upgrades.length === 0) {\n logger.info(\"All @trackunit/* dependencies are already at their latest version\");\n return [];\n }\n\n updateJson<MutablePackageJson, MutablePackageJson>(tree, \"package.json\", json => {\n for (const upgrade of upgrades) {\n const dependencies = json[upgrade.field];\n if (dependencies?.[upgrade.name] !== undefined) {\n dependencies[upgrade.name] = upgrade.to;\n }\n }\n return json;\n });\n\n for (const upgrade of upgrades) {\n logger.info(` ${upgrade.name}: ${upgrade.from} -> ${upgrade.to}`);\n }\n logger.info(`Updated ${upgrades.length} @trackunit/* dependency(ies) in package.json`);\n\n return upgrades;\n};\n"]}
|