@trackunit/migrations 0.0.3
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 +7 -0
- package/README.md +105 -0
- package/generators.json +15 -0
- package/package.json +18 -0
- package/src/generators/migrate/generator.js +135 -0
- package/src/generators/migrate/schema.json +11 -0
- package/src/generators/run-migrations/__fixtures__/failing-migration.cjs +3 -0
- package/src/generators/run-migrations/__fixtures__/noop-migration.cjs +1 -0
- package/src/generators/run-migrations/generator.js +90 -0
- package/src/generators/run-migrations/schema.json +11 -0
- package/src/generators/utils/discover-packages.js +195 -0
- package/src/index.js +8 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @trackunit/migrations
|
|
2
|
+
|
|
3
|
+
Coordinator library that discovers installed `@trackunit/*` packages, updates their versions, and runs pending migrations.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
Each published `@trackunit/*` package can declare a `"migrations"` field in its `package.json` pointing to a `migrations.json` manifest. This library recursively scans `node_modules` (including nested/transitive dependencies) to find all such packages, then follows a two-step flow modeled after `nx migrate`:
|
|
8
|
+
|
|
9
|
+
1. **`migrate`** -- discovers packages, updates `package.json` versions, writes pending migrations to `trackunit-migrations.json`
|
|
10
|
+
2. **`run-migrations`** -- executes the pending migrations from that file, then clears the list
|
|
11
|
+
|
|
12
|
+
Version state is tracked in `trackunit-migrations.json` so migrations that have already been applied are not re-run.
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart TD
|
|
16
|
+
subgraph step1 [Step 1: nx g @trackunit/migrations:migrate]
|
|
17
|
+
scan["Recursively scan node_modules\nfor @trackunit/* packages"]
|
|
18
|
+
scan --> checkField{"package.json\nhas migrations field?"}
|
|
19
|
+
checkField -->|No| skipPkg[Skip package]
|
|
20
|
+
checkField -->|Yes| loadManifest["Load migrations.json"]
|
|
21
|
+
loadManifest --> readState["Read trackunit-migrations.json\nfor migratedVersions state"]
|
|
22
|
+
readState --> filterVersion{"migration.version >\nlastMigratedVersion?"}
|
|
23
|
+
filterVersion -->|No| skipMigration[Skip migration]
|
|
24
|
+
filterVersion -->|Yes| collect["Add to pending list"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
subgraph updatePkg [Version Updates]
|
|
28
|
+
scan --> compareDeps{"Installed version >\npackage.json version?"}
|
|
29
|
+
compareDeps -->|Yes| bumpDep["Update version in\nconsumer package.json"]
|
|
30
|
+
compareDeps -->|No| noop[No change]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
collect --> writeFile["Write trackunit-migrations.json\nwith pending migrations +\nupdated migratedVersions"]
|
|
34
|
+
|
|
35
|
+
subgraph step2 [Step 2: nx g @trackunit/migrations:run-migrations]
|
|
36
|
+
readFile["Read trackunit-migrations.json"] --> loop["For each pending migration"]
|
|
37
|
+
loop --> importImpl["Import migration implementation"]
|
|
38
|
+
importImpl --> execute["Execute migration against NX Tree\n(AST transforms, file rewrites)"]
|
|
39
|
+
execute --> clearPending["Clear pending list\nPreserve migratedVersions"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
writeFile --> review["Developer reviews\ntrackunit-migrations.json"]
|
|
43
|
+
review --> readFile
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
flowchart LR
|
|
48
|
+
subgraph nodeModules ["node_modules (recursive scan)"]
|
|
49
|
+
rc["@trackunit/react-components\nv2.0.0\nmigrations: ./migrations.json"]
|
|
50
|
+
rcc["@trackunit/react-chart-components\nv1.20.0\nmigrations: ./migrations.json"]
|
|
51
|
+
su["@trackunit/shared-utils\nv1.13.0\n(no migrations field)"]
|
|
52
|
+
nested["some-lib/node_modules/\n@trackunit/react-components\nv1.21.8 (older, deduped out)"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
subgraph migrationsFiles [migrations.json per package]
|
|
56
|
+
rcMig["react-components/migrations.json\n- rename-button-kind (v1.22.0)\n- button-required-variant (v2.0.0)"]
|
|
57
|
+
rccMig["react-chart-components/migrations.json\n- chart-api-v2 (v1.20.0)"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
subgraph output [trackunit-migrations.json]
|
|
61
|
+
pending["migrations:\n- rename-button-kind\n- button-required-variant\n- chart-api-v2\n\nmigratedVersions:\n react-components: 2.0.0\n react-chart-components: 1.20.0"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
rc --> rcMig
|
|
65
|
+
rcc --> rccMig
|
|
66
|
+
su -.->|skipped| nodeModules
|
|
67
|
+
nested -.->|deduped| rc
|
|
68
|
+
rcMig --> pending
|
|
69
|
+
rccMig --> pending
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
### Step 1: Discover and collect migrations
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
nx g @trackunit/migrations:migrate
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This updates `@trackunit/*` dependency versions in your `package.json` and writes a `trackunit-migrations.json` file listing any pending migrations. Review the file before proceeding.
|
|
81
|
+
|
|
82
|
+
### Step 2: Run pending migrations
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
nx g @trackunit/migrations:run-migrations
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This executes each migration listed in `trackunit-migrations.json` and clears the pending list. You can delete the file afterward or keep it for version tracking.
|
|
89
|
+
|
|
90
|
+
### Internal monorepo
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
yarn nx g @trackunit/migrations:migrate
|
|
94
|
+
yarn nx g @trackunit/migrations:run-migrations
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For more info and a full guide on Iris App SDK Development, please visit our [Developer Hub](https://developers.trackunit.com/).
|
|
98
|
+
|
|
99
|
+
## Trackunit
|
|
100
|
+
|
|
101
|
+
This package was developed by Trackunit ApS.
|
|
102
|
+
|
|
103
|
+
Trackunit is the leading SaaS-based IoT solution for the construction industry, offering an ecosystem of hardware, fleet management software & telematics.
|
|
104
|
+
|
|
105
|
+

|
package/generators.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"generators": {
|
|
4
|
+
"migrate": {
|
|
5
|
+
"factory": "./src/generators/migrate/generator",
|
|
6
|
+
"schema": "./src/generators/migrate/schema.json",
|
|
7
|
+
"description": "Discover @trackunit packages, update package.json versions, and write pending migrations to trackunit-migrations.json"
|
|
8
|
+
},
|
|
9
|
+
"run-migrations": {
|
|
10
|
+
"factory": "./src/generators/run-migrations/generator",
|
|
11
|
+
"schema": "./src/generators/run-migrations/schema.json",
|
|
12
|
+
"description": "Execute pending migrations from trackunit-migrations.json"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trackunit/migrations",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"repository": "https://github.com/Trackunit/manager",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.x"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"generators": "./generators.json",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@nx/devkit": "22.6.5",
|
|
13
|
+
"tslib": "^2.6.2",
|
|
14
|
+
"semver": "7.5.4"
|
|
15
|
+
},
|
|
16
|
+
"types": "./src/index.d.ts",
|
|
17
|
+
"type": "commonjs"
|
|
18
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrate = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const discover_packages_1 = require("../utils/discover-packages");
|
|
8
|
+
const TRACKUNIT_MIGRATIONS_FILE = "trackunit-migrations.json";
|
|
9
|
+
const getLastMigratedVersion = (tree, packageName) => {
|
|
10
|
+
if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE))
|
|
11
|
+
return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const file = (0, devkit_1.readJson)(tree, TRACKUNIT_MIGRATIONS_FILE);
|
|
14
|
+
return file.migratedVersions[packageName];
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const getCurrentDepVersion = (tree, packageName) => {
|
|
21
|
+
const packageJson = (0, devkit_1.readJson)(tree, "package.json");
|
|
22
|
+
return packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName];
|
|
23
|
+
};
|
|
24
|
+
const updateDepVersion = (tree, pkg) => {
|
|
25
|
+
const currentDep = getCurrentDepVersion(tree, pkg.name);
|
|
26
|
+
if (currentDep === undefined || (0, discover_packages_1.compareVersions)(pkg.version, currentDep.replace(/^[~^]/, "")) <= 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
(0, devkit_1.updateJson)(tree, "package.json", json => {
|
|
30
|
+
if (json.dependencies?.[pkg.name] !== undefined) {
|
|
31
|
+
json.dependencies[pkg.name] = pkg.version;
|
|
32
|
+
}
|
|
33
|
+
if (json.devDependencies?.[pkg.name] !== undefined) {
|
|
34
|
+
json.devDependencies[pkg.name] = pkg.version;
|
|
35
|
+
}
|
|
36
|
+
return json;
|
|
37
|
+
});
|
|
38
|
+
devkit_1.logger.info(` Updated ${pkg.name} to ${pkg.version} in package.json`);
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Returns an implementation path that is relative to the workspace root
|
|
42
|
+
* (`process.cwd()`) when the resolved path lives inside the workspace,
|
|
43
|
+
* so that `trackunit-migrations.json` is portable between machines and
|
|
44
|
+
* checkouts. Falls back to the absolute path for migrations sourced from
|
|
45
|
+
* outside the workspace (e.g. node_modules in a non-default location).
|
|
46
|
+
*/
|
|
47
|
+
const portableImplementationPath = (absolutePath, workspaceRoot) => {
|
|
48
|
+
const relative = path_1.default.relative(workspaceRoot, absolutePath);
|
|
49
|
+
if (relative === "" || relative.startsWith("..") || path_1.default.isAbsolute(relative)) {
|
|
50
|
+
return absolutePath;
|
|
51
|
+
}
|
|
52
|
+
return relative.split(path_1.default.sep).join("/");
|
|
53
|
+
};
|
|
54
|
+
const collectPendingMigrations = (pkg, lastMigrated, migrationsJson, workspaceRoot) => {
|
|
55
|
+
const pending = [];
|
|
56
|
+
const migrationsDir = path_1.default.dirname(pkg.migrationsJsonPath);
|
|
57
|
+
for (const [name, entry] of Object.entries(migrationsJson.generators ?? {})) {
|
|
58
|
+
if (lastMigrated !== undefined && (0, discover_packages_1.compareVersions)(entry.version, lastMigrated) <= 0) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const absoluteImpl = path_1.default.resolve(migrationsDir, entry.implementation);
|
|
62
|
+
pending.push({
|
|
63
|
+
package: pkg.name,
|
|
64
|
+
name,
|
|
65
|
+
version: entry.version,
|
|
66
|
+
description: entry.description,
|
|
67
|
+
implementation: portableImplementationPath(absoluteImpl, workspaceRoot),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return pending;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Step 1: Discovers installed @trackunit packages, compares versions
|
|
74
|
+
* against the consumer's package.json, updates dependency versions,
|
|
75
|
+
* and writes trackunit-migrations.json with pending migrations.
|
|
76
|
+
*
|
|
77
|
+
* Usage: nx g @trackunit/migrations:migrate
|
|
78
|
+
* Then: nx g @trackunit/migrations:run-migrations
|
|
79
|
+
*/
|
|
80
|
+
const migrate = (tree, options) => {
|
|
81
|
+
const packages = (0, discover_packages_1.discoverTrackunitPackages)();
|
|
82
|
+
const workspaceRoot = process.cwd();
|
|
83
|
+
devkit_1.logger.info(`Discovered ${packages.length} @trackunit/* package(s) with migrations`);
|
|
84
|
+
const allPending = [];
|
|
85
|
+
const migratedVersions = {};
|
|
86
|
+
if (tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {
|
|
87
|
+
try {
|
|
88
|
+
const existing = (0, devkit_1.readJson)(tree, TRACKUNIT_MIGRATIONS_FILE);
|
|
89
|
+
Object.assign(migratedVersions, existing.migratedVersions);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// start fresh
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const pkg of packages) {
|
|
96
|
+
updateDepVersion(tree, pkg);
|
|
97
|
+
const migrationsJson = (0, discover_packages_1.loadMigrationsJson)(pkg.migrationsJsonPath);
|
|
98
|
+
if (!migrationsJson?.generators || Object.keys(migrationsJson.generators).length === 0) {
|
|
99
|
+
if (options.verbose) {
|
|
100
|
+
devkit_1.logger.info(` ${pkg.name}: no migrations defined`);
|
|
101
|
+
}
|
|
102
|
+
migratedVersions[pkg.name] = pkg.version;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const lastMigrated = getLastMigratedVersion(tree, pkg.name);
|
|
106
|
+
if (options.verbose) {
|
|
107
|
+
devkit_1.logger.info(` ${pkg.name}: installed=${pkg.version}, lastMigrated=${lastMigrated ?? "none"}`);
|
|
108
|
+
}
|
|
109
|
+
const pending = collectPendingMigrations(pkg, lastMigrated, migrationsJson, workspaceRoot);
|
|
110
|
+
allPending.push(...pending);
|
|
111
|
+
if (pending.length > 0) {
|
|
112
|
+
devkit_1.logger.info(`${pkg.name}@${pkg.version}: ${pending.length} pending migration(s)`);
|
|
113
|
+
// Defer advancing migratedVersions until run-migrations has applied
|
|
114
|
+
// these entries. Otherwise a second `migrate` run would read this
|
|
115
|
+
// version back as `lastMigrated` and silently drop the queued migrations.
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
migratedVersions[pkg.name] = pkg.version;
|
|
119
|
+
}
|
|
120
|
+
const output = {
|
|
121
|
+
migratedVersions,
|
|
122
|
+
migrations: allPending,
|
|
123
|
+
};
|
|
124
|
+
tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(output, null, 2));
|
|
125
|
+
if (allPending.length === 0) {
|
|
126
|
+
devkit_1.logger.info("No pending migrations. trackunit-migrations.json updated.");
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
devkit_1.logger.info(`\nWrote ${allPending.length} pending migration(s) to ${TRACKUNIT_MIGRATIONS_FILE}`);
|
|
130
|
+
devkit_1.logger.info("Review the file, then run: nx g @trackunit/migrations:run-migrations");
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
exports.migrate = migrate;
|
|
134
|
+
exports.default = exports.migrate;
|
|
135
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = function noopMigration() {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runMigrations = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const discover_packages_1 = require("../utils/discover-packages");
|
|
8
|
+
/**
|
|
9
|
+
* Resolves a stored implementation path. Relative paths in
|
|
10
|
+
* `trackunit-migrations.json` are interpreted as relative to the
|
|
11
|
+
* workspace root (`process.cwd()`); absolute paths are used as-is.
|
|
12
|
+
* This keeps the on-disk file portable between checkouts while
|
|
13
|
+
* preserving back-compat with absolute paths.
|
|
14
|
+
*/
|
|
15
|
+
const resolveImplementationPath = (storedPath) => path_1.default.isAbsolute(storedPath) ? storedPath : path_1.default.resolve(process.cwd(), storedPath);
|
|
16
|
+
const TRACKUNIT_MIGRATIONS_FILE = "trackunit-migrations.json";
|
|
17
|
+
const runMigration = async (tree, migration, verbose) => {
|
|
18
|
+
try {
|
|
19
|
+
const resolvedPath = resolveImplementationPath(migration.implementation);
|
|
20
|
+
const migrationModule = await Promise.resolve(`${resolvedPath}`).then(s => tslib_1.__importStar(require(s)));
|
|
21
|
+
const migrationFn = migrationModule.default ?? migrationModule;
|
|
22
|
+
if (typeof migrationFn !== "function") {
|
|
23
|
+
devkit_1.logger.warn(` "${migration.name}" does not export a function, skipping`);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (verbose) {
|
|
27
|
+
devkit_1.logger.info(` Running: ${migration.name} (v${migration.version}) from ${migration.package}`);
|
|
28
|
+
}
|
|
29
|
+
migrationFn(tree);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
devkit_1.logger.error(` Failed "${migration.name}": ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Step 2: Reads trackunit-migrations.json and executes each pending
|
|
39
|
+
* migration. Successful migrations are removed from the pending list
|
|
40
|
+
* and bump `migratedVersions[package]` to their version. Failures
|
|
41
|
+
* stay in the list so they remain retryable on the next run without
|
|
42
|
+
* having to go through `migrate` again.
|
|
43
|
+
*
|
|
44
|
+
* Usage: nx g @trackunit/migrations:run-migrations
|
|
45
|
+
*/
|
|
46
|
+
const runMigrations = async (tree, options) => {
|
|
47
|
+
if (!tree.exists(TRACKUNIT_MIGRATIONS_FILE)) {
|
|
48
|
+
devkit_1.logger.error(`${TRACKUNIT_MIGRATIONS_FILE} not found. Run "nx g @trackunit/migrations:migrate" first.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const file = (0, devkit_1.readJson)(tree, TRACKUNIT_MIGRATIONS_FILE);
|
|
52
|
+
if (file.migrations.length === 0) {
|
|
53
|
+
devkit_1.logger.info("No pending migrations to run.");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
devkit_1.logger.info(`Running ${file.migrations.length} migration(s)...\n`);
|
|
57
|
+
let succeeded = 0;
|
|
58
|
+
const remaining = [];
|
|
59
|
+
const migratedVersions = { ...file.migratedVersions };
|
|
60
|
+
for (const migration of file.migrations) {
|
|
61
|
+
devkit_1.logger.info(`${migration.package}: ${migration.name} - ${migration.description}`);
|
|
62
|
+
const ok = await runMigration(tree, migration, options.verbose ?? false);
|
|
63
|
+
if (ok) {
|
|
64
|
+
succeeded++;
|
|
65
|
+
const current = migratedVersions[migration.package];
|
|
66
|
+
if (current === undefined || (0, discover_packages_1.compareVersions)(migration.version, current) > 0) {
|
|
67
|
+
migratedVersions[migration.package] = migration.version;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
remaining.push(migration);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const updated = {
|
|
75
|
+
migratedVersions,
|
|
76
|
+
migrations: remaining,
|
|
77
|
+
};
|
|
78
|
+
tree.write(TRACKUNIT_MIGRATIONS_FILE, JSON.stringify(updated, null, 2));
|
|
79
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
80
|
+
devkit_1.logger.info(`\n${succeeded} migration(s) applied successfully.`);
|
|
81
|
+
if (remaining.length > 0) {
|
|
82
|
+
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".`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
devkit_1.logger.info(`\nYou can delete ${TRACKUNIT_MIGRATIONS_FILE} or keep it for version tracking.`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
exports.runMigrations = runMigrations;
|
|
89
|
+
exports.default = exports.runMigrations;
|
|
90
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.discoverTrackunitPackages = exports.discoverWorkspaceTrackunitPackages = exports.compareVersions = exports.loadMigrationsJson = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const readPackageJson = (pkgJsonPath) => {
|
|
8
|
+
try {
|
|
9
|
+
const raw = (0, fs_1.readFileSync)(pkgJsonPath, "utf-8");
|
|
10
|
+
return JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const resolveMigrationsPath = (pkgDir, pkgJson) => {
|
|
17
|
+
const migrations = pkgJson.migrations;
|
|
18
|
+
if (migrations === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
const migrationsFile = typeof migrations === "string" ? migrations : migrations.migrations;
|
|
21
|
+
const resolved = path_1.default.resolve(pkgDir, migrationsFile);
|
|
22
|
+
return (0, fs_1.existsSync)(resolved) ? resolved : undefined;
|
|
23
|
+
};
|
|
24
|
+
/** Compares two semver strings, returning negative/zero/positive like Array.sort comparators. */
|
|
25
|
+
/** Loads and parses a migrations.json file from disk. */
|
|
26
|
+
const loadMigrationsJson = (migrationsPath) => {
|
|
27
|
+
try {
|
|
28
|
+
const raw = (0, fs_1.readFileSync)(migrationsPath, "utf-8");
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
exports.loadMigrationsJson = loadMigrationsJson;
|
|
36
|
+
const semver_1 = require("semver");
|
|
37
|
+
/**
|
|
38
|
+
* Compare two semver strings, returning negative/zero/positive like Array.sort comparators.
|
|
39
|
+
*
|
|
40
|
+
* @param a - The first semver string to compare.
|
|
41
|
+
* @param b - The second semver string to compare.
|
|
42
|
+
* @returns {number} A negative number if a < b, 0 if a === b, and a positive number if a > b.
|
|
43
|
+
*/
|
|
44
|
+
const compareVersions = (a, b) => (0, semver_1.compare)(a, b);
|
|
45
|
+
exports.compareVersions = compareVersions;
|
|
46
|
+
const scanScopeDir = (scopeDir) => {
|
|
47
|
+
if (!(0, fs_1.existsSync)(scopeDir))
|
|
48
|
+
return [];
|
|
49
|
+
const entries = (0, fs_1.readdirSync)(scopeDir, { withFileTypes: true });
|
|
50
|
+
const found = [];
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
53
|
+
continue;
|
|
54
|
+
const pkgDir = path_1.default.join(scopeDir, entry.name);
|
|
55
|
+
const pkgJsonPath = path_1.default.join(pkgDir, "package.json");
|
|
56
|
+
const pkgJson = readPackageJson(pkgJsonPath);
|
|
57
|
+
if (!pkgJson)
|
|
58
|
+
continue;
|
|
59
|
+
const migrationsJsonPath = resolveMigrationsPath(pkgDir, pkgJson);
|
|
60
|
+
if (migrationsJsonPath) {
|
|
61
|
+
found.push({
|
|
62
|
+
name: pkgJson.name ?? `@trackunit/${entry.name}`,
|
|
63
|
+
version: pkgJson.version,
|
|
64
|
+
migrationsJsonPath,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return found;
|
|
69
|
+
};
|
|
70
|
+
const MANAGER_REPO_PACKAGE_NAME = "@trackunit/manager";
|
|
71
|
+
const WORKSPACE_SCAN_ROOTS = ["libs", "apps"];
|
|
72
|
+
const isManagerMonorepo = (cwd) => {
|
|
73
|
+
const rootPkgJson = readPackageJson(path_1.default.join(cwd, "package.json"));
|
|
74
|
+
return rootPkgJson?.name === MANAGER_REPO_PACKAGE_NAME;
|
|
75
|
+
};
|
|
76
|
+
const walkForPackageJsons = (rootDir, onPackageJson) => {
|
|
77
|
+
if (!(0, fs_1.existsSync)(rootDir))
|
|
78
|
+
return;
|
|
79
|
+
const stack = [rootDir];
|
|
80
|
+
while (stack.length > 0) {
|
|
81
|
+
const dir = stack.pop();
|
|
82
|
+
if (dir === undefined)
|
|
83
|
+
continue;
|
|
84
|
+
let entries;
|
|
85
|
+
try {
|
|
86
|
+
entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
93
|
+
continue;
|
|
94
|
+
const entryPath = path_1.default.join(dir, entry.name);
|
|
95
|
+
if (entry.isDirectory()) {
|
|
96
|
+
stack.push(entryPath);
|
|
97
|
+
}
|
|
98
|
+
else if (entry.isFile() && entry.name === "package.json") {
|
|
99
|
+
onPackageJson(entryPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* In-monorepo fallback discovery: scans libs/ and apps/ for
|
|
106
|
+
* package.json files whose name starts with "@trackunit/" and that
|
|
107
|
+
* declare a "migrations" field. Used only when the engine is run
|
|
108
|
+
* inside the manager monorepo (root package.json name is
|
|
109
|
+
* "@trackunit/manager"), since internal workspace projects are not
|
|
110
|
+
* symlinked under node_modules/@trackunit/* by Yarn.
|
|
111
|
+
*/
|
|
112
|
+
const discoverWorkspaceTrackunitPackages = (cwd = process.cwd()) => {
|
|
113
|
+
const found = [];
|
|
114
|
+
for (const root of WORKSPACE_SCAN_ROOTS) {
|
|
115
|
+
walkForPackageJsons(path_1.default.join(cwd, root), pkgJsonPath => {
|
|
116
|
+
const pkgJson = readPackageJson(pkgJsonPath);
|
|
117
|
+
if (!pkgJson?.name || !pkgJson.name.startsWith("@trackunit/"))
|
|
118
|
+
return;
|
|
119
|
+
const migrationsJsonPath = resolveMigrationsPath(path_1.default.dirname(pkgJsonPath), pkgJson);
|
|
120
|
+
if (!migrationsJsonPath)
|
|
121
|
+
return;
|
|
122
|
+
found.push({
|
|
123
|
+
name: pkgJson.name,
|
|
124
|
+
version: pkgJson.version,
|
|
125
|
+
migrationsJsonPath,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return found;
|
|
130
|
+
};
|
|
131
|
+
exports.discoverWorkspaceTrackunitPackages = discoverWorkspaceTrackunitPackages;
|
|
132
|
+
/**
|
|
133
|
+
* Recursively walks node_modules directories looking for @trackunit/*
|
|
134
|
+
* packages that declare a "migrations" field. Scans root node_modules
|
|
135
|
+
* and nested node_modules inside every package. When the same package
|
|
136
|
+
* is found at multiple levels, the highest semver version is kept.
|
|
137
|
+
*
|
|
138
|
+
* Works with all package managers (npm, yarn, pnpm, bun).
|
|
139
|
+
*
|
|
140
|
+
* When invoked inside the manager monorepo (root package.json name
|
|
141
|
+
* is "@trackunit/manager"), additionally scans libs/ and apps/ for
|
|
142
|
+
* workspace packages that ship migrations, since those are not
|
|
143
|
+
* linked under node_modules/@trackunit/.
|
|
144
|
+
*/
|
|
145
|
+
const discoverTrackunitPackages = () => {
|
|
146
|
+
const cwd = process.cwd();
|
|
147
|
+
const rootNodeModules = path_1.default.resolve(cwd, "node_modules");
|
|
148
|
+
const allFound = [];
|
|
149
|
+
const visited = new Set();
|
|
150
|
+
const walkNodeModules = (nodeModulesDir) => {
|
|
151
|
+
if (!(0, fs_1.existsSync)(nodeModulesDir))
|
|
152
|
+
return;
|
|
153
|
+
const canonical = path_1.default.resolve(nodeModulesDir);
|
|
154
|
+
if (visited.has(canonical))
|
|
155
|
+
return;
|
|
156
|
+
visited.add(canonical);
|
|
157
|
+
const trackunitScope = path_1.default.join(nodeModulesDir, "@trackunit");
|
|
158
|
+
allFound.push(...scanScopeDir(trackunitScope));
|
|
159
|
+
const topEntries = (0, fs_1.readdirSync)(nodeModulesDir, {
|
|
160
|
+
withFileTypes: true,
|
|
161
|
+
});
|
|
162
|
+
for (const entry of topEntries) {
|
|
163
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
164
|
+
continue;
|
|
165
|
+
const entryPath = path_1.default.join(nodeModulesDir, entry.name);
|
|
166
|
+
if (entry.name.startsWith("@")) {
|
|
167
|
+
const scopeEntries = (0, fs_1.readdirSync)(entryPath, {
|
|
168
|
+
withFileTypes: true,
|
|
169
|
+
});
|
|
170
|
+
for (const scopeEntry of scopeEntries) {
|
|
171
|
+
if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink())
|
|
172
|
+
continue;
|
|
173
|
+
walkNodeModules(path_1.default.join(entryPath, scopeEntry.name, "node_modules"));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
walkNodeModules(path_1.default.join(entryPath, "node_modules"));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
walkNodeModules(rootNodeModules);
|
|
182
|
+
if (isManagerMonorepo(cwd)) {
|
|
183
|
+
allFound.push(...(0, exports.discoverWorkspaceTrackunitPackages)(cwd));
|
|
184
|
+
}
|
|
185
|
+
const byName = new Map();
|
|
186
|
+
for (const pkg of allFound) {
|
|
187
|
+
const existing = byName.get(pkg.name);
|
|
188
|
+
if (!existing || (0, exports.compareVersions)(pkg.version, existing.version) > 0) {
|
|
189
|
+
byName.set(pkg.name, pkg);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return [...byName.values()];
|
|
193
|
+
};
|
|
194
|
+
exports.discoverTrackunitPackages = discoverTrackunitPackages;
|
|
195
|
+
//# sourceMappingURL=discover-packages.js.map
|
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runMigrations = exports.migrate = void 0;
|
|
4
|
+
var generator_1 = require("./generators/migrate/generator");
|
|
5
|
+
Object.defineProperty(exports, "migrate", { enumerable: true, get: function () { return generator_1.migrate; } });
|
|
6
|
+
var generator_2 = require("./generators/run-migrations/generator");
|
|
7
|
+
Object.defineProperty(exports, "runMigrations", { enumerable: true, get: function () { return generator_2.runMigrations; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|