@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 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 discovers installed `@trackunit/*` packages, updates their versions, and runs pending migrations.
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
- Each published `@trackunit/*` package can declare a `"migrations"` field in its `package.json` pointing to a `migrations.json` manifest. This library recursively scans `node_modules` (including nested/transitive dependencies) to find all such packages, then follows a two-step flow modeled after `nx migrate`:
7
+ `migrate` does two independent things, in this order:
8
8
 
9
- 1. **`migrate`** -- discovers packages, updates `package.json` versions, writes pending migrations to `trackunit-migrations.json`
10
- 2. **`run-migrations`** -- executes the pending migrations from that file, then clears the list
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
- scan --> compareDeps{"Installed version >\npackage.json version?"}
29
- compareDeps -->|Yes| bumpDep["Update version in\nconsumer package.json"]
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 updates `@trackunit/*` dependency versions in your `package.json` and writes a `trackunit-migrations.json` file listing any pending migrations. Review the file before proceeding.
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.59",
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 || (0, discover_packages_1.compareVersions)(pkg.version, currentDep.replace(/^[~^]/, "")) <= 0) {
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: Discovers installed @trackunit packages, compares versions
74
- * against the consumer's package.json, updates dependency versions,
75
- * and writes trackunit-migrations.json with pending migrations.
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 packages = (0, discover_packages_1.discoverTrackunitPackages)();
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 (options.verbose) {
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 (options.verbose) {
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"]}