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