@trackunit/migrations 0.2.64-alpha-7557f71cc7a.0 → 0.2.65

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,33 @@
1
+ ## 0.2.65 (2026-09-03)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **migrations:** order a package's codemods oldest change first [GLU-2100] ([8b3613f2fee](https://github.com/Trackunit/manager/commit/8b3613f2fee))
6
+ - **migrations:** extract a shared trackunit-migrations.json writer [GLU-2108] ([fe27709d5db](https://github.com/Trackunit/manager/commit/fe27709d5db))
7
+ - **migrations:** explain an upgrade capped by the running Node version [GLU-2107] ([1f072c0df08](https://github.com/Trackunit/manager/commit/1f072c0df08))
8
+ - **migrations:** report the migrations withheld and what unlocks them [GLU-2106] ([3f52b8f3e56](https://github.com/Trackunit/manager/commit/3f52b8f3e56))
9
+
10
+ ### 🩹 Fixes
11
+
12
+ - **migrations:** make the tree's root the only workspace root [GLU-2100] ([de594f728a8](https://github.com/Trackunit/manager/commit/de594f728a8))
13
+ - **migrations:** order codemods by a comparison that is a total order [GLU-2100] ([9d707fa8148](https://github.com/Trackunit/manager/commit/9d707fa8148))
14
+ - **migrations:** warn when a discovered manifest cannot be read [GLU-2130] ([#25243](https://github.com/Trackunit/manager/issues/25243))
15
+ - **migrations:** anchor the post-install migrations write to the tree, not cwd [GLU-2131] ([#25243](https://github.com/Trackunit/manager/issues/25243))
16
+ - **migrations:** stop an unplaceable migration stamping the package as migrated [GLU-2125] ([#25243](https://github.com/Trackunit/manager/issues/25243))
17
+ - **migrations:** stop a run that withheld everything reading as a clean run [GLU-2109] ([18fc4dd395a](https://github.com/Trackunit/manager/commit/18fc4dd395a))
18
+ - **migrations:** collect migrations after the install, in one run [GLU-2108] ([b69bcaba725](https://github.com/Trackunit/manager/commit/b69bcaba725))
19
+ - **migrations:** survive versions semver cannot parse [GLU-2106] ([daf7f3b8130](https://github.com/Trackunit/manager/commit/daf7f3b8130))
20
+ - **migrations:** withhold migrations targeting versions that are not installed [GLU-2106] ([08762f4851d](https://github.com/Trackunit/manager/commit/08762f4851d))
21
+
22
+ ### ❤️ Thank You
23
+
24
+ - Cursor @cursoragent
25
+ - Mikkel Thorbjørn Andersen
26
+
27
+ ## 0.2.64 (2026-09-01)
28
+
29
+ This was a version bump only for migrations to align it with other projects, there were no code changes.
30
+
1
31
  ## 0.2.63 (2026-08-31)
2
32
 
3
33
  This was a version bump only for migrations to align it with other projects, there were no code changes.
package/README.md CHANGED
@@ -7,23 +7,68 @@ Coordinator library that upgrades every `@trackunit/*` dependency to its latest
7
7
  `migrate` does two independent things, in this order:
8
8
 
9
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.
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 every migration in their manifests that has not already run.
11
+
12
+ ### A migration has an identity, not a version
13
+
14
+ Nothing in the run compares versions to decide whether a migration applies. A migration has run or it has not, and `trackunit-migrations.json` records the ones that have, by `package:name`:
15
+
16
+ ```json
17
+ {
18
+ "appliedMigrations": ["@trackunit/react-components:v2-0-0-tag-add-removetaglabel"],
19
+ "migrations": []
20
+ }
21
+ ```
22
+
23
+ The manifest that gets read ships *inside* the version that is installed, so what it declares applies to that version by construction — there is nothing left for a version comparison to decide. This is the same model Flyway and Alembic use, and it removes a whole class of failure that a hand-written `version` field could not avoid: a tag one release ahead of reality silently withholds a codemod forever, and a tag one release behind silently rewrites source against an API that does not exist yet. Both happened in this repo before the field was removed.
24
+
25
+ The obligation that replaces it sits with whoever publishes a codemod: **a migration must ship in the same release as the API change it migrates, and never earlier.** Publishing it early puts it in the manifest of a version it does not apply to, which is the one way this model can still go wrong.
26
+
27
+ Names are opaque identifiers. The `v2-0-0-` prefixes are a readability convention matching the directory layout, not something the tool parses. Renaming an entry makes it a different migration and it will run again, so don't.
28
+
29
+ A manifest entry does still carry a `version`, and it does exactly one job: it orders a package's codemods, oldest change first, so several codemods touching the same code apply in the sequence they were written for rather than in whatever order the JSON keys happen to sit. It is never a filter. An entry with no version, or one semver cannot read, keeps its manifest position — authoring order is then the best signal available, and a malformed field must not silently reorder anything.
30
+
31
+ The distinction matters because conflating the two is what the old model got wrong: one field answered both "has this run" and "does this apply", so a tag that was wrong in either direction broke the other question too.
11
32
 
12
33
  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
34
 
35
+ "Latest published version" means the latest your Node can actually run. Resolution applies the `engines.node` filter, so a package whose newest release requires a Node you do not have resolves to the newest release you can run instead. That is correct, but silently stopping short reads as the tool failing to upgrade, so the run says when it happened:
36
+
37
+ ```
38
+ @trackunit/react-components: stopped at 1.10.61; 2.10.21 requires Node >=24.x and this run is on Node 22.14.0
39
+ ```
40
+
41
+ The version named is the one the `latest` dist-tag points at, because that is what resolution would have picked. A higher release the tag does not point at -- a hotfix from a maintenance branch, or anything deliberately untagged -- was never a candidate on any Node version, so blaming it for the cap would be untrue.
42
+
43
+ Only a version held back by an unsatisfied `engines.node` is reported. A newer version passed over for any other reason is not an engine cap, a malformed `engines` range is treated as "cannot tell" rather than "blocked", and prereleases are never named as the version you are missing. The cap is reported, never worked around: which version gets picked is `npm-pick-manifest`'s decision and it is already the right one.
44
+
14
45
  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
46
 
16
47
  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
48
 
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.
49
+ One run does the whole job. Migrations live inside published packages, so they are only discoverable once the new versions are on disk — which means discovery has to happen after the install, not before it. When versions change, `migrate` upgrades `package.json`, installs, and only then discovers and queues; when nothing needs upgrading there is nothing to install and it all happens in one pass. Either way `migrate` stops at queueing, so you still review `trackunit-migrations.json` before `run-migrations` touches any source.
50
+
51
+ Two consequences of writing the file after Nx has flushed and locked its file tree. Nx reports `UPDATE package.json` but not the migrations file, and `--dry-run` reports the `package.json` change without discovering migrations, because the install it would need never runs. Nx already warns that a dry run changed nothing.
52
+
53
+ If the install fails, the run fails. `package.json` keeps the upgraded versions, because that part succeeded, and no migrations file is written — exiting cleanly there would report a workspace as migrated when discovery never ran. The error names the install command and the re-run.
19
54
 
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.
55
+ `--skipInstall` is the one case that cannot finish in a single run: you have taken responsibility for the install, so the migrations shipped in the new versions are not visible yet. That run says so, writes no migrations file, and names both remaining steps.
56
+
57
+ 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. Collection is otherwise identical; an author can run the codemod they just wrote without waiting for a release.
21
58
 
22
59
  Then the second generator applies them:
23
60
 
24
- - **`run-migrations`** -- executes the pending migrations from `trackunit-migrations.json`, then clears the list
61
+ - **`run-migrations`** -- executes the pending migrations from `trackunit-migrations.json`, records each one that succeeded in `appliedMigrations`, and clears the list
62
+
63
+ Only `run-migrations` records anything. `migrate` proposes work and carries the applied set through untouched, so a codemod that failed, or that you never ran, is offered again next time.
64
+
65
+ `run-migrations` also enforces the record it keeps: an entry whose `package:name` is already in `appliedMigrations` is skipped and dropped from the pending list, and an identity listed twice runs once. A queue can name the same codemod twice after a merge, a hand-edit, or the legacy-format fallback that re-queues everything, and re-running is not harmless in general -- a rename that also rewrites an import list, or anything that appends, corrupts source on the second pass, and `migrate` would not offer it again afterwards to say so.
66
+
67
+ Both generators treat the workspace root as the root of Nx's file tree, never the directory `nx` happened to be invoked from. That one root decides where `node_modules` is scanned, what implementation paths are stored relative to, and where `trackunit-migrations.json` is read and written. Splitting them would be worse than picking the wrong one, because the halves then disagree while the run still reports success: invoked from `apps/foo`, discovery would scan `apps/foo/node_modules`, queue nothing, and write an empty-looking file at the root.
68
+
69
+ ### Upgrading from the old format
25
70
 
26
- Version state is tracked in `trackunit-migrations.json` so migrations that have already been applied are not re-run.
71
+ An earlier version of this file tracked a `migratedVersions` high-water mark per package. That cannot be translated into identities — it records how far a package got, never which codemods got it there. A workspace still on the old format gets one warning and every migration queued again. Review the list before running it; a codemod whose change is already in place matches nothing and is a no-op.
27
72
 
28
73
  ```mermaid
29
74
  flowchart TD
@@ -32,10 +77,10 @@ flowchart TD
32
77
  scan --> checkField{"package.json\nhas migrations field?"}
33
78
  checkField -->|No| skipPkg[Skip package]
34
79
  checkField -->|Yes| loadManifest["Load migrations.json"]
35
- loadManifest --> readState["Read trackunit-migrations.json\nfor migratedVersions state"]
36
- readState --> filterVersion{"migration.version >\nlastMigratedVersion?"}
37
- filterVersion -->|No| skipMigration[Skip migration]
38
- filterVersion -->|Yes| collect["Add to pending list"]
80
+ loadManifest --> readState["Read trackunit-migrations.json\nfor appliedMigrations"]
81
+ readState --> filterApplied{"package:name already\nin appliedMigrations?"}
82
+ filterApplied -->|Yes| skipMigration[Skip migration]
83
+ filterApplied -->|No| collect["Add to pending list"]
39
84
  end
40
85
 
41
86
  subgraph updatePkg [Version Updates: every @trackunit/* dependency]
@@ -44,16 +89,16 @@ flowchart TD
44
89
  resolveLatest --> compareDeps{"Latest >\npackage.json version?"}
45
90
  compareDeps -->|Yes| bumpDep["Rewrite version in\nconsumer package.json,\nkeeping the range operator"]
46
91
  compareDeps -->|No| noop[No change]
47
- bumpDep --> reinstall["Install, then re-run migrate\nto see migrations shipped\nin the new versions"]
92
+ bumpDep --> reinstall["Install, then discover\nin the same run --\nmigrations ship inside\nthe new versions"]
48
93
  end
49
94
 
50
- collect --> writeFile["Write trackunit-migrations.json\nwith pending migrations +\nupdated migratedVersions"]
95
+ collect --> writeFile["Write trackunit-migrations.json\nwith pending migrations +\nunchanged appliedMigrations"]
51
96
 
52
97
  subgraph step2 [Step 2: nx g @trackunit/migrations:run-migrations]
53
98
  readFile["Read trackunit-migrations.json"] --> loop["For each pending migration"]
54
99
  loop --> importImpl["Import migration implementation"]
55
100
  importImpl --> execute["Execute migration against NX Tree\n(AST transforms, file rewrites)"]
56
- execute --> clearPending["Clear pending list\nPreserve migratedVersions"]
101
+ execute --> recordApplied["Record package:name in\nappliedMigrations on success,\nkeep failures pending"]
57
102
  end
58
103
 
59
104
  writeFile --> review["Developer reviews\ntrackunit-migrations.json"]
@@ -70,12 +115,12 @@ flowchart LR
70
115
  end
71
116
 
72
117
  subgraph migrationsFiles [migrations.json per package]
73
- rcMig["react-components/migrations.json\n- rename-button-kind (v1.22.0)\n- button-required-variant (v2.0.0)"]
74
- rccMig["react-chart-components/migrations.json\n- chart-api-v2 (v1.20.0)"]
118
+ rcMig["react-components/migrations.json\n- rename-button-kind\n- button-required-variant"]
119
+ rccMig["react-chart-components/migrations.json\n- chart-api-v2"]
75
120
  end
76
121
 
77
122
  subgraph output [trackunit-migrations.json]
78
- pending["migrations:\n- rename-button-kind\n- button-required-variant\n- chart-api-v2\n\nmigratedVersions:\n react-components: 2.0.0\n react-chart-components: 1.20.0"]
123
+ pending["migrations:\n- rename-button-kind\n- button-required-variant\n- chart-api-v2\n\nappliedMigrations:\n (filled in by run-migrations)"]
79
124
  end
80
125
 
81
126
  rc --> rcMig
@@ -100,7 +145,7 @@ nx g @trackunit/migrations:migrate
100
145
 
101
146
  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
147
 
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.
148
+ If any version changed, the generator installs the new packages and discovers migrations in the same run -- migrations ship inside the packages themselves, so they are only inspectable once installed. Add `--skipInstall` to run the install yourself, and `--verbose` to see the per-package decisions, including the dependencies that were left alone.
104
149
 
105
150
  ### Step 2: Run pending migrations
106
151
 
@@ -108,7 +153,7 @@ If any version changed, the generator installs the new packages and asks you to
108
153
  nx g @trackunit/migrations:run-migrations
109
154
  ```
110
155
 
111
- This executes each migration listed in `trackunit-migrations.json` and clears the pending list. You can delete the file afterward or keep it for version tracking.
156
+ This executes each migration listed in `trackunit-migrations.json`, records the ones that succeeded, and clears the pending list. Commit the file: it is what stops those migrations being offered again. Deleting it means every migration runs again on the next `migrate`.
112
157
 
113
158
  ### Internal monorepo
114
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/migrations",
3
- "version": "0.2.64-alpha-7557f71cc7a.0",
3
+ "version": "0.2.65",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -4,20 +4,19 @@ exports.migrate = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const devkit_1 = require("@nx/devkit");
6
6
  const path_1 = tslib_1.__importDefault(require("path"));
7
+ const semver_1 = require("semver");
7
8
  const discover_packages_1 = require("../utils/discover-packages");
9
+ const migrations_file_1 = require("../utils/migrations-file");
8
10
  const upgrade_dependencies_1 = require("../utils/upgrade-dependencies");
9
- const TRACKUNIT_MIGRATIONS_FILE = "trackunit-migrations.json";
10
- const getLastMigratedVersion = (tree, packageName) => {
11
- if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE))
12
- return undefined;
13
- try {
14
- const file = (0, devkit_1.readJson)(tree, TRACKUNIT_MIGRATIONS_FILE);
15
- return file.migratedVersions[packageName];
16
- }
17
- catch {
18
- return undefined;
19
- }
20
- };
11
+ /**
12
+ * `pkg.version` is typed `string` but read from whatever package.json happens
13
+ * to be on disk, so it is a compile-time claim rather than a runtime
14
+ * guarantee, and `semver.compare` throws on anything it cannot parse. One
15
+ * malformed version would otherwise abort the dependency pin for every other
16
+ * package, in a walker deliberately built to survive broken packages rather
17
+ * than crash on them.
18
+ */
19
+ const isComparableVersion = (version) => version !== undefined && (0, semver_1.valid)(version) !== null;
21
20
  const getCurrentDepVersion = (tree, packageName) => {
22
21
  const packageJson = (0, devkit_1.readJson)(tree, "package.json");
23
22
  return packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName];
@@ -27,7 +26,10 @@ const updateDepVersion = (tree, pkg) => {
27
26
  if (currentDep === undefined)
28
27
  return;
29
28
  const currentVersion = (0, upgrade_dependencies_1.pinnedVersionOf)(currentDep);
30
- if (currentVersion === undefined || (0, discover_packages_1.compareVersions)(pkg.version, currentVersion) <= 0) {
29
+ if (currentVersion === undefined || !isComparableVersion(pkg.version)) {
30
+ return;
31
+ }
32
+ if ((0, discover_packages_1.compareVersions)(pkg.version, currentVersion) <= 0) {
31
33
  return;
32
34
  }
33
35
  (0, devkit_1.updateJson)(tree, "package.json", json => {
@@ -42,11 +44,16 @@ const updateDepVersion = (tree, pkg) => {
42
44
  devkit_1.logger.info(` Updated ${pkg.name} to ${pkg.version} in package.json`);
43
45
  };
44
46
  /**
45
- * Returns an implementation path that is relative to the workspace root
46
- * (`process.cwd()`) when the resolved path lives inside the workspace,
47
- * so that `trackunit-migrations.json` is portable between machines and
48
- * checkouts. Falls back to the absolute path for migrations sourced from
49
- * outside the workspace (e.g. node_modules in a non-default location).
47
+ * Returns an implementation path that is relative to the workspace root when
48
+ * the resolved path lives inside the workspace, so that
49
+ * `trackunit-migrations.json` is portable between machines and checkouts.
50
+ * Falls back to the absolute path for migrations sourced from outside the
51
+ * workspace (e.g. node_modules in a non-default location).
52
+ *
53
+ * The root passed in is the tree's, which is also what `run-migrations`
54
+ * resolves against. Relativising against the invocation directory instead
55
+ * would store paths that only resolve when the second command happens to be
56
+ * run from the same place.
50
57
  */
51
58
  const portableImplementationPath = (absolutePath, workspaceRoot) => {
52
59
  const relative = path_1.default.relative(workspaceRoot, absolutePath);
@@ -55,11 +62,48 @@ const portableImplementationPath = (absolutePath, workspaceRoot) => {
55
62
  }
56
63
  return relative.split(path_1.default.sep).join("/");
57
64
  };
58
- const collectPendingMigrations = (pkg, lastMigrated, migrationsJson, workspaceRoot) => {
65
+ /**
66
+ * Orders a package's codemods oldest change first.
67
+ *
68
+ * `version` is the release whose change a codemod prepares for, and it decides
69
+ * order only. It is deliberately not a filter: a codemod is published only once
70
+ * it is safe to run against the release shipping it, so everything a manifest
71
+ * declares is applicable. `react-components@1.26.8` ships the `v2-0-0-*`
72
+ * codemods for exactly that reason -- they migrate to APIs 1.26.8 already has,
73
+ * which is the preparation a consumer needs *before* the major bump. Filtering
74
+ * them out would withhold the work and leave the bump to break instead.
75
+ *
76
+ * An entry with no version, or one semver cannot read, keeps its manifest
77
+ * position: authoring order is then the best signal available, and a malformed
78
+ * version field must not silently reorder a package's codemods.
79
+ */
80
+ const inExecutionOrder = (entries) => {
81
+ // Only the comparable entries take part in the sort, and they are written
82
+ // back into the slots comparable entries already occupied. Comparing every
83
+ // pair instead -- falling back to position whenever one side is unparseable
84
+ // -- is not a total order: with 3.0.0, no-version, 1.5.0 you get
85
+ // `a < b` and `b < c` by position but `a > c` by version, and `sort` may then
86
+ // return anything. Both promises above break at once, and an unversioned
87
+ // entry can even drift off its own index.
88
+ const comparable = entries
89
+ .map((entry, position) => ({ entry, position, version: entry[1].version }))
90
+ .filter((candidate) => isComparableVersion(candidate.version));
91
+ const sorted = [...comparable].sort((left, right) => (0, discover_packages_1.compareVersions)(left.version, right.version) || left.position - right.position);
92
+ const ordered = [...entries];
93
+ comparable.forEach(({ position }, index) => {
94
+ const replacement = sorted[index];
95
+ if (replacement !== undefined) {
96
+ ordered[position] = replacement.entry;
97
+ }
98
+ });
99
+ return ordered;
100
+ };
101
+ /** Queues every migration in a package's manifest that has not already run. */
102
+ const collectMigrations = (pkg, applied, migrationsJson, workspaceRoot) => {
59
103
  const pending = [];
60
104
  const migrationsDir = path_1.default.dirname(pkg.migrationsJsonPath);
61
- for (const [name, entry] of Object.entries(migrationsJson.generators ?? {})) {
62
- if (lastMigrated !== undefined && (0, discover_packages_1.compareVersions)(entry.version, lastMigrated) <= 0) {
105
+ for (const [name, entry] of inExecutionOrder(Object.entries(migrationsJson.generators ?? {}))) {
106
+ if (applied.has((0, migrations_file_1.migrationId)(pkg.name, name))) {
63
107
  continue;
64
108
  }
65
109
  const absoluteImpl = path_1.default.resolve(migrationsDir, entry.implementation);
@@ -73,19 +117,68 @@ const collectPendingMigrations = (pkg, lastMigrated, migrationsJson, workspaceRo
73
117
  }
74
118
  return pending;
75
119
  };
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) => {
120
+ // `--skipInstall` is the one case that cannot finish in a single run: the
121
+ // migrations that ship inside the new versions are only discoverable once they
122
+ // are on disk, and the caller has taken responsibility for putting them there.
123
+ const reportSkippedInstall = (upgrades) => {
82
124
  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;
125
+ devkit_1.logger.warn(`\n${upgrades.length} dependency version(s) changed, so node_modules is now stale. Run:\n ${pmc.install}\n nx g @trackunit/migrations:migrate`);
126
+ };
127
+ /**
128
+ * Everything that depends on what is actually installed: discovery, the
129
+ * already-applied filter, the summary, and the write.
130
+ *
131
+ * Split out because it has to run at two different points in the Nx generator
132
+ * lifecycle. When no install is needed it runs inside the generator, against
133
+ * the tree. When an install is needed it runs in the generator's callback,
134
+ * after Nx has flushed and the install has refreshed node_modules -- which is
135
+ * the only moment the new versions are visible at all.
136
+ */
137
+ const collectAndWriteMigrations = (collection) => {
138
+ const packages = (0, discover_packages_1.discoverTrackunitPackages)(collection.workspaceRoot);
139
+ devkit_1.logger.info(`Discovered ${packages.length} @trackunit/* package(s) with migrations`);
140
+ const allPending = [];
141
+ for (const pkg of packages) {
142
+ if (collection.pinDependenciesIn !== undefined) {
143
+ updateDepVersion(collection.pinDependenciesIn, pkg);
144
+ }
145
+ const migrationsJson = (0, discover_packages_1.loadMigrationsJson)(pkg.migrationsJsonPath);
146
+ // Discovery only yields a package whose manifest exists on disk, so
147
+ // `undefined` means it is there and could not be parsed. Folding that into
148
+ // "declares nothing" would drop whatever it declares without saying so,
149
+ // leaving a short queue that looks complete -- the failure this library
150
+ // keeps having to close.
151
+ if (migrationsJson === undefined) {
152
+ devkit_1.logger.warn(` ${pkg.name}: could not read ${pkg.migrationsJsonPath}, so any migrations it declares are missing from this run.`);
153
+ continue;
154
+ }
155
+ if (!migrationsJson.generators || Object.keys(migrationsJson.generators).length === 0) {
156
+ if (collection.verbose) {
157
+ devkit_1.logger.info(` ${pkg.name}: no migrations defined`);
158
+ }
159
+ continue;
160
+ }
161
+ const pending = collectMigrations(pkg, collection.applied, migrationsJson, collection.workspaceRoot);
162
+ allPending.push(...pending);
163
+ if (collection.verbose) {
164
+ const total = Object.keys(migrationsJson.generators).length;
165
+ devkit_1.logger.info(` ${pkg.name}: installed=${pkg.version}, ${total - pending.length} of ${total} already applied`);
166
+ }
167
+ if (pending.length > 0) {
168
+ devkit_1.logger.info(`${pkg.name}@${pkg.version}: ${pending.length} pending migration(s)`);
169
+ }
170
+ }
171
+ // The applied set is carried through untouched. `migrate` only ever proposes
172
+ // work; `run-migrations` is what records that a migration ran, and only for
173
+ // the ones that actually succeeded.
174
+ collection.write({ appliedMigrations: [...collection.applied], migrations: allPending });
175
+ if (allPending.length === 0) {
176
+ devkit_1.logger.info(`No pending migrations. ${migrations_file_1.TRACKUNIT_MIGRATIONS_FILE} updated.`);
177
+ }
178
+ else {
179
+ devkit_1.logger.info(`\nWrote ${allPending.length} pending migration(s) to ${migrations_file_1.TRACKUNIT_MIGRATIONS_FILE}`);
180
+ devkit_1.logger.info("Review the file, then run: nx g @trackunit/migrations:run-migrations");
87
181
  }
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
182
  };
90
183
  /**
91
184
  * Step 1: Upgrades every `@trackunit/*` dependency to its latest published
@@ -104,66 +197,69 @@ const reportUpgradeFollowUp = (upgrades, willInstall) => {
104
197
  */
105
198
  const migrate = async (tree, options) => {
106
199
  const verbose = options.verbose ?? false;
107
- const workspaceRoot = process.cwd();
200
+ // Nx does not normalise cwd for generators -- `getCwd()` exists in nx
201
+ // precisely because it can be a subdirectory -- so the workspace root is the
202
+ // tree's root and nothing here may read cwd instead. One root, used for all
203
+ // three jobs: where node_modules is scanned, what implementation paths are
204
+ // relative to, and where the file is written.
205
+ //
206
+ // Splitting them is worse than picking the wrong one, because the parts then
207
+ // disagree and the run still reports success: from `apps/foo`, discovery
208
+ // scans `apps/foo/node_modules` and queues nothing while the file lands at
209
+ // the root looking like a clean workspace. `run-migrations` resolves stored
210
+ // paths against this same root.
211
+ const workspaceRoot = tree.root;
108
212
  // Inside manager the @trackunit libs are workspace projects whose versions
109
213
  // are owned by CI, so resolving them from the registry would fight the
110
214
  // 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)();
113
- devkit_1.logger.info(`Discovered ${packages.length} @trackunit/* package(s) with migrations`);
114
- const allPending = [];
115
- const migratedVersions = {};
116
- if (tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {
117
- try {
118
- const existing = (0, devkit_1.readJson)(tree, TRACKUNIT_MIGRATIONS_FILE);
119
- Object.assign(migratedVersions, existing.migratedVersions);
120
- }
121
- catch {
122
- // start fresh
123
- }
215
+ const inManagerWorkspace = (0, upgrade_dependencies_1.isManagerWorkspace)(tree);
216
+ const upgrades = inManagerWorkspace ? [] : await (0, upgrade_dependencies_1.upgradeTrackunitDependencies)(tree, verbose);
217
+ // Read before the install, because after it Nx has flushed and locked the
218
+ // tree and this is the only handle on the workspace's existing state.
219
+ const applied = (0, migrations_file_1.readAppliedMigrations)(tree);
220
+ // Nothing changed, so node_modules already matches package.json and the
221
+ // second stage can run here: through the tree, so Nx reports the change and
222
+ // `--dry-run` suppresses it.
223
+ if (upgrades.length === 0) {
224
+ collectAndWriteMigrations({
225
+ workspaceRoot,
226
+ verbose,
227
+ applied,
228
+ pinDependenciesIn: tree,
229
+ write: (0, migrations_file_1.treeMigrationsFileWriter)(tree),
230
+ });
231
+ return undefined;
124
232
  }
125
- for (const pkg of packages) {
126
- updateDepVersion(tree, pkg);
127
- const migrationsJson = (0, discover_packages_1.loadMigrationsJson)(pkg.migrationsJsonPath);
128
- if (!migrationsJson?.generators || Object.keys(migrationsJson.generators).length === 0) {
129
- if (verbose) {
130
- devkit_1.logger.info(` ${pkg.name}: no migrations defined`);
131
- }
132
- migratedVersions[pkg.name] = pkg.version;
133
- continue;
134
- }
135
- const lastMigrated = getLastMigratedVersion(tree, pkg.name);
136
- if (verbose) {
137
- devkit_1.logger.info(` ${pkg.name}: installed=${pkg.version}, lastMigrated=${lastMigrated ?? "none"}`);
233
+ if (options.skipInstall === true) {
234
+ reportSkippedInstall(upgrades);
235
+ return undefined;
236
+ }
237
+ // Versions changed, so anything discovered now would come from the packages
238
+ // being replaced. Nothing is written here on purpose: a migrations file
239
+ // produced before the install would assert a state that is about to stop
240
+ // being true. Nx runs this callback after flushing package.json, which is the
241
+ // first moment the new versions can be installed and therefore seen.
242
+ return async () => {
243
+ try {
244
+ (0, devkit_1.installPackagesTask)(tree);
138
245
  }
139
- const pending = collectPendingMigrations(pkg, lastMigrated, migrationsJson, workspaceRoot);
140
- allPending.push(...pending);
141
- if (pending.length > 0) {
142
- devkit_1.logger.info(`${pkg.name}@${pkg.version}: ${pending.length} pending migration(s)`);
143
- // Defer advancing migratedVersions until run-migrations has applied
144
- // these entries. Otherwise a second `migrate` run would read this
145
- // version back as `lastMigrated` and silently drop the queued migrations.
146
- continue;
246
+ catch (error) {
247
+ // The upgraded package.json is already flushed and is kept: it is the
248
+ // work that succeeded. Rethrowing fails the run non-zero, which matters
249
+ // because the migrations were never collected -- exiting 0 here would
250
+ // report a workspace as migrated when discovery never ran.
251
+ const pmc = (0, devkit_1.getPackageManagerCommand)((0, devkit_1.detectPackageManager)());
252
+ devkit_1.logger.error(`\nThe install failed, so no migrations were collected. package.json keeps the upgraded versions. Run:\n ${pmc.install}\n nx g @trackunit/migrations:migrate`);
253
+ throw error;
147
254
  }
148
- migratedVersions[pkg.name] = pkg.version;
149
- }
150
- const output = {
151
- migratedVersions,
152
- migrations: allPending,
255
+ collectAndWriteMigrations({
256
+ workspaceRoot,
257
+ verbose,
258
+ applied,
259
+ pinDependenciesIn: undefined,
260
+ write: (0, migrations_file_1.fsMigrationsFileWriter)(workspaceRoot),
261
+ });
153
262
  };
154
- tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(output, null, 2));
155
- if (allPending.length === 0) {
156
- devkit_1.logger.info("No pending migrations. trackunit-migrations.json updated.");
157
- }
158
- else {
159
- devkit_1.logger.info(`\nWrote ${allPending.length} pending migration(s) to ${TRACKUNIT_MIGRATIONS_FILE}`);
160
- devkit_1.logger.info("Review the file, then run: nx g @trackunit/migrations:run-migrations");
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;
167
263
  };
168
264
  exports.migrate = migrate;
169
265
  exports.default = exports.migrate;
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "skipInstall": {
11
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.",
12
+ "description": "Do not install after updating @trackunit/* dependency versions. Migrations ship inside the new versions, so nothing can discover them until something installs them: this run upgrades package.json, writes no trackunit-migrations.json, and tells you to install and run the generator again. Without this flag a single run does all of it.",
13
13
  "default": false
14
14
  }
15
15
  }
@@ -0,0 +1,7 @@
1
+ // A deliberately non-idempotent codemod: it appends rather than replaces, so a
2
+ // second execution leaves a second mark. Running once is indistinguishable from
3
+ // running twice for the idempotent fixtures, which is why this one exists.
4
+ module.exports = function appendingMigration(tree) {
5
+ const existing = tree.exists("runs.txt") ? tree.read("runs.txt", "utf-8") : "";
6
+ tree.write("runs.txt", `${existing}x`);
7
+ };
@@ -7,7 +7,7 @@ const fs_1 = require("fs");
7
7
  const module_1 = require("module");
8
8
  const path_1 = tslib_1.__importDefault(require("path"));
9
9
  const url_1 = require("url");
10
- const discover_packages_1 = require("../utils/discover-packages");
10
+ const migrations_file_1 = require("../utils/migrations-file");
11
11
  const requireMigration = (0, module_1.createRequire)(__filename);
12
12
  const MODULE_EXTENSIONS = [".js", ".cjs", ".mjs", ".ts", ".tsx"];
13
13
  const isFile = (candidate) => {
@@ -50,11 +50,11 @@ let typeScriptLoaderRegistered = false;
50
50
  * path resolution. Consumers run compiled `.js` and never reach this branch,
51
51
  * so the loaders stay dev-only dependencies of the manager monorepo.
52
52
  */
53
- const ensureTypeScriptLoader = () => {
53
+ const ensureTypeScriptLoader = (workspaceRoot) => {
54
54
  if (typeScriptLoaderRegistered)
55
55
  return;
56
56
  const tsConfigPaths = requireMigration("tsconfig-paths");
57
- const configResult = tsConfigPaths.loadConfig(path_1.default.join(process.cwd(), "tsconfig.base.json"));
57
+ const configResult = tsConfigPaths.loadConfig(path_1.default.join(workspaceRoot, "tsconfig.base.json"));
58
58
  if (configResult.resultType === "success") {
59
59
  tsConfigPaths.register({ baseUrl: configResult.absoluteBaseUrl, paths: configResult.paths });
60
60
  }
@@ -63,12 +63,15 @@ const ensureTypeScriptLoader = () => {
63
63
  };
64
64
  /**
65
65
  * Resolves a stored implementation path. Relative paths in
66
- * `trackunit-migrations.json` are interpreted as relative to the
67
- * workspace root (`process.cwd()`); absolute paths are used as-is.
68
- * This keeps the on-disk file portable between checkouts while
69
- * preserving back-compat with absolute paths.
66
+ * `trackunit-migrations.json` are interpreted as relative to the workspace
67
+ * root; absolute paths are used as-is. This keeps the on-disk file portable
68
+ * between checkouts while preserving back-compat with absolute paths.
69
+ *
70
+ * The root is the tree's, matching what `migrate` relativised against. Using
71
+ * the invocation directory would resolve to nothing whenever the two commands
72
+ * are run from different places.
70
73
  */
71
- const resolveImplementationPath = (storedPath) => path_1.default.isAbsolute(storedPath) ? storedPath : path_1.default.resolve(process.cwd(), storedPath);
74
+ const resolveImplementationPath = (storedPath, workspaceRoot) => path_1.default.isAbsolute(storedPath) ? storedPath : path_1.default.resolve(workspaceRoot, storedPath);
72
75
  const isEsmRequireError = (error) => error instanceof Error && "code" in error && error.code === "ERR_REQUIRE_ESM";
73
76
  /**
74
77
  * Loads a migration module from an implementation path that is stored
@@ -81,10 +84,10 @@ const isEsmRequireError = (error) => error instanceof Error && "code" in error &
81
84
  * these paths at all; it is kept only as a fallback for genuine ESM output
82
85
  * (rare — the migrations build emits CommonJS).
83
86
  */
84
- const loadMigrationModule = async (resolvedPath) => {
87
+ const loadMigrationModule = async (resolvedPath, workspaceRoot) => {
85
88
  const file = resolveModuleFile(resolvedPath);
86
89
  if (file.endsWith(".ts") || file.endsWith(".tsx")) {
87
- ensureTypeScriptLoader();
90
+ ensureTypeScriptLoader(workspaceRoot);
88
91
  }
89
92
  try {
90
93
  return requireMigration(file);
@@ -95,18 +98,17 @@ const loadMigrationModule = async (resolvedPath) => {
95
98
  return Promise.resolve(`${(0, url_1.pathToFileURL)(file).href}`).then(s => tslib_1.__importStar(require(s)));
96
99
  }
97
100
  };
98
- const TRACKUNIT_MIGRATIONS_FILE = "trackunit-migrations.json";
99
101
  const runMigration = async (tree, migration, verbose) => {
100
102
  try {
101
- const resolvedPath = resolveImplementationPath(migration.implementation);
102
- const migrationModule = await loadMigrationModule(resolvedPath);
103
+ const resolvedPath = resolveImplementationPath(migration.implementation, tree.root);
104
+ const migrationModule = await loadMigrationModule(resolvedPath, tree.root);
103
105
  const migrationFn = migrationModule.default ?? migrationModule;
104
106
  if (typeof migrationFn !== "function") {
105
107
  devkit_1.logger.warn(` "${migration.name}" does not export a function, skipping`);
106
108
  return false;
107
109
  }
108
110
  if (verbose) {
109
- devkit_1.logger.info(` Running: ${migration.name} (v${migration.version}) from ${migration.package}`);
111
+ devkit_1.logger.info(` Running: ${migration.name} from ${migration.package}`);
110
112
  }
111
113
  migrationFn(tree);
112
114
  return true;
@@ -118,53 +120,73 @@ const runMigration = async (tree, migration, verbose) => {
118
120
  };
119
121
  /**
120
122
  * Step 2: Reads trackunit-migrations.json and executes each pending
121
- * migration. Successful migrations are removed from the pending list
122
- * and bump `migratedVersions[package]` to their version. Failures
123
- * stay in the list so they remain retryable on the next run without
124
- * having to go through `migrate` again.
123
+ * migration. Successful migrations are removed from the pending list and
124
+ * recorded in `appliedMigrations`, which is what stops `migrate` offering
125
+ * them again. Failures stay in the list and are not recorded, so they remain
126
+ * retryable on the next run without having to go through `migrate` again.
125
127
  *
126
128
  * Usage: nx g @trackunit/migrations:run-migrations
127
129
  */
128
130
  const runMigrations = async (tree, options) => {
129
- if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {
130
- devkit_1.logger.error(`${TRACKUNIT_MIGRATIONS_FILE} not found. Run "nx g @trackunit/migrations:migrate" first.`);
131
+ if (!tree.exists(migrations_file_1.TRACKUNIT_MIGRATIONS_FILE)) {
132
+ devkit_1.logger.error(`${migrations_file_1.TRACKUNIT_MIGRATIONS_FILE} not found. Run "nx g @trackunit/migrations:migrate" first.`);
131
133
  return;
132
134
  }
133
- const file = (0, devkit_1.readJson)(tree, TRACKUNIT_MIGRATIONS_FILE);
135
+ const file = (0, devkit_1.readJson)(tree, migrations_file_1.TRACKUNIT_MIGRATIONS_FILE);
134
136
  if (file.migrations.length === 0) {
135
137
  devkit_1.logger.info("No pending migrations to run.");
136
138
  return;
137
139
  }
138
140
  devkit_1.logger.info(`Running ${file.migrations.length} migration(s)...\n`);
139
141
  let succeeded = 0;
142
+ let skipped = 0;
140
143
  const remaining = [];
141
- const migratedVersions = { ...file.migratedVersions };
144
+ // A Set, because a migration that failed on an earlier run and succeeds now
145
+ // is still in the pending list but may already be recorded.
146
+ const applied = new Set(file.appliedMigrations);
147
+ // The queue can name the same codemod twice -- a merge, a hand-edit, or the
148
+ // legacy-format path that re-queues everything. Being in `applied` is the
149
+ // same situation one run earlier.
150
+ const attempted = new Set();
142
151
  for (const migration of file.migrations) {
152
+ const id = (0, migrations_file_1.migrationId)(migration.package, migration.name);
153
+ // Executing a codemod a second time is not a no-op in general: a rename
154
+ // that also rewrites an import list, or anything that appends, corrupts
155
+ // source on the second pass, and `migrate` will not re-queue it afterwards
156
+ // so nothing says it happened. The recorded set is only worth keeping if it
157
+ // is enforced here.
158
+ if (applied.has(id) || attempted.has(id)) {
159
+ const reason = applied.has(id) ? "already applied" : "listed more than once in this run";
160
+ devkit_1.logger.info(`${migration.package}: ${migration.name} - skipped, ${reason}`);
161
+ skipped++;
162
+ continue;
163
+ }
164
+ attempted.add(id);
143
165
  devkit_1.logger.info(`${migration.package}: ${migration.name} - ${migration.description}`);
144
166
  const ok = await runMigration(tree, migration, options.verbose ?? false);
145
167
  if (ok) {
146
168
  succeeded++;
147
- const current = migratedVersions[migration.package];
148
- if (current === undefined || (0, discover_packages_1.compareVersions)(migration.version, current) > 0) {
149
- migratedVersions[migration.package] = migration.version;
150
- }
169
+ applied.add(id);
151
170
  }
152
171
  else {
153
172
  remaining.push(migration);
154
173
  }
155
174
  }
156
175
  const updated = {
157
- migratedVersions,
176
+ appliedMigrations: [...applied],
158
177
  migrations: remaining,
159
178
  };
160
- tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(updated, null, 2));
179
+ tree.write(migrations_file_1.TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(updated, null, 2));
161
180
  await (0, devkit_1.formatFiles)(tree);
162
181
  devkit_1.logger.info(`\n${succeeded} migration(s) applied successfully.`);
182
+ if (skipped > 0) {
183
+ devkit_1.logger.info(`${skipped} migration(s) skipped as already applied, and removed from the pending list.`);
184
+ }
163
185
  if (remaining.length > 0) {
164
- devkit_1.logger.warn(`${remaining.length} migration(s) failed and remain in ${TRACKUNIT_MIGRATIONS_FILE}. Fix the cause and re-run "nx g @trackunit/migrations:run-migrations".`);
186
+ devkit_1.logger.warn(`${remaining.length} migration(s) failed and remain in ${migrations_file_1.TRACKUNIT_MIGRATIONS_FILE}. Fix the cause and re-run "nx g @trackunit/migrations:run-migrations".`);
165
187
  }
166
188
  else {
167
- devkit_1.logger.info(`\nYou can delete ${TRACKUNIT_MIGRATIONS_FILE} or keep it for version tracking.`);
189
+ devkit_1.logger.info(`\nCommit ${migrations_file_1.TRACKUNIT_MIGRATIONS_FILE} to record which migrations have run.`);
168
190
  }
169
191
  };
170
192
  exports.runMigrations = runMigrations;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fsMigrationsFileWriter = exports.treeMigrationsFileWriter = exports.readAppliedMigrations = exports.migrationId = exports.TRACKUNIT_MIGRATIONS_FILE = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const devkit_1 = require("@nx/devkit");
6
+ const fs_1 = require("fs");
7
+ const path_1 = tslib_1.__importDefault(require("path"));
8
+ exports.TRACKUNIT_MIGRATIONS_FILE = "trackunit-migrations.json";
9
+ /**
10
+ * A migration's identity is the package it ships in plus its key in that
11
+ * package's `migrations.json`. Nothing is parsed back out of this string --
12
+ * the only operation is set membership -- so the separator just has to be a
13
+ * character npm forbids in a package name, which `:` is.
14
+ */
15
+ const migrationId = (packageName, name) => `${packageName}:${name}`;
16
+ exports.migrationId = migrationId;
17
+ /**
18
+ * A file that exists but cannot be read is not the same as no file at all, and
19
+ * treating it as one loses the record twice over: this run re-queues
20
+ * everything, then rewrites the file from what it just found, so the previous
21
+ * contents are gone. Version control is the way back, which only helps a
22
+ * developer who has been told.
23
+ */
24
+ const warnRecordUnreadable = () => {
25
+ devkit_1.logger.warn(`${exports.TRACKUNIT_MIGRATIONS_FILE} exists but its record of applied migrations could not be read. Every migration is queued again, and this run overwrites the file -- recover the previous contents from version control if you need them. Migrations already applied are no-ops.`);
26
+ };
27
+ /**
28
+ * Reads the set of migrations that have already run.
29
+ *
30
+ * A migration has run or it has not, and that is answered by identity. The
31
+ * predecessor of this file instead tracked a semver high-water mark per package
32
+ * and filtered on `entry.version > lastMigrated`, which could only ever be as
33
+ * accurate as a hand-written tag and could be advanced past codemods that never
34
+ * ran -- so a skipped migration stayed skipped for good.
35
+ *
36
+ * `migrations.json` still carries `version`, but only to order a package's
37
+ * codemods. Nothing about whether a migration runs is read from it.
38
+ */
39
+ const readAppliedMigrations = (tree) => {
40
+ if (!tree.exists(exports.TRACKUNIT_MIGRATIONS_FILE))
41
+ return new Set();
42
+ try {
43
+ const file = (0, devkit_1.readJson)(tree, exports.TRACKUNIT_MIGRATIONS_FILE);
44
+ if (Array.isArray(file.appliedMigrations)) {
45
+ return new Set(file.appliedMigrations);
46
+ }
47
+ // The version-based format cannot be translated: it recorded how far each
48
+ // package had got, never which codemods produced that. Saying so is the
49
+ // only honest option, because the alternative -- guessing -- is what makes
50
+ // a migration silently skipped forever.
51
+ if (file.migratedVersions !== undefined) {
52
+ devkit_1.logger.warn(`${exports.TRACKUNIT_MIGRATIONS_FILE} uses the old per-package version format, which cannot say which migrations ran. Every migration is queued again; review the list before running it. Migrations already applied are no-ops.`);
53
+ return new Set();
54
+ }
55
+ warnRecordUnreadable();
56
+ return new Set();
57
+ }
58
+ catch {
59
+ warnRecordUnreadable();
60
+ return new Set();
61
+ }
62
+ };
63
+ exports.readAppliedMigrations = readAppliedMigrations;
64
+ const serialise = (file) => JSON.stringify(file, null, 2);
65
+ /**
66
+ * Writes through the Nx tree, so Nx reports the change and `--dry-run` suppresses it.
67
+ *
68
+ * @param tree - The NX virtual file system for the workspace being migrated.
69
+ * @returns {MigrationsFileWriter} A writer that records the file as a tree change.
70
+ */
71
+ const treeMigrationsFileWriter = (tree) => file => tree.write(exports.TRACKUNIT_MIGRATIONS_FILE, serialise(file));
72
+ exports.treeMigrationsFileWriter = treeMigrationsFileWriter;
73
+ /**
74
+ * Writes straight to disk, for use after Nx has flushed and locked the tree.
75
+ *
76
+ * @param workspaceRoot - Absolute path the file is written relative to.
77
+ * @returns {MigrationsFileWriter} A writer that writes the file with `fs`.
78
+ */
79
+ const fsMigrationsFileWriter = (workspaceRoot) => file => (0, fs_1.writeFileSync)(path_1.default.join(workspaceRoot, exports.TRACKUNIT_MIGRATIONS_FILE), serialise(file), "utf-8");
80
+ exports.fsMigrationsFileWriter = fsMigrationsFileWriter;
81
+ //# sourceMappingURL=migrations-file.js.map
@@ -96,6 +96,50 @@ const collectTrackunitDependencies = (tree) => {
96
96
  };
97
97
  exports.collectTrackunitDependencies = collectTrackunitDependencies;
98
98
  const registryOptions = () => process.env.TU_NPM_REGISTRY ? { registry: process.env.TU_NPM_REGISTRY } : undefined;
99
+ /**
100
+ * `pacote.manifest` already applies npm-pick-manifest's engine filter against
101
+ * the running Node version, so the version it hands back can be far behind the
102
+ * registry's `latest` with nothing said about it -- which reads as the tool
103
+ * failing to upgrade rather than as a deliberate, correct choice. Naming what
104
+ * was passed over needs the packument, which carries `engines` in its cheap
105
+ * abbreviated form and is served from pacote's cache after the manifest lookup.
106
+ *
107
+ * The only version worth naming is the one the `latest` dist-tag points at,
108
+ * because that is what `pacote.manifest` resolves and therefore the only thing
109
+ * the engine filter can have taken away. Scanning for the highest published
110
+ * version instead would blame a hotfix from a maintenance branch, or anything
111
+ * deliberately left untagged, for an upgrade it was never a candidate for on
112
+ * any Node version.
113
+ *
114
+ * Only an unsatisfied `engines.node` counts. A malformed range is treated as
115
+ * "cannot tell" rather than "blocked", so a bad `engines` field never invents
116
+ * a cap, and a prerelease tagged `latest` is not what a consumer is missing
117
+ * out on.
118
+ */
119
+ const findEngineCap = async (name, reached, nodeVersion) => {
120
+ if ((0, semver_1.valid)(reached) === null || (0, semver_1.valid)(nodeVersion) === null)
121
+ return undefined;
122
+ let packument;
123
+ try {
124
+ packument = await pacote.packument(name, registryOptions());
125
+ }
126
+ catch {
127
+ // The upgrade itself already succeeded; this lookup only annotates it, so a
128
+ // failure here must stay silent rather than imply no cap exists.
129
+ return undefined;
130
+ }
131
+ const latest = packument["dist-tags"]?.latest;
132
+ if (latest === undefined || (0, semver_1.valid)(latest) === null)
133
+ return undefined;
134
+ if ((0, semver_1.prerelease)(latest) !== null || !(0, semver_1.gt)(latest, reached))
135
+ return undefined;
136
+ const requiredNode = packument.versions?.[latest]?.engines?.node;
137
+ if (requiredNode === undefined || (0, semver_1.validRange)(requiredNode) === null)
138
+ return undefined;
139
+ if ((0, semver_1.satisfies)(nodeVersion, requiredNode))
140
+ return undefined;
141
+ return { reached, skipped: latest, requiredNode };
142
+ };
99
143
  // A single unreachable or unpublished package must not abort the upgrade of the
100
144
  // other 30, so a failed lookup degrades to "leave this one alone" and says so.
101
145
  const resolveLatestVersion = async (name) => {
@@ -125,7 +169,7 @@ const resolveLatestVersion = async (name) => {
125
169
  * @returns {Promise<ReadonlyArray<DependencyUpgrade>>} One entry per declaration whose specifier changed.
126
170
  * @throws {Error} When not one dependency could be resolved from the registry, which means the upgrade never happened.
127
171
  */
128
- const upgradeTrackunitDependencies = async (tree, verbose) => {
172
+ const upgradeTrackunitDependencies = async (tree, verbose, nodeVersion = process.versions.node) => {
129
173
  const declared = (0, exports.collectTrackunitDependencies)(tree);
130
174
  const names = [...new Set(declared.map(declaration => declaration.name))];
131
175
  if (names.length === 0) {
@@ -136,11 +180,22 @@ const upgradeTrackunitDependencies = async (tree, verbose) => {
136
180
  // Keyed by name, not by declaration, so a package declared in both fields
137
181
  // still costs one registry round trip.
138
182
  const latestByName = new Map();
183
+ const capByName = new Map();
139
184
  await Promise.all(names.map(async (name) => {
140
185
  const latest = await resolveLatestVersion(name);
141
- if (latest !== undefined)
142
- latestByName.set(name, latest);
186
+ if (latest === undefined)
187
+ return;
188
+ latestByName.set(name, latest);
189
+ const cap = await findEngineCap(name, latest, nodeVersion);
190
+ if (cap !== undefined)
191
+ capByName.set(name, cap);
143
192
  }));
193
+ // Reported before the up-to-date branch below, because a workspace can be at
194
+ // the newest version its Node can run and still be capped -- which is exactly
195
+ // the case where "already at their latest version" is most misleading.
196
+ for (const [name, cap] of capByName) {
197
+ devkit_1.logger.info(` ${name}: stopped at ${cap.reached}; ${cap.skipped} requires Node ${cap.requiredNode} and this run is on Node ${nodeVersion}`);
198
+ }
144
199
  // Resolving nothing at all is an environment failure -- no network, a
145
200
  // proxying or private registry, an expired token -- not a workspace that
146
201
  // happens to be current. Reporting it as success would recreate the exact
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
package/src/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/migrations/src/index.ts"],"names":[],"mappings":";;;AAAA,4DAAyD;AAAhD,oGAAA,OAAO,OAAA;AAChB,mEAAsE;AAA7D,0GAAA,aAAa,OAAA","sourcesContent":["export { migrate } from \"./generators/migrate/generator\";\nexport { runMigrations } from \"./generators/run-migrations/generator\";\n"]}