@trackunit/migrations 0.2.64 → 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 +26 -0
- package/README.md +62 -17
- package/package.json +1 -1
- package/src/generators/migrate/generator.js +181 -85
- package/src/generators/migrate/schema.json +1 -1
- package/src/generators/run-migrations/__fixtures__/appending-migration.cjs +7 -0
- package/src/generators/run-migrations/generator.js +52 -30
- package/src/generators/utils/migrations-file.js +81 -0
- package/src/generators/utils/upgrade-dependencies.js +58 -3
- package/src/generators/migrate/generator.js.map +0 -1
- package/src/generators/run-migrations/generator.js.map +0 -1
- package/src/generators/utils/discover-packages.js.map +0 -1
- package/src/generators/utils/upgrade-dependencies.js.map +0 -1
- package/src/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
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
|
+
|
|
1
27
|
## 0.2.64 (2026-09-01)
|
|
2
28
|
|
|
3
29
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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`,
|
|
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
|
-
|
|
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
|
|
36
|
-
readState -->
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
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 +\
|
|
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 -->
|
|
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
|
|
74
|
-
rccMig["react-chart-components/migrations.json\n- chart-api-v2
|
|
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\
|
|
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
|
|
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
|
|
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
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 || (
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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
|
-
//
|
|
77
|
-
// migrations
|
|
78
|
-
//
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
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(
|
|
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}
|
|
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
|
-
*
|
|
123
|
-
* stay in the list
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(`\
|
|
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
|
|
142
|
-
|
|
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"]}
|