@trackunit/migrations 0.2.61-alpha-3e7014314a8.0 → 0.2.61

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,48 @@
1
+ ## 0.2.61 (2026-08-28)
2
+
3
+ This was a version bump only for migrations to align it with other projects, there were no code changes.
4
+
5
+ ## 0.2.60 (2026-08-26)
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - **migrations:** fail when nothing can be resolved from the registry [GLU-2058] ([a4d04d40b67](https://github.com/Trackunit/manager/commit/a4d04d40b67))
10
+ - **migrations:** judge each dependency field on its own specifier [GLU-2057] ([80be5ef0ef5](https://github.com/Trackunit/manager/commit/80be5ef0ef5))
11
+ - **migrations:** upgrade every @trackunit dependency, not just ones with codemods [GLU-2054] ([d9b74407648](https://github.com/Trackunit/manager/commit/d9b74407648))
12
+
13
+ ### ❤️ Thank You
14
+
15
+ - Cursor @cursoragent
16
+ - Mikkel Thorbjørn Andersen
17
+
18
+ ## 0.2.59 (2026-08-25)
19
+
20
+ ### 🩹 Fixes
21
+
22
+ - **migrations:** load migrations from source inside the manager monorepo ([3c22bb2527f](https://github.com/Trackunit/manager/commit/3c22bb2527f))
23
+
24
+ ### ❤️ Thank You
25
+
26
+ - Cursor @cursoragent
27
+ - Mikkel Thorbjørn Andersen
28
+
29
+ ## 0.2.58 (2026-08-25)
30
+
31
+ ### 🩹 Fixes
32
+
33
+ - **migrations:** route every directory listing through the tolerant reader [GLU-2029] ([eff5dba9767](https://github.com/Trackunit/manager/commit/eff5dba9767))
34
+ - **migrations:** warn when package discovery skips a directory [GLU-2028] ([09cc451476c](https://github.com/Trackunit/manager/commit/09cc451476c))
35
+ - **migrations:** GLU-2007 make discovery and react codemods work in consumer repos ([3f25bb543f9](https://github.com/Trackunit/manager/commit/3f25bb543f9))
36
+
37
+ ### ❤️ Thank You
38
+
39
+ - Cursor @cursoragent
40
+ - Mikkel Thorbjørn Andersen
41
+
42
+ ## 0.2.57 (2026-08-25)
43
+
44
+ This was a version bump only for migrations to align it with other projects, there were no code changes.
45
+
1
46
  ## 0.2.56 (2026-08-20)
2
47
 
3
48
  This was a version bump only for migrations to align it with other projects, there were no code changes.
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.61-alpha-3e7014314a8.0",
3
+ "version": "0.2.61",
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 @@
1
+ module.exports = function extensionlessMigration() {};
@@ -3,8 +3,64 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runMigrations = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const devkit_1 = require("@nx/devkit");
6
+ const fs_1 = require("fs");
7
+ const module_1 = require("module");
6
8
  const path_1 = tslib_1.__importDefault(require("path"));
9
+ const url_1 = require("url");
7
10
  const discover_packages_1 = require("../utils/discover-packages");
11
+ const requireMigration = (0, module_1.createRequire)(__filename);
12
+ const MODULE_EXTENSIONS = [".js", ".cjs", ".mjs", ".ts", ".tsx"];
13
+ const isFile = (candidate) => {
14
+ try {
15
+ return (0, fs_1.statSync)(candidate).isFile();
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ };
21
+ /**
22
+ * `trackunit-migrations.json` stores implementation paths without a file
23
+ * extension. Node's own resolution only probes `.js`/`.json`/`.node`, and
24
+ * only for `require` (never for `import()`), so the stored path has to be
25
+ * expanded to a concrete file before loading.
26
+ */
27
+ const resolveModuleFile = (basePath) => {
28
+ if (isFile(basePath))
29
+ return basePath;
30
+ for (const ext of MODULE_EXTENSIONS) {
31
+ const candidate = `${basePath}${ext}`;
32
+ if (isFile(candidate))
33
+ return candidate;
34
+ }
35
+ for (const ext of MODULE_EXTENSIONS) {
36
+ const candidate = path_1.default.join(basePath, `index${ext}`);
37
+ if (isFile(candidate))
38
+ return candidate;
39
+ }
40
+ return basePath;
41
+ };
42
+ let typeScriptLoaderRegistered = false;
43
+ /**
44
+ * Running inside the manager monorepo discovers migrations as TypeScript
45
+ * source, whose sibling imports are extensionless and whose `@trackunit/*`
46
+ * imports resolve through workspace tsconfig path aliases. Nx's generator
47
+ * runtime transpiles the explicitly-named generator files but does not add
48
+ * a require hook that resolves either, so a real migration and everything
49
+ * it imports must be loaded through a TypeScript register plus tsconfig
50
+ * path resolution. Consumers run compiled `.js` and never reach this branch,
51
+ * so the loaders stay dev-only dependencies of the manager monorepo.
52
+ */
53
+ const ensureTypeScriptLoader = () => {
54
+ if (typeScriptLoaderRegistered)
55
+ return;
56
+ const tsConfigPaths = requireMigration("tsconfig-paths");
57
+ const configResult = tsConfigPaths.loadConfig(path_1.default.join(process.cwd(), "tsconfig.base.json"));
58
+ if (configResult.resultType === "success") {
59
+ tsConfigPaths.register({ baseUrl: configResult.absoluteBaseUrl, paths: configResult.paths });
60
+ }
61
+ requireMigration("@swc-node/register");
62
+ typeScriptLoaderRegistered = true;
63
+ };
8
64
  /**
9
65
  * Resolves a stored implementation path. Relative paths in
10
66
  * `trackunit-migrations.json` are interpreted as relative to the
@@ -13,11 +69,37 @@ const discover_packages_1 = require("../utils/discover-packages");
13
69
  * preserving back-compat with absolute paths.
14
70
  */
15
71
  const resolveImplementationPath = (storedPath) => path_1.default.isAbsolute(storedPath) ? storedPath : path_1.default.resolve(process.cwd(), storedPath);
72
+ const isEsmRequireError = (error) => error instanceof Error && "code" in error && error.code === "ERR_REQUIRE_ESM";
73
+ /**
74
+ * Loads a migration module from an implementation path that is stored
75
+ * without a file extension (see `portableImplementationPath` in the
76
+ * `migrate` generator). The path is expanded to a concrete file, then
77
+ * loaded with `require`, which resolves `.js` for the built/published
78
+ * output consumers run and `.ts` for source when running inside the
79
+ * manager monorepo (a TypeScript loader is registered on demand for that
80
+ * case). Native `import()` performs no extension resolution and cannot load
81
+ * these paths at all; it is kept only as a fallback for genuine ESM output
82
+ * (rare — the migrations build emits CommonJS).
83
+ */
84
+ const loadMigrationModule = async (resolvedPath) => {
85
+ const file = resolveModuleFile(resolvedPath);
86
+ if (file.endsWith(".ts") || file.endsWith(".tsx")) {
87
+ ensureTypeScriptLoader();
88
+ }
89
+ try {
90
+ return requireMigration(file);
91
+ }
92
+ catch (error) {
93
+ if (!isEsmRequireError(error))
94
+ throw error;
95
+ return Promise.resolve(`${(0, url_1.pathToFileURL)(file).href}`).then(s => tslib_1.__importStar(require(s)));
96
+ }
97
+ };
16
98
  const TRACKUNIT_MIGRATIONS_FILE = "trackunit-migrations.json";
17
99
  const runMigration = async (tree, migration, verbose) => {
18
100
  try {
19
101
  const resolvedPath = resolveImplementationPath(migration.implementation);
20
- const migrationModule = await Promise.resolve(`${resolvedPath}`).then(s => tslib_1.__importStar(require(s)));
102
+ const migrationModule = await loadMigrationModule(resolvedPath);
21
103
  const migrationFn = migrationModule.default ?? migrationModule;
22
104
  if (typeof migrationFn !== "function") {
23
105
  devkit_1.logger.warn(` "${migration.name}" does not export a function, skipping`);
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../libs/migrations/src/generators/run-migrations/generator.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, formatFiles, logger, readJson } from \"@nx/devkit\";\nimport path from \"path\";\nimport { compareVersions } from \"../utils/discover-packages\";\n\ntype RunMigrationsSchema = {\n readonly verbose?: boolean;\n};\n\n/**\n * Resolves a stored implementation path. Relative paths in\n * `trackunit-migrations.json` are interpreted as relative to the\n * workspace root (`process.cwd()`); absolute paths are used as-is.\n * This keeps the on-disk file portable between checkouts while\n * preserving back-compat with absolute paths.\n */\nconst resolveImplementationPath = (storedPath: string): string =>\n path.isAbsolute(storedPath) ? storedPath : path.resolve(process.cwd(), storedPath);\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 runMigration = async (tree: Tree, migration: PendingMigration, verbose: boolean): Promise<boolean> => {\n try {\n const resolvedPath = resolveImplementationPath(migration.implementation);\n const migrationModule: Record<string, unknown> = await import(resolvedPath);\n const migrationFn = migrationModule.default ?? migrationModule;\n\n if (typeof migrationFn !== \"function\") {\n logger.warn(` \"${migration.name}\" does not export a function, skipping`);\n return false;\n }\n\n if (verbose) {\n logger.info(` Running: ${migration.name} (v${migration.version}) from ${migration.package}`);\n }\n\n migrationFn(tree);\n return true;\n } catch (error) {\n logger.error(` Failed \"${migration.name}\": ${error instanceof Error ? error.message : String(error)}`);\n return false;\n }\n};\n\n/**\n * Step 2: Reads trackunit-migrations.json and executes each pending\n * migration. Successful migrations are removed from the pending list\n * and bump `migratedVersions[package]` to their version. Failures\n * stay in the list so they remain retryable on the next run without\n * having to go through `migrate` again.\n *\n * Usage: nx g @trackunit/migrations:run-migrations\n */\nexport const runMigrations = async (tree: Tree, options: RunMigrationsSchema): Promise<void> => {\n if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {\n logger.error(`${TRACKUNIT_MIGRATIONS_FILE} not found. Run \"nx g @trackunit/migrations:migrate\" first.`);\n return;\n }\n\n const file: TrackunitMigrationsFile = readJson(tree, TRACKUNIT_MIGRATIONS_FILE);\n\n if (file.migrations.length === 0) {\n logger.info(\"No pending migrations to run.\");\n return;\n }\n\n logger.info(`Running ${file.migrations.length} migration(s)...\\n`);\n\n let succeeded = 0;\n const remaining: Array<PendingMigration> = [];\n const migratedVersions: Record<string, string> = { ...file.migratedVersions };\n\n for (const migration of file.migrations) {\n logger.info(`${migration.package}: ${migration.name} - ${migration.description}`);\n\n const ok = await runMigration(tree, migration, options.verbose ?? false);\n if (ok) {\n succeeded++;\n const current = migratedVersions[migration.package];\n if (current === undefined || compareVersions(migration.version, current) > 0) {\n migratedVersions[migration.package] = migration.version;\n }\n } else {\n remaining.push(migration);\n }\n }\n\n const updated: TrackunitMigrationsFile = {\n migratedVersions,\n migrations: remaining,\n };\n tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(updated, null, 2));\n\n await formatFiles(tree);\n\n logger.info(`\\n${succeeded} migration(s) applied successfully.`);\n if (remaining.length > 0) {\n logger.warn(\n `${remaining.length} migration(s) failed and remain in ${TRACKUNIT_MIGRATIONS_FILE}. Fix the cause and re-run \"nx g @trackunit/migrations:run-migrations\".`\n );\n } else {\n logger.info(`\\nYou can delete ${TRACKUNIT_MIGRATIONS_FILE} or keep it for version tracking.`);\n }\n};\n\nexport default runMigrations;\n"]}
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../libs/migrations/src/generators/run-migrations/generator.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, formatFiles, logger, readJson } from \"@nx/devkit\";\nimport { statSync } from \"fs\";\nimport { createRequire } from \"module\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { compareVersions } from \"../utils/discover-packages\";\n\ntype RunMigrationsSchema = {\n readonly verbose?: boolean;\n};\n\nconst requireMigration = createRequire(__filename);\n\nconst MODULE_EXTENSIONS: ReadonlyArray<string> = [\".js\", \".cjs\", \".mjs\", \".ts\", \".tsx\"];\n\nconst isFile = (candidate: string): boolean => {\n try {\n return statSync(candidate).isFile();\n } catch {\n return false;\n }\n};\n\n/**\n * `trackunit-migrations.json` stores implementation paths without a file\n * extension. Node's own resolution only probes `.js`/`.json`/`.node`, and\n * only for `require` (never for `import()`), so the stored path has to be\n * expanded to a concrete file before loading.\n */\nconst resolveModuleFile = (basePath: string): string => {\n if (isFile(basePath)) return basePath;\n for (const ext of MODULE_EXTENSIONS) {\n const candidate = `${basePath}${ext}`;\n if (isFile(candidate)) return candidate;\n }\n for (const ext of MODULE_EXTENSIONS) {\n const candidate = path.join(basePath, `index${ext}`);\n if (isFile(candidate)) return candidate;\n }\n return basePath;\n};\n\ntype TsConfigPaths = Record<string, ReadonlyArray<string>>;\n\ntype TsConfigLoadResult =\n | { readonly resultType: \"success\"; readonly absoluteBaseUrl: string; readonly paths: TsConfigPaths }\n | { readonly resultType: \"failed\" };\n\ntype TsConfigPathsModule = {\n readonly loadConfig: (cwd: string) => TsConfigLoadResult;\n readonly register: (options: { readonly baseUrl: string; readonly paths: TsConfigPaths }) => void;\n};\n\nlet typeScriptLoaderRegistered = false;\n\n/**\n * Running inside the manager monorepo discovers migrations as TypeScript\n * source, whose sibling imports are extensionless and whose `@trackunit/*`\n * imports resolve through workspace tsconfig path aliases. Nx's generator\n * runtime transpiles the explicitly-named generator files but does not add\n * a require hook that resolves either, so a real migration and everything\n * it imports must be loaded through a TypeScript register plus tsconfig\n * path resolution. Consumers run compiled `.js` and never reach this branch,\n * so the loaders stay dev-only dependencies of the manager monorepo.\n */\nconst ensureTypeScriptLoader = (): void => {\n if (typeScriptLoaderRegistered) return;\n\n const tsConfigPaths: TsConfigPathsModule = requireMigration(\"tsconfig-paths\");\n const configResult = tsConfigPaths.loadConfig(path.join(process.cwd(), \"tsconfig.base.json\"));\n if (configResult.resultType === \"success\") {\n tsConfigPaths.register({ baseUrl: configResult.absoluteBaseUrl, paths: configResult.paths });\n }\n\n requireMigration(\"@swc-node/register\");\n typeScriptLoaderRegistered = true;\n};\n\n/**\n * Resolves a stored implementation path. Relative paths in\n * `trackunit-migrations.json` are interpreted as relative to the\n * workspace root (`process.cwd()`); absolute paths are used as-is.\n * This keeps the on-disk file portable between checkouts while\n * preserving back-compat with absolute paths.\n */\nconst resolveImplementationPath = (storedPath: string): string =>\n path.isAbsolute(storedPath) ? storedPath : path.resolve(process.cwd(), storedPath);\n\nconst isEsmRequireError = (error: unknown): boolean =>\n error instanceof Error && \"code\" in error && error.code === \"ERR_REQUIRE_ESM\";\n\n/**\n * Loads a migration module from an implementation path that is stored\n * without a file extension (see `portableImplementationPath` in the\n * `migrate` generator). The path is expanded to a concrete file, then\n * loaded with `require`, which resolves `.js` for the built/published\n * output consumers run and `.ts` for source when running inside the\n * manager monorepo (a TypeScript loader is registered on demand for that\n * case). Native `import()` performs no extension resolution and cannot load\n * these paths at all; it is kept only as a fallback for genuine ESM output\n * (rare — the migrations build emits CommonJS).\n */\nconst loadMigrationModule = async (resolvedPath: string): Promise<Record<string, unknown>> => {\n const file = resolveModuleFile(resolvedPath);\n\n if (file.endsWith(\".ts\") || file.endsWith(\".tsx\")) {\n ensureTypeScriptLoader();\n }\n\n try {\n return requireMigration(file);\n } catch (error) {\n if (!isEsmRequireError(error)) throw error;\n return import(pathToFileURL(file).href);\n }\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 runMigration = async (tree: Tree, migration: PendingMigration, verbose: boolean): Promise<boolean> => {\n try {\n const resolvedPath = resolveImplementationPath(migration.implementation);\n const migrationModule = await loadMigrationModule(resolvedPath);\n const migrationFn = migrationModule.default ?? migrationModule;\n\n if (typeof migrationFn !== \"function\") {\n logger.warn(` \"${migration.name}\" does not export a function, skipping`);\n return false;\n }\n\n if (verbose) {\n logger.info(` Running: ${migration.name} (v${migration.version}) from ${migration.package}`);\n }\n\n migrationFn(tree);\n return true;\n } catch (error) {\n logger.error(` Failed \"${migration.name}\": ${error instanceof Error ? error.message : String(error)}`);\n return false;\n }\n};\n\n/**\n * Step 2: Reads trackunit-migrations.json and executes each pending\n * migration. Successful migrations are removed from the pending list\n * and bump `migratedVersions[package]` to their version. Failures\n * stay in the list so they remain retryable on the next run without\n * having to go through `migrate` again.\n *\n * Usage: nx g @trackunit/migrations:run-migrations\n */\nexport const runMigrations = async (tree: Tree, options: RunMigrationsSchema): Promise<void> => {\n if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {\n logger.error(`${TRACKUNIT_MIGRATIONS_FILE} not found. Run \"nx g @trackunit/migrations:migrate\" first.`);\n return;\n }\n\n const file: TrackunitMigrationsFile = readJson(tree, TRACKUNIT_MIGRATIONS_FILE);\n\n if (file.migrations.length === 0) {\n logger.info(\"No pending migrations to run.\");\n return;\n }\n\n logger.info(`Running ${file.migrations.length} migration(s)...\\n`);\n\n let succeeded = 0;\n const remaining: Array<PendingMigration> = [];\n const migratedVersions: Record<string, string> = { ...file.migratedVersions };\n\n for (const migration of file.migrations) {\n logger.info(`${migration.package}: ${migration.name} - ${migration.description}`);\n\n const ok = await runMigration(tree, migration, options.verbose ?? false);\n if (ok) {\n succeeded++;\n const current = migratedVersions[migration.package];\n if (current === undefined || compareVersions(migration.version, current) > 0) {\n migratedVersions[migration.package] = migration.version;\n }\n } else {\n remaining.push(migration);\n }\n }\n\n const updated: TrackunitMigrationsFile = {\n migratedVersions,\n migrations: remaining,\n };\n tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(updated, null, 2));\n\n await formatFiles(tree);\n\n logger.info(`\\n${succeeded} migration(s) applied successfully.`);\n if (remaining.length > 0) {\n logger.warn(\n `${remaining.length} migration(s) failed and remain in ${TRACKUNIT_MIGRATIONS_FILE}. Fix the cause and re-run \"nx g @trackunit/migrations:run-migrations\".`\n );\n } else {\n logger.info(`\\nYou can delete ${TRACKUNIT_MIGRATIONS_FILE} or keep it for version tracking.`);\n }\n};\n\nexport default runMigrations;\n"]}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.discoverTrackunitPackages = exports.discoverWorkspaceTrackunitPackages = exports.compareVersions = exports.loadMigrationsJson = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const devkit_1 = require("@nx/devkit");
5
6
  const fs_1 = require("fs");
6
7
  const path_1 = tslib_1.__importDefault(require("path"));
7
8
  const readPackageJson = (pkgJsonPath) => {
@@ -43,10 +44,31 @@ const semver_1 = require("semver");
43
44
  */
44
45
  const compareVersions = (a, b) => (0, semver_1.compare)(a, b);
45
46
  exports.compareVersions = compareVersions;
47
+ // Swallowing a failed directory read shrinks the discovered package set, which
48
+ // `migrate` then reports as "no pending migrations" -- indistinguishable from
49
+ // the green-run-no-codemods symptom this walker exists to fix. Warning keeps
50
+ // the walk crash-proof while making a truncated result visible.
51
+ const warnSkipped = (dir, error) => {
52
+ const reason = error instanceof Error ? error.message : String(error);
53
+ devkit_1.logger.warn(`Skipped "${dir}" while discovering @trackunit packages: ${reason}. Discovered migrations may be incomplete.`);
54
+ };
55
+ // `existsSync` only says the entry resolved, not that it can be listed: an
56
+ // unreadable store directory or a path that turns out to be a file still
57
+ // throws. Every listing in this module goes through here so one bad directory
58
+ // cannot abort discovery.
59
+ const readDirEntries = (dir) => {
60
+ try {
61
+ return (0, fs_1.readdirSync)(dir, { withFileTypes: true });
62
+ }
63
+ catch (error) {
64
+ warnSkipped(dir, error);
65
+ return [];
66
+ }
67
+ };
46
68
  const scanScopeDir = (scopeDir) => {
47
69
  if (!(0, fs_1.existsSync)(scopeDir))
48
70
  return [];
49
- const entries = (0, fs_1.readdirSync)(scopeDir, { withFileTypes: true });
71
+ const entries = readDirEntries(scopeDir);
50
72
  const found = [];
51
73
  for (const entry of entries) {
52
74
  if (!entry.isDirectory() && !entry.isSymbolicLink())
@@ -69,6 +91,11 @@ const scanScopeDir = (scopeDir) => {
69
91
  };
70
92
  const MANAGER_REPO_PACKAGE_NAME = "@trackunit/manager";
71
93
  const WORKSPACE_SCAN_ROOTS = ["libs", "apps"];
94
+ // Levels to climb from a resolved package directory to reach the
95
+ // `node_modules` holding it: `<store>/node_modules/name` versus
96
+ // `<store>/node_modules/@scope/name`.
97
+ const UNSCOPED_DEPTH = 1;
98
+ const SCOPED_DEPTH = 2;
72
99
  const isManagerMonorepo = (cwd) => {
73
100
  const rootPkgJson = readPackageJson(path_1.default.join(cwd, "package.json"));
74
101
  return rootPkgJson?.name === MANAGER_REPO_PACKAGE_NAME;
@@ -81,14 +108,7 @@ const walkForPackageJsons = (rootDir, onPackageJson) => {
81
108
  const dir = stack.pop();
82
109
  if (dir === undefined)
83
110
  continue;
84
- let entries;
85
- try {
86
- entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
87
- }
88
- catch {
89
- continue;
90
- }
91
- for (const entry of entries) {
111
+ for (const entry of readDirEntries(dir)) {
92
112
  if (entry.name === "node_modules" || entry.name.startsWith("."))
93
113
  continue;
94
114
  const entryPath = path_1.default.join(dir, entry.name);
@@ -135,49 +155,90 @@ exports.discoverWorkspaceTrackunitPackages = discoverWorkspaceTrackunitPackages;
135
155
  * and nested node_modules inside every package. When the same package
136
156
  * is found at multiple levels, the highest semver version is kept.
137
157
  *
138
- * Works with all package managers (npm, yarn, pnpm, bun).
158
+ * Works with hoisted layouts (npm, yarn) and with the isolated layouts
159
+ * where dependencies are symlinks into a content store (Yarn
160
+ * `nodeLinker: pnpm`, pnpm, bun), including pnpm with hoisting disabled,
161
+ * where a package's dependencies are siblings of it inside the store
162
+ * rather than nested beneath it.
139
163
  *
140
164
  * When invoked inside the manager monorepo (root package.json name
141
165
  * is "@trackunit/manager"), additionally scans libs/ and apps/ for
142
166
  * workspace packages that ship migrations, since those are not
143
167
  * linked under node_modules/@trackunit/.
168
+ *
169
+ * @param cwd - Workspace root to scan; defaults to the current directory
170
+ * @returns {ReadonlyArray<DiscoveredPackage>} One entry per package name, at its highest discovered version
144
171
  */
145
- const discoverTrackunitPackages = () => {
146
- const cwd = process.cwd();
172
+ const discoverTrackunitPackages = (cwd = process.cwd()) => {
147
173
  const rootNodeModules = path_1.default.resolve(cwd, "node_modules");
148
174
  const allFound = [];
149
175
  const visited = new Set();
150
176
  const walkNodeModules = (nodeModulesDir) => {
151
177
  if (!(0, fs_1.existsSync)(nodeModulesDir))
152
178
  return;
153
- const canonical = path_1.default.resolve(nodeModulesDir);
179
+ // Isolated layouts (Yarn `nodeLinker: pnpm`, pnpm, bun) symlink every
180
+ // dependency into a content store, so a mutual dependency produces
181
+ // endless distinct logical paths pointing at the same real directories.
182
+ // `path.resolve` normalises but does not follow symlinks, so it cannot
183
+ // recognise the repeat and the walk grows the path until the OS rejects
184
+ // it. Canonicalising through the real path is what makes this terminate.
185
+ let canonical;
186
+ try {
187
+ canonical = (0, fs_1.realpathSync)(nodeModulesDir);
188
+ }
189
+ catch (error) {
190
+ warnSkipped(nodeModulesDir, error);
191
+ return;
192
+ }
154
193
  if (visited.has(canonical))
155
194
  return;
156
195
  visited.add(canonical);
157
196
  const trackunitScope = path_1.default.join(nodeModulesDir, "@trackunit");
158
197
  allFound.push(...scanScopeDir(trackunitScope));
159
- const topEntries = (0, fs_1.readdirSync)(nodeModulesDir, {
160
- withFileTypes: true,
161
- });
162
- for (const entry of topEntries) {
198
+ for (const entry of readDirEntries(nodeModulesDir)) {
163
199
  if (!entry.isDirectory() && !entry.isSymbolicLink())
164
200
  continue;
165
201
  const entryPath = path_1.default.join(nodeModulesDir, entry.name);
166
202
  if (entry.name.startsWith("@")) {
167
- const scopeEntries = (0, fs_1.readdirSync)(entryPath, {
168
- withFileTypes: true,
169
- });
170
- for (const scopeEntry of scopeEntries) {
203
+ for (const scopeEntry of readDirEntries(entryPath)) {
171
204
  if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink())
172
205
  continue;
173
- walkNodeModules(path_1.default.join(entryPath, scopeEntry.name, "node_modules"));
206
+ const scopedPkgDir = path_1.default.join(entryPath, scopeEntry.name);
207
+ walkNodeModules(path_1.default.join(scopedPkgDir, "node_modules"));
208
+ walkStoreSiblingsOf(scopedPkgDir, SCOPED_DEPTH);
174
209
  }
175
210
  }
176
211
  else {
177
212
  walkNodeModules(path_1.default.join(entryPath, "node_modules"));
213
+ walkStoreSiblingsOf(entryPath, UNSCOPED_DEPTH);
178
214
  }
179
215
  }
180
216
  };
217
+ /**
218
+ * pnpm with hoisting disabled places a package's dependencies *alongside*
219
+ * it inside the store rather than nested beneath it, so probing only
220
+ * `<entry>/node_modules` never reaches a @trackunit package that is purely
221
+ * transitive. Resolving the entry and walking the store `node_modules` that
222
+ * contains it reaches those siblings. In hoisted layouts this resolves back
223
+ * to a directory the walk has already scanned, so `visited` makes it free.
224
+ */
225
+ function walkStoreSiblingsOf(pkgDir, depth) {
226
+ if (!(0, fs_1.existsSync)(pkgDir))
227
+ return;
228
+ let real;
229
+ try {
230
+ real = (0, fs_1.realpathSync)(pkgDir);
231
+ }
232
+ catch (error) {
233
+ warnSkipped(pkgDir, error);
234
+ return;
235
+ }
236
+ let candidate = real;
237
+ for (let level = 0; level < depth; level += 1)
238
+ candidate = path_1.default.dirname(candidate);
239
+ if (path_1.default.basename(candidate) === "node_modules")
240
+ walkNodeModules(candidate);
241
+ }
181
242
  walkNodeModules(rootNodeModules);
182
243
  if (isManagerMonorepo(cwd)) {
183
244
  allFound.push(...(0, exports.discoverWorkspaceTrackunitPackages)(cwd));
@@ -1 +1 @@
1
- {"version":3,"file":"discover-packages.js","sourceRoot":"","sources":["../../../../../../libs/migrations/src/generators/utils/discover-packages.ts"],"names":[],"mappings":";;;;AAAA,2BAA2D;AAC3D,wDAAwB;AAcxB,MAAM,eAAe,GAAG,CAAC,WAAmB,EAAyC,EAAE;IACrF,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,iBAAY,EAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,MAAc,EAAE,OAAkC,EAAsB,EAAE;IACvG,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACtC,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE/C,MAAM,cAAc,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAC3F,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAEtD,OAAO,IAAA,eAAU,EAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC,CAAC;AAEF,iGAAiG;AACjG,yDAAyD;AAClD,MAAM,kBAAkB,GAAG,CAChC,cAAsB,EAQV,EAAE;IACd,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,iBAAY,EAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAhBW,QAAA,kBAAkB,sBAgB7B;AAEF,mCAAiC;AACjC;;;;;;GAMG;AACI,MAAM,eAAe,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,IAAA,gBAAO,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAAlE,QAAA,eAAe,mBAAmD;AAE/E,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAoC,EAAE;IAC1E,IAAI,CAAC,IAAA,eAAU,EAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,OAAO,GAAG,IAAA,gBAAW,EAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,KAAK,GAA6B,EAAE,CAAC;IAE3C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE;YAAE,SAAS;QAE9D,MAAM,MAAM,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAG,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;QAE7C,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClE,IAAI,kBAAkB,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,cAAc,KAAK,CAAC,IAAI,EAAE;gBAChD,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,kBAAkB;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,yBAAyB,GAAG,oBAAoB,CAAC;AACvD,MAAM,oBAAoB,GAA0B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErE,MAAM,iBAAiB,GAAG,CAAC,GAAW,EAAW,EAAE;IACjD,MAAM,WAAW,GAAG,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;IACpE,OAAO,WAAW,EAAE,IAAI,KAAK,yBAAyB,CAAC;AACzD,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAE,aAA4C,EAAQ,EAAE;IAClG,IAAI,CAAC,IAAA,eAAU,EAAC,OAAO,CAAC;QAAE,OAAO;IAEjC,MAAM,KAAK,GAAkB,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACxB,IAAI,GAAG,KAAK,SAAS;YAAE,SAAS;QAEhC,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,IAAA,gBAAW,EAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC1E,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACxB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAC3D,aAAa,CAAC,SAAS,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;GAOG;AACI,MAAM,kCAAkC,GAAG,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE,EAAoC,EAAE;IAClH,MAAM,KAAK,GAA6B,EAAE,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,oBAAoB,EAAE,CAAC;QACxC,mBAAmB,CAAC,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,CAAC,EAAE;YACtD,MAAM,OAAO,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;YAC7C,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;gBAAE,OAAO;YAEtE,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,cAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;YACrF,IAAI,CAAC,kBAAkB;gBAAE,OAAO;YAEhC,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,kBAAkB;aACnB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AApBW,QAAA,kCAAkC,sCAoB7C;AAEF;;;;;;;;;;;;GAYG;AACI,MAAM,yBAAyB,GAAG,GAAqC,EAAE;IAC9E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,eAAe,GAAG,cAAI,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAA6B,EAAE,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,MAAM,eAAe,GAAG,CAAC,cAAsB,EAAQ,EAAE;QACvD,IAAI,CAAC,IAAA,eAAU,EAAC,cAAc,CAAC;YAAE,OAAO;QAExC,MAAM,SAAS,GAAG,cAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC/C,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO;QACnC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAEvB,MAAM,cAAc,GAAG,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;QAC/D,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC;QAE/C,MAAM,UAAU,GAAG,IAAA,gBAAW,EAAC,cAAc,EAAE;YAC7C,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;QACH,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE;gBAAE,SAAS;YAE9D,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAExD,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/B,MAAM,YAAY,GAAG,IAAA,gBAAW,EAAC,SAAS,EAAE;oBAC1C,aAAa,EAAE,IAAI;iBACpB,CAAC,CAAC;gBACH,KAAK,MAAM,UAAU,IAAI,YAAY,EAAE,CAAC;oBACtC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE;wBAAE,SAAS;oBACxE,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;gBACzE,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,eAAe,CAAC,eAAe,CAAC,CAAC;IAEjC,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAA,0CAAkC,EAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;IACpD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ,IAAI,IAAA,uBAAe,EAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACpE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC9B,CAAC,CAAC;AArDW,QAAA,yBAAyB,6BAqDpC","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport path from \"path\";\n\ntype PackageJsonWithMigrations = {\n readonly name?: string;\n readonly version: string;\n readonly migrations?: string | { readonly migrations: string };\n};\n\nexport type DiscoveredPackage = {\n readonly name: string;\n readonly version: string;\n readonly migrationsJsonPath: string;\n};\n\nconst readPackageJson = (pkgJsonPath: string): PackageJsonWithMigrations | undefined => {\n try {\n const raw = readFileSync(pkgJsonPath, \"utf-8\");\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n};\n\nconst resolveMigrationsPath = (pkgDir: string, pkgJson: PackageJsonWithMigrations): string | undefined => {\n const migrations = pkgJson.migrations;\n if (migrations === undefined) return undefined;\n\n const migrationsFile = typeof migrations === \"string\" ? migrations : migrations.migrations;\n const resolved = path.resolve(pkgDir, migrationsFile);\n\n return existsSync(resolved) ? resolved : undefined;\n};\n\n/** Compares two semver strings, returning negative/zero/positive like Array.sort comparators. */\n/** Loads and parses a migrations.json file from disk. */\nexport const loadMigrationsJson = (\n migrationsPath: string\n):\n | {\n readonly generators?: Record<\n string,\n { readonly version: string; readonly description: string; readonly implementation: string }\n >;\n }\n | undefined => {\n try {\n const raw = readFileSync(migrationsPath, \"utf-8\");\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n};\n\nimport { compare } from \"semver\";\n/**\n * Compare two semver strings, returning negative/zero/positive like Array.sort comparators.\n *\n * @param a - The first semver string to compare.\n * @param b - The second semver string to compare.\n * @returns {number} A negative number if a < b, 0 if a === b, and a positive number if a > b.\n */\nexport const compareVersions = (a: string, b: string): number => compare(a, b);\n\nconst scanScopeDir = (scopeDir: string): ReadonlyArray<DiscoveredPackage> => {\n if (!existsSync(scopeDir)) return [];\n\n const entries = readdirSync(scopeDir, { withFileTypes: true });\n const found: Array<DiscoveredPackage> = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;\n\n const pkgDir = path.join(scopeDir, entry.name);\n const pkgJsonPath = path.join(pkgDir, \"package.json\");\n const pkgJson = readPackageJson(pkgJsonPath);\n\n if (!pkgJson) continue;\n\n const migrationsJsonPath = resolveMigrationsPath(pkgDir, pkgJson);\n if (migrationsJsonPath) {\n found.push({\n name: pkgJson.name ?? `@trackunit/${entry.name}`,\n version: pkgJson.version,\n migrationsJsonPath,\n });\n }\n }\n\n return found;\n};\n\nconst MANAGER_REPO_PACKAGE_NAME = \"@trackunit/manager\";\nconst WORKSPACE_SCAN_ROOTS: ReadonlyArray<string> = [\"libs\", \"apps\"];\n\nconst isManagerMonorepo = (cwd: string): boolean => {\n const rootPkgJson = readPackageJson(path.join(cwd, \"package.json\"));\n return rootPkgJson?.name === MANAGER_REPO_PACKAGE_NAME;\n};\n\nconst walkForPackageJsons = (rootDir: string, onPackageJson: (pkgJsonPath: string) => void): void => {\n if (!existsSync(rootDir)) return;\n\n const stack: Array<string> = [rootDir];\n while (stack.length > 0) {\n const dir = stack.pop();\n if (dir === undefined) continue;\n\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n const entryPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n stack.push(entryPath);\n } else if (entry.isFile() && entry.name === \"package.json\") {\n onPackageJson(entryPath);\n }\n }\n }\n};\n\n/**\n * In-monorepo fallback discovery: scans libs/ and apps/ for\n * package.json files whose name starts with \"@trackunit/\" and that\n * declare a \"migrations\" field. Used only when the engine is run\n * inside the manager monorepo (root package.json name is\n * \"@trackunit/manager\"), since internal workspace projects are not\n * symlinked under node_modules/@trackunit/* by Yarn.\n */\nexport const discoverWorkspaceTrackunitPackages = (cwd: string = process.cwd()): ReadonlyArray<DiscoveredPackage> => {\n const found: Array<DiscoveredPackage> = [];\n\n for (const root of WORKSPACE_SCAN_ROOTS) {\n walkForPackageJsons(path.join(cwd, root), pkgJsonPath => {\n const pkgJson = readPackageJson(pkgJsonPath);\n if (!pkgJson?.name || !pkgJson.name.startsWith(\"@trackunit/\")) return;\n\n const migrationsJsonPath = resolveMigrationsPath(path.dirname(pkgJsonPath), pkgJson);\n if (!migrationsJsonPath) return;\n\n found.push({\n name: pkgJson.name,\n version: pkgJson.version,\n migrationsJsonPath,\n });\n });\n }\n\n return found;\n};\n\n/**\n * Recursively walks node_modules directories looking for @trackunit/*\n * packages that declare a \"migrations\" field. Scans root node_modules\n * and nested node_modules inside every package. When the same package\n * is found at multiple levels, the highest semver version is kept.\n *\n * Works with all package managers (npm, yarn, pnpm, bun).\n *\n * When invoked inside the manager monorepo (root package.json name\n * is \"@trackunit/manager\"), additionally scans libs/ and apps/ for\n * workspace packages that ship migrations, since those are not\n * linked under node_modules/@trackunit/.\n */\nexport const discoverTrackunitPackages = (): ReadonlyArray<DiscoveredPackage> => {\n const cwd = process.cwd();\n const rootNodeModules = path.resolve(cwd, \"node_modules\");\n const allFound: Array<DiscoveredPackage> = [];\n const visited = new Set<string>();\n\n const walkNodeModules = (nodeModulesDir: string): void => {\n if (!existsSync(nodeModulesDir)) return;\n\n const canonical = path.resolve(nodeModulesDir);\n if (visited.has(canonical)) return;\n visited.add(canonical);\n\n const trackunitScope = path.join(nodeModulesDir, \"@trackunit\");\n allFound.push(...scanScopeDir(trackunitScope));\n\n const topEntries = readdirSync(nodeModulesDir, {\n withFileTypes: true,\n });\n for (const entry of topEntries) {\n if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;\n\n const entryPath = path.join(nodeModulesDir, entry.name);\n\n if (entry.name.startsWith(\"@\")) {\n const scopeEntries = readdirSync(entryPath, {\n withFileTypes: true,\n });\n for (const scopeEntry of scopeEntries) {\n if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink()) continue;\n walkNodeModules(path.join(entryPath, scopeEntry.name, \"node_modules\"));\n }\n } else {\n walkNodeModules(path.join(entryPath, \"node_modules\"));\n }\n }\n };\n\n walkNodeModules(rootNodeModules);\n\n if (isManagerMonorepo(cwd)) {\n allFound.push(...discoverWorkspaceTrackunitPackages(cwd));\n }\n\n const byName = new Map<string, DiscoveredPackage>();\n for (const pkg of allFound) {\n const existing = byName.get(pkg.name);\n if (!existing || compareVersions(pkg.version, existing.version) > 0) {\n byName.set(pkg.name, pkg);\n }\n }\n\n return [...byName.values()];\n};\n"]}
1
+ {"version":3,"file":"discover-packages.js","sourceRoot":"","sources":["../../../../../../libs/migrations/src/generators/utils/discover-packages.ts"],"names":[],"mappings":"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","sourcesContent":["import { logger } from \"@nx/devkit\";\nimport { type Dirent, existsSync, readdirSync, readFileSync, realpathSync } from \"fs\";\nimport path from \"path\";\n\ntype PackageJsonWithMigrations = {\n readonly name?: string;\n readonly version: string;\n readonly migrations?: string | { readonly migrations: string };\n};\n\nexport type DiscoveredPackage = {\n readonly name: string;\n readonly version: string;\n readonly migrationsJsonPath: string;\n};\n\nconst readPackageJson = (pkgJsonPath: string): PackageJsonWithMigrations | undefined => {\n try {\n const raw = readFileSync(pkgJsonPath, \"utf-8\");\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n};\n\nconst resolveMigrationsPath = (pkgDir: string, pkgJson: PackageJsonWithMigrations): string | undefined => {\n const migrations = pkgJson.migrations;\n if (migrations === undefined) return undefined;\n\n const migrationsFile = typeof migrations === \"string\" ? migrations : migrations.migrations;\n const resolved = path.resolve(pkgDir, migrationsFile);\n\n return existsSync(resolved) ? resolved : undefined;\n};\n\n/** Compares two semver strings, returning negative/zero/positive like Array.sort comparators. */\n/** Loads and parses a migrations.json file from disk. */\nexport const loadMigrationsJson = (\n migrationsPath: string\n):\n | {\n readonly generators?: Record<\n string,\n { readonly version: string; readonly description: string; readonly implementation: string }\n >;\n }\n | undefined => {\n try {\n const raw = readFileSync(migrationsPath, \"utf-8\");\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n};\n\nimport { compare } from \"semver\";\n/**\n * Compare two semver strings, returning negative/zero/positive like Array.sort comparators.\n *\n * @param a - The first semver string to compare.\n * @param b - The second semver string to compare.\n * @returns {number} A negative number if a < b, 0 if a === b, and a positive number if a > b.\n */\nexport const compareVersions = (a: string, b: string): number => compare(a, b);\n\n// Swallowing a failed directory read shrinks the discovered package set, which\n// `migrate` then reports as \"no pending migrations\" -- indistinguishable from\n// the green-run-no-codemods symptom this walker exists to fix. Warning keeps\n// the walk crash-proof while making a truncated result visible.\nconst warnSkipped = (dir: string, error: unknown): void => {\n const reason = error instanceof Error ? error.message : String(error);\n logger.warn(\n `Skipped \"${dir}\" while discovering @trackunit packages: ${reason}. Discovered migrations may be incomplete.`\n );\n};\n\n// `existsSync` only says the entry resolved, not that it can be listed: an\n// unreadable store directory or a path that turns out to be a file still\n// throws. Every listing in this module goes through here so one bad directory\n// cannot abort discovery.\nconst readDirEntries = (dir: string): ReadonlyArray<Dirent> => {\n try {\n return readdirSync(dir, { withFileTypes: true });\n } catch (error) {\n warnSkipped(dir, error);\n return [];\n }\n};\n\nconst scanScopeDir = (scopeDir: string): ReadonlyArray<DiscoveredPackage> => {\n if (!existsSync(scopeDir)) return [];\n\n const entries = readDirEntries(scopeDir);\n const found: Array<DiscoveredPackage> = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;\n\n const pkgDir = path.join(scopeDir, entry.name);\n const pkgJsonPath = path.join(pkgDir, \"package.json\");\n const pkgJson = readPackageJson(pkgJsonPath);\n\n if (!pkgJson) continue;\n\n const migrationsJsonPath = resolveMigrationsPath(pkgDir, pkgJson);\n if (migrationsJsonPath) {\n found.push({\n name: pkgJson.name ?? `@trackunit/${entry.name}`,\n version: pkgJson.version,\n migrationsJsonPath,\n });\n }\n }\n\n return found;\n};\n\nconst MANAGER_REPO_PACKAGE_NAME = \"@trackunit/manager\";\nconst WORKSPACE_SCAN_ROOTS: ReadonlyArray<string> = [\"libs\", \"apps\"];\n\n// Levels to climb from a resolved package directory to reach the\n// `node_modules` holding it: `<store>/node_modules/name` versus\n// `<store>/node_modules/@scope/name`.\nconst UNSCOPED_DEPTH = 1;\nconst SCOPED_DEPTH = 2;\n\nconst isManagerMonorepo = (cwd: string): boolean => {\n const rootPkgJson = readPackageJson(path.join(cwd, \"package.json\"));\n return rootPkgJson?.name === MANAGER_REPO_PACKAGE_NAME;\n};\n\nconst walkForPackageJsons = (rootDir: string, onPackageJson: (pkgJsonPath: string) => void): void => {\n if (!existsSync(rootDir)) return;\n\n const stack: Array<string> = [rootDir];\n while (stack.length > 0) {\n const dir = stack.pop();\n if (dir === undefined) continue;\n\n for (const entry of readDirEntries(dir)) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n const entryPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n stack.push(entryPath);\n } else if (entry.isFile() && entry.name === \"package.json\") {\n onPackageJson(entryPath);\n }\n }\n }\n};\n\n/**\n * In-monorepo fallback discovery: scans libs/ and apps/ for\n * package.json files whose name starts with \"@trackunit/\" and that\n * declare a \"migrations\" field. Used only when the engine is run\n * inside the manager monorepo (root package.json name is\n * \"@trackunit/manager\"), since internal workspace projects are not\n * symlinked under node_modules/@trackunit/* by Yarn.\n */\nexport const discoverWorkspaceTrackunitPackages = (cwd: string = process.cwd()): ReadonlyArray<DiscoveredPackage> => {\n const found: Array<DiscoveredPackage> = [];\n\n for (const root of WORKSPACE_SCAN_ROOTS) {\n walkForPackageJsons(path.join(cwd, root), pkgJsonPath => {\n const pkgJson = readPackageJson(pkgJsonPath);\n if (!pkgJson?.name || !pkgJson.name.startsWith(\"@trackunit/\")) return;\n\n const migrationsJsonPath = resolveMigrationsPath(path.dirname(pkgJsonPath), pkgJson);\n if (!migrationsJsonPath) return;\n\n found.push({\n name: pkgJson.name,\n version: pkgJson.version,\n migrationsJsonPath,\n });\n });\n }\n\n return found;\n};\n\n/**\n * Recursively walks node_modules directories looking for @trackunit/*\n * packages that declare a \"migrations\" field. Scans root node_modules\n * and nested node_modules inside every package. When the same package\n * is found at multiple levels, the highest semver version is kept.\n *\n * Works with hoisted layouts (npm, yarn) and with the isolated layouts\n * where dependencies are symlinks into a content store (Yarn\n * `nodeLinker: pnpm`, pnpm, bun), including pnpm with hoisting disabled,\n * where a package's dependencies are siblings of it inside the store\n * rather than nested beneath it.\n *\n * When invoked inside the manager monorepo (root package.json name\n * is \"@trackunit/manager\"), additionally scans libs/ and apps/ for\n * workspace packages that ship migrations, since those are not\n * linked under node_modules/@trackunit/.\n *\n * @param cwd - Workspace root to scan; defaults to the current directory\n * @returns {ReadonlyArray<DiscoveredPackage>} One entry per package name, at its highest discovered version\n */\nexport const discoverTrackunitPackages = (cwd: string = process.cwd()): ReadonlyArray<DiscoveredPackage> => {\n const rootNodeModules = path.resolve(cwd, \"node_modules\");\n const allFound: Array<DiscoveredPackage> = [];\n const visited = new Set<string>();\n\n const walkNodeModules = (nodeModulesDir: string): void => {\n if (!existsSync(nodeModulesDir)) return;\n\n // Isolated layouts (Yarn `nodeLinker: pnpm`, pnpm, bun) symlink every\n // dependency into a content store, so a mutual dependency produces\n // endless distinct logical paths pointing at the same real directories.\n // `path.resolve` normalises but does not follow symlinks, so it cannot\n // recognise the repeat and the walk grows the path until the OS rejects\n // it. Canonicalising through the real path is what makes this terminate.\n let canonical: string;\n try {\n canonical = realpathSync(nodeModulesDir);\n } catch (error) {\n warnSkipped(nodeModulesDir, error);\n return;\n }\n if (visited.has(canonical)) return;\n visited.add(canonical);\n\n const trackunitScope = path.join(nodeModulesDir, \"@trackunit\");\n allFound.push(...scanScopeDir(trackunitScope));\n\n for (const entry of readDirEntries(nodeModulesDir)) {\n if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;\n\n const entryPath = path.join(nodeModulesDir, entry.name);\n\n if (entry.name.startsWith(\"@\")) {\n for (const scopeEntry of readDirEntries(entryPath)) {\n if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink()) continue;\n const scopedPkgDir = path.join(entryPath, scopeEntry.name);\n walkNodeModules(path.join(scopedPkgDir, \"node_modules\"));\n walkStoreSiblingsOf(scopedPkgDir, SCOPED_DEPTH);\n }\n } else {\n walkNodeModules(path.join(entryPath, \"node_modules\"));\n walkStoreSiblingsOf(entryPath, UNSCOPED_DEPTH);\n }\n }\n };\n\n /**\n * pnpm with hoisting disabled places a package's dependencies *alongside*\n * it inside the store rather than nested beneath it, so probing only\n * `<entry>/node_modules` never reaches a @trackunit package that is purely\n * transitive. Resolving the entry and walking the store `node_modules` that\n * contains it reaches those siblings. In hoisted layouts this resolves back\n * to a directory the walk has already scanned, so `visited` makes it free.\n */\n function walkStoreSiblingsOf(pkgDir: string, depth: number): void {\n if (!existsSync(pkgDir)) return;\n\n let real: string;\n try {\n real = realpathSync(pkgDir);\n } catch (error) {\n warnSkipped(pkgDir, error);\n return;\n }\n\n let candidate = real;\n for (let level = 0; level < depth; level += 1) candidate = path.dirname(candidate);\n\n if (path.basename(candidate) === \"node_modules\") walkNodeModules(candidate);\n }\n\n walkNodeModules(rootNodeModules);\n\n if (isManagerMonorepo(cwd)) {\n allFound.push(...discoverWorkspaceTrackunitPackages(cwd));\n }\n\n const byName = new Map<string, DiscoveredPackage>();\n for (const pkg of allFound) {\n const existing = byName.get(pkg.name);\n if (!existing || compareVersions(pkg.version, existing.version) > 0) {\n byName.set(pkg.name, pkg);\n }\n }\n\n return [...byName.values()];\n};\n"]}
@@ -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"]}