@_linked/cli 1.41.1 → 1.43.0
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 +29 -0
- package/README.md +21 -54
- package/lib/esm/cli-methods.js +2 -2
- package/lib/esm/cli-methods.js.map +1 -1
- package/lib/esm/cli.js +13 -1
- package/lib/esm/cli.js.map +1 -1
- package/lib/esm/commands/localize.d.ts +6 -15
- package/lib/esm/commands/localize.d.ts.map +1 -1
- package/lib/esm/commands/localize.js +26 -35
- package/lib/esm/commands/localize.js.map +1 -1
- package/lib/esm/commands/setup-publish.d.ts +26 -1
- package/lib/esm/commands/setup-publish.d.ts.map +1 -1
- package/lib/esm/commands/setup-publish.js +107 -9
- package/lib/esm/commands/setup-publish.js.map +1 -1
- package/lib/esm/installed-packages.d.ts +12 -12
- package/lib/esm/installed-packages.d.ts.map +1 -1
- package/lib/esm/installed-packages.js +37 -27
- package/lib/esm/installed-packages.js.map +1 -1
- package/lib/esm/lifecycle.d.ts +4 -3
- package/lib/esm/lifecycle.d.ts.map +1 -1
- package/lib/esm/lifecycle.js +6 -14
- package/lib/esm/lifecycle.js.map +1 -1
- package/lib/esm/localize/adopt.d.ts +10 -0
- package/lib/esm/localize/adopt.d.ts.map +1 -0
- package/lib/esm/localize/adopt.js +76 -0
- package/lib/esm/localize/adopt.js.map +1 -0
- package/lib/esm/localize/delocalize.d.ts +6 -0
- package/lib/esm/localize/delocalize.d.ts.map +1 -0
- package/lib/esm/localize/delocalize.js +138 -0
- package/lib/esm/localize/delocalize.js.map +1 -0
- package/lib/esm/localize/ensure.d.ts +26 -0
- package/lib/esm/localize/ensure.d.ts.map +1 -0
- package/lib/esm/localize/ensure.js +152 -0
- package/lib/esm/localize/ensure.js.map +1 -0
- package/lib/esm/localize/errors.d.ts +23 -0
- package/lib/esm/localize/errors.d.ts.map +1 -0
- package/lib/esm/localize/errors.js +26 -0
- package/lib/esm/localize/errors.js.map +1 -0
- package/lib/esm/localize/fsops.d.ts +36 -0
- package/lib/esm/localize/fsops.d.ts.map +1 -0
- package/lib/esm/localize/fsops.js +155 -0
- package/lib/esm/localize/fsops.js.map +1 -0
- package/lib/esm/localize/index.d.ts +29 -0
- package/lib/esm/localize/index.d.ts.map +1 -0
- package/lib/esm/localize/index.js +22 -0
- package/lib/esm/localize/index.js.map +1 -0
- package/lib/esm/localize/list.d.ts +18 -0
- package/lib/esm/localize/list.d.ts.map +1 -0
- package/lib/esm/localize/list.js +174 -0
- package/lib/esm/localize/list.js.map +1 -0
- package/lib/esm/localize/localize.d.ts +84 -0
- package/lib/esm/localize/localize.d.ts.map +1 -0
- package/lib/esm/localize/localize.js +354 -0
- package/lib/esm/localize/localize.js.map +1 -0
- package/lib/esm/localize/manifest.d.ts +54 -0
- package/lib/esm/localize/manifest.d.ts.map +1 -0
- package/lib/esm/localize/manifest.js +133 -0
- package/lib/esm/localize/manifest.js.map +1 -0
- package/lib/esm/localize/provided.d.ts +28 -0
- package/lib/esm/localize/provided.d.ts.map +1 -0
- package/lib/esm/localize/provided.js +89 -0
- package/lib/esm/localize/provided.js.map +1 -0
- package/lib/esm/localize/prune.d.ts +58 -0
- package/lib/esm/localize/prune.d.ts.map +1 -0
- package/lib/esm/localize/prune.js +390 -0
- package/lib/esm/localize/prune.js.map +1 -0
- package/lib/esm/localize/relink.d.ts +11 -0
- package/lib/esm/localize/relink.d.ts.map +1 -0
- package/lib/esm/localize/relink.js +174 -0
- package/lib/esm/localize/relink.js.map +1 -0
- package/lib/esm/localize/resolve.d.ts +47 -0
- package/lib/esm/localize/resolve.d.ts.map +1 -0
- package/lib/esm/localize/resolve.js +153 -0
- package/lib/esm/localize/resolve.js.map +1 -0
- package/lib/esm/localize/run.d.ts +35 -0
- package/lib/esm/localize/run.d.ts.map +1 -0
- package/lib/esm/localize/run.js +30 -0
- package/lib/esm/localize/run.js.map +1 -0
- package/lib/esm/localized-dedupe.d.ts +6 -0
- package/lib/esm/localized-dedupe.d.ts.map +1 -1
- package/lib/esm/localized-dedupe.js +23 -66
- package/lib/esm/localized-dedupe.js.map +1 -1
- package/package.json +1 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relink.js","sourceRoot":"","sources":["../../../src/localize/relink.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `--relink` -- the `postinstall` hook. Recreate every recorded symlink after\n * an install removed it (`npm ci` deletes `node_modules` and exits 0).\n *\n * Three rules, each of which is the difference between a useful hook and one\n * everybody disables:\n *\n * - **It always exits 0.** A postinstall that fails an install because of a\n * developer's private state is worse than the problem it solves.\n * - **With no manifest it produces NO output at all.** That is the CI case and\n * the fresh-clone case. Any output there and people learn to ignore this\n * hook's output, which is the one thing it can never become.\n * - **It never runs npm at the consumer root.** A postinstall that installs\n * the root, recurses. A test asserts it.\n *\n * It DOES run `npm install` inside a checkout whose dependencies are gone, and\n * only then. Measured on npm 11: a root `npm install` treats a linked checkout\n * that sits under the root as part of the root's tree, marks its dependencies\n * `extraneous` and prunes them -- for a localized published dependency as\n * much as for an undeclared one. Putting the link back without the\n * dependencies leaves a checkout that cannot build. That install runs in the\n * checkout, never at the root, so it cannot re-enter this hook; and it runs\n * only when a declared dependency is missing, so an ordinary install stays\n * silent and fast.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {readManifest} from './manifest.js';\nimport {isSymlink, readJson, writeLink} from './fsops.js';\nimport {declaredRange} from './localize.js';\nimport {\n candidatesFor,\n isProvidedByApp,\n pruneProvided,\n shouldPrune,\n} from './prune.js';\nimport {ensure} from './ensure.js';\nimport type {PruneOptions} from './prune.js';\nimport type {Deps} from './run.js';\n\n/**\n * @param {object} deps\n * @param {{prune?: boolean}} [opts] unless `prune` is\n * false, every checkout's own copies of what the app provides are removed\n * after relinking (see prune.ts), and a dependency missing from a checkout\n * because it was pruned is not mistaken for one a root install took.\n */\nexport function relink(deps: Deps, opts: PruneOptions = {}): number {\n let manifest;\n try {\n manifest = readManifest(deps.appRoot, deps);\n } catch (e) {\n // Even a refusal is only a warning here: never fail an install.\n deps.warn(`[localize] ${e.message}`);\n return 0;\n }\n\n if (!manifest.exists) return 0;\n const names = Object.keys(manifest.entries);\n if (!names.length) return 0;\n\n let relinked = 0;\n for (const name of names) {\n const entry = manifest.entries[name];\n const checkout = path.join(deps.appRoot, entry.path);\n\n if (!fs.existsSync(checkout)) {\n deps.warn(\n `[localize] ${name} is recorded as local but ${entry.path} is gone — using the registry ` +\n `copy. ` +\n (entry.repo\n ? `\\`linked localize ${name}\\` to re-clone.`\n : `It has no recorded repository, so restore the checkout by hand, then ` +\n `\\`linked localize ${name} --adopt\\`.`),\n );\n continue;\n }\n\n reinstallIfPruned(name, entry, manifest.entries, opts, deps);\n\n if (isSymlink(path.join(deps.appRoot, 'node_modules', name))) {\n // Already linked; rewriting it would be noise on every install.\n const existing = fs.realpathSync(\n path.join(deps.appRoot, 'node_modules', name),\n );\n if (existing === fs.realpathSync(checkout)) continue;\n }\n\n writeLink(deps.appRoot, name, checkout);\n relinked++;\n deps.log(\n `[localize] relinked ${name} -> ${entry.path} (branch ${entry.branch})`,\n );\n warnRangeDrift(name, entry, checkout, deps);\n }\n\n if (relinked) {\n deps.log(\n `[localize] ${relinked} local package${relinked === 1 ? '' : 's'} relinked. ` +\n '`linked localize --list` for detail.',\n );\n }\n\n if (shouldPrune(opts)) {\n try {\n pruneProvided(manifest.entries, opts, deps);\n } catch (e) {\n // Never fail an install over it.\n deps.warn(`[localize] pruning failed: ${e.message}`);\n }\n }\n return 0;\n}\n\n/**\n * Restore a checkout's own `node_modules` when a root install pruned it: any\n * declared dependency (or devDependency -- the checkout's build needs those)\n * missing from it. A failure warns; the hook still exits 0.\n */\nfunction reinstallIfPruned(name, entry, entries, opts, deps) {\n const checkout = path.join(deps.appRoot, entry.path);\n // When pruning, a dependency the app provides is absent on purpose.\n const candidates = shouldPrune(opts) ? candidatesFor(entries, deps) : null;\n const provided = candidates\n ? (dep, range) => isProvidedByApp(dep, range, entries, candidates, deps)\n : () => false;\n const missing = missingDependencies(\n checkout,\n cloneRootOf(entry, deps),\n provided,\n );\n if (!missing.length) return;\n deps.log(\n `[localize] ${name}: ${missing.length} of its dependencies are gone from ${entry.path}/node_modules ` +\n `(a root install prunes a checkout under the root) — reinstalling it`,\n );\n try {\n ensure(entry, deps, opts);\n } catch (e) {\n deps.warn(\n `[localize] ${name}: ${e.message}\\n` +\n `Retry with \\`linked localize --reinstall ${name}\\`.`,\n );\n }\n}\n\n/**\n * Declared dependency names found in neither the checkout's own `node_modules`\n * nor, for a package inside a monorepo, the clone root's -- where a workspaces\n * install hoists them. Checking only the package directory there would find\n * every hoisted dependency \"missing\" and reinstall on every postinstall.\n */\nfunction missingDependencies(\n checkout: string,\n cloneRoot: string,\n provided: (dep: string, range: string, pkg: any) => boolean = () => false,\n): string[] {\n const pkg = readJson(path.join(checkout, 'package.json'));\n if (!pkg) return [];\n const declared = {...pkg.devDependencies, ...pkg.dependencies};\n const roots = cloneRoot === checkout ? [checkout] : [checkout, cloneRoot];\n return Object.keys(declared).filter(\n (dep) =>\n !roots.some((root) =>\n fs.existsSync(path.join(root, 'node_modules', dep)),\n ) && !provided(dep, declared[dep], pkg),\n );\n}\n\n/** The clone a `subdir` entry lives in: its path with the subdir taken off the end. */\nfunction cloneRootOf(entry, deps) {\n const pkgDir = path.join(deps.appRoot, entry.path);\n if (!entry.subdir) return pkgDir;\n const rel = path.normalize(entry.subdir);\n return pkgDir.endsWith(path.sep + rel)\n ? pkgDir.slice(0, -(rel.length + 1))\n : pkgDir;\n}\n\n/**\n * npm matches a link by NAME and never checks the range, so a checkout at\n * 2.0.99 silently satisfies `^3.0.0` and `npm ls` does not flag it. This warns\n * and never errors, and the range is never used to decide whether a checkout\n * is usable -- only to say out loud that the declared range is now fiction.\n */\nfunction warnRangeDrift(name, entry, checkout, deps) {\n const declared = declaredRange(name, deps);\n const version = readJson(path.join(checkout, 'package.json'))?.version;\n if (!declared || !version) return;\n if (\n sameMajor(version, declared) &&\n (!entry.range || entry.range === declared)\n )\n return;\n deps.warn(\n `[localize] ${name}: checkout is ${version}, package.json asks ${declared}` +\n (entry.range ? ` (recorded ${entry.range})` : '') +\n ` — the symlink wins; npm does NOT check the range.`,\n );\n}\n\n/**\n * Deliberately crude: enough to spot obvious drift for a warning, never used\n * to gate anything. Real semver (`satisfies` in prune.ts) here would invite\n * someone to treat the answer as a constraint.\n */\nfunction sameMajor(version, range) {\n const m = /^[\\^~]?(\\d+)\\./.exec(range);\n if (!m) return true;\n return version.split('.')[0] === m[1];\n}\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Deps } from './run.js';
|
|
2
|
+
export interface Resolved {
|
|
3
|
+
/** The package name, as it is imported and as `node_modules/<npmName>` is spelled. */
|
|
4
|
+
npmName: string;
|
|
5
|
+
/** A git URL that `git clone` accepts. */
|
|
6
|
+
repo: string;
|
|
7
|
+
/** Directory name for the clone. */
|
|
8
|
+
checkoutName: string;
|
|
9
|
+
/** Package location inside the clone, when the repository holds more than this one package. */
|
|
10
|
+
subdir?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} Resolved
|
|
14
|
+
* @property {string} npmName the package name, as it is imported and as
|
|
15
|
+
* `node_modules/<npmName>` is spelled
|
|
16
|
+
* @property {string} repo a git URL that `git clone` accepts
|
|
17
|
+
* @property {string} checkoutName directory name for the clone
|
|
18
|
+
* @property {string} [subdir] package location inside the clone, when the
|
|
19
|
+
* repository holds more than this one package
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} name a full npm package name (`lodash`, `@scope/thing`)
|
|
23
|
+
* @param {object} deps
|
|
24
|
+
* @param {object} [override] `{repo, subdir}` from `--repo`/`--subdir` or from
|
|
25
|
+
* a manifest entry recorded by an earlier run
|
|
26
|
+
* @returns {Resolved}
|
|
27
|
+
* @throws {LocalizeError} code 4 when the registry has no usable `repository`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolvePackage(name: string, deps: Pick<Deps, 'run'>, override?: {
|
|
30
|
+
repo?: string;
|
|
31
|
+
subdir?: string;
|
|
32
|
+
}): Resolved;
|
|
33
|
+
/**
|
|
34
|
+
* npm accepts several spellings in `repository.url`; `git clone` accepts fewer.
|
|
35
|
+
*
|
|
36
|
+
* Handled: the `git+` prefix npm recommends, the `git://` scheme many older
|
|
37
|
+
* packages still carry, and the `github:owner/repo` / bare `owner/repo`
|
|
38
|
+
* shorthands npm normalises on publish but not always in a package's source.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizeGitUrl(raw: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Directory name for the clone. The scope is flattened rather than dropped, so
|
|
43
|
+
* `@a/thing` and `@b/thing` can both be localized in one tree -- dropping it
|
|
44
|
+
* would make the second clone silently reuse the first.
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkoutNameFor(npmName: string): string;
|
|
47
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/localize/resolve.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,UAAU,CAAC;AAEnC,MAAM,WAAW,QAAQ;IACvB,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,EACvB,QAAQ,CAAC,EAAE;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAC,GAC1C,QAAQ,CAmBV;AAyED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWnD;AAkBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIvD"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm package name -> git repository.
|
|
3
|
+
*
|
|
4
|
+
* There is exactly ONE resolution path, and keeping it to one is the design:
|
|
5
|
+
*
|
|
6
|
+
* npm name -> registry metadata -> `repository` (+ `directory`) -> clone
|
|
7
|
+
*
|
|
8
|
+
* No org probing, no name guessing, no mapping table, no pluggable resolver.
|
|
9
|
+
* The `repository` field is data the package already published, so it is the
|
|
10
|
+
* authoritative answer rather than an inference -- and an inference that is
|
|
11
|
+
* wrong here costs a clone and a full install of the wrong repository.
|
|
12
|
+
*
|
|
13
|
+
* A consumer that wants to accept short names, or to search several GitHub
|
|
14
|
+
* orgs, resolves that to a full package name ITSELF and then calls localize.
|
|
15
|
+
* That behaviour is specific to whoever has it, and localize deliberately
|
|
16
|
+
* offers no seam for it.
|
|
17
|
+
*
|
|
18
|
+
* Metadata is read with `npm view`, not with an HTTP request to
|
|
19
|
+
* registry.npmjs.org, so a private registry, a scoped registry, a proxy and an
|
|
20
|
+
* auth token all work without localize knowing any of them exist.
|
|
21
|
+
*/
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { EXIT_NOT_FOUND, LocalizeError } from './errors.js';
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} Resolved
|
|
26
|
+
* @property {string} npmName the package name, as it is imported and as
|
|
27
|
+
* `node_modules/<npmName>` is spelled
|
|
28
|
+
* @property {string} repo a git URL that `git clone` accepts
|
|
29
|
+
* @property {string} checkoutName directory name for the clone
|
|
30
|
+
* @property {string} [subdir] package location inside the clone, when the
|
|
31
|
+
* repository holds more than this one package
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} name a full npm package name (`lodash`, `@scope/thing`)
|
|
35
|
+
* @param {object} deps
|
|
36
|
+
* @param {object} [override] `{repo, subdir}` from `--repo`/`--subdir` or from
|
|
37
|
+
* a manifest entry recorded by an earlier run
|
|
38
|
+
* @returns {Resolved}
|
|
39
|
+
* @throws {LocalizeError} code 4 when the registry has no usable `repository`.
|
|
40
|
+
*/
|
|
41
|
+
export function resolvePackage(name, deps, override) {
|
|
42
|
+
if (override === null || override === void 0 ? void 0 : override.repo) {
|
|
43
|
+
return Object.assign({ npmName: name, repo: override.repo, checkoutName: checkoutNameFor(name) }, (override.subdir ? { subdir: normalizeSubdir(override.subdir) } : {}));
|
|
44
|
+
}
|
|
45
|
+
const meta = fetchMetadata(name, deps);
|
|
46
|
+
const { url, directory } = readRepositoryField(meta, name);
|
|
47
|
+
return Object.assign({ npmName: name, repo: url, checkoutName: checkoutNameFor(name) }, (directory ? { subdir: directory } : {}));
|
|
48
|
+
}
|
|
49
|
+
/** Ask npm for the published metadata. `npm view` inherits the user's registry config. */
|
|
50
|
+
function fetchMetadata(name, deps) {
|
|
51
|
+
const r = deps.run('npm', ['view', name, 'repository', 'name', '--json']);
|
|
52
|
+
if (r.status !== 0) {
|
|
53
|
+
throw new LocalizeError(`cannot read registry metadata for "${name}":\n${(r.stderr || r.stdout).trim()}\n` +
|
|
54
|
+
`localize resolves a repository only from the published package. If the package is not ` +
|
|
55
|
+
`published, or you are not authenticated for its registry, pass the repository ` +
|
|
56
|
+
`explicitly: linked localize ${name} --repo <git-url>`, EXIT_NOT_FOUND);
|
|
57
|
+
}
|
|
58
|
+
// A published package with no `repository` field at all: npm exits 0 and
|
|
59
|
+
// prints nothing. That is "missing", not "malformed" -- let the caller's
|
|
60
|
+
// missing-field refusal handle it, since it is the one that names the fix.
|
|
61
|
+
if (r.stdout.trim() === '')
|
|
62
|
+
return null;
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(r.stdout);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
throw new LocalizeError(`npm returned metadata for "${name}" that is not JSON (${e.message}).`, EXIT_NOT_FOUND);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Pull the repository out of the metadata and normalise it to something
|
|
72
|
+
* `git clone` accepts.
|
|
73
|
+
*
|
|
74
|
+
* Refuses rather than guesses when the field is absent. Plenty of published
|
|
75
|
+
* packages have no `repository` at all, and there is no honest way to find
|
|
76
|
+
* their source from the registry -- so the answer is an explicit override, not
|
|
77
|
+
* a search.
|
|
78
|
+
*/
|
|
79
|
+
function readRepositoryField(meta, name) {
|
|
80
|
+
// `npm view <name> <field> --json` returns the bare field value; with several
|
|
81
|
+
// fields it returns an object keyed by field name. Accept either, and accept
|
|
82
|
+
// the array npm returns when several versions match.
|
|
83
|
+
let node = meta;
|
|
84
|
+
if (Array.isArray(node))
|
|
85
|
+
node = node[0];
|
|
86
|
+
if (node && typeof node === 'object' && 'repository' in node)
|
|
87
|
+
node = node.repository;
|
|
88
|
+
const missing = () => new LocalizeError(`"${name}" publishes no usable "repository" field, so there is nothing to clone.\n` +
|
|
89
|
+
`localize never guesses at a repository URL. Pass it once:\n` +
|
|
90
|
+
` linked localize ${name} --repo <git-url> [--subdir <path-inside-the-repo>]\n` +
|
|
91
|
+
`It is recorded in local-packages.json, so you only pass it the first time.`, EXIT_NOT_FOUND);
|
|
92
|
+
if (!node)
|
|
93
|
+
throw missing();
|
|
94
|
+
let raw;
|
|
95
|
+
let directory;
|
|
96
|
+
if (typeof node === 'string') {
|
|
97
|
+
raw = node;
|
|
98
|
+
}
|
|
99
|
+
else if (typeof node === 'object') {
|
|
100
|
+
raw = typeof node.url === 'string' ? node.url : undefined;
|
|
101
|
+
directory = typeof node.directory === 'string' ? node.directory : undefined;
|
|
102
|
+
}
|
|
103
|
+
if (!raw)
|
|
104
|
+
throw missing();
|
|
105
|
+
return {
|
|
106
|
+
url: normalizeGitUrl(raw),
|
|
107
|
+
directory: directory ? normalizeSubdir(directory) : undefined,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* npm accepts several spellings in `repository.url`; `git clone` accepts fewer.
|
|
112
|
+
*
|
|
113
|
+
* Handled: the `git+` prefix npm recommends, the `git://` scheme many older
|
|
114
|
+
* packages still carry, and the `github:owner/repo` / bare `owner/repo`
|
|
115
|
+
* shorthands npm normalises on publish but not always in a package's source.
|
|
116
|
+
*/
|
|
117
|
+
export function normalizeGitUrl(raw) {
|
|
118
|
+
let url = raw.trim();
|
|
119
|
+
if (url.startsWith('git+'))
|
|
120
|
+
url = url.slice(4);
|
|
121
|
+
if (url.endsWith('.git/'))
|
|
122
|
+
url = url.slice(0, -1);
|
|
123
|
+
const shorthand = /^(?:github:)?([\w.-]+)\/([\w.-]+)$/.exec(url);
|
|
124
|
+
if (shorthand)
|
|
125
|
+
return `https://github.com/${shorthand[1]}/${shorthand[2]}.git`;
|
|
126
|
+
if (url.startsWith('git://'))
|
|
127
|
+
return 'https://' + url.slice('git://'.length);
|
|
128
|
+
return url;
|
|
129
|
+
}
|
|
130
|
+
/** A repo-relative POSIX path with no leading `./` and no escape. */
|
|
131
|
+
function normalizeSubdir(dir) {
|
|
132
|
+
const clean = path.posix
|
|
133
|
+
.normalize(dir.replace(/\\/g, '/'))
|
|
134
|
+
.replace(/^\.\//, '')
|
|
135
|
+
.replace(/\/+$/, '');
|
|
136
|
+
if (clean === '.' || clean === '')
|
|
137
|
+
return undefined;
|
|
138
|
+
if (clean.startsWith('..') || path.posix.isAbsolute(clean)) {
|
|
139
|
+
throw new LocalizeError(`the package directory "${dir}" points outside the repository. Refusing to use it.`, EXIT_NOT_FOUND);
|
|
140
|
+
}
|
|
141
|
+
return clean;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Directory name for the clone. The scope is flattened rather than dropped, so
|
|
145
|
+
* `@a/thing` and `@b/thing` can both be localized in one tree -- dropping it
|
|
146
|
+
* would make the second clone silently reuse the first.
|
|
147
|
+
*/
|
|
148
|
+
export function checkoutNameFor(npmName) {
|
|
149
|
+
return npmName.startsWith('@')
|
|
150
|
+
? npmName.slice(1).replace(/\//g, '-')
|
|
151
|
+
: npmName;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/localize/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * npm package name -> git repository.\n *\n * There is exactly ONE resolution path, and keeping it to one is the design:\n *\n * npm name -> registry metadata -> `repository` (+ `directory`) -> clone\n *\n * No org probing, no name guessing, no mapping table, no pluggable resolver.\n * The `repository` field is data the package already published, so it is the\n * authoritative answer rather than an inference -- and an inference that is\n * wrong here costs a clone and a full install of the wrong repository.\n *\n * A consumer that wants to accept short names, or to search several GitHub\n * orgs, resolves that to a full package name ITSELF and then calls localize.\n * That behaviour is specific to whoever has it, and localize deliberately\n * offers no seam for it.\n *\n * Metadata is read with `npm view`, not with an HTTP request to\n * registry.npmjs.org, so a private registry, a scoped registry, a proxy and an\n * auth token all work without localize knowing any of them exist.\n */\nimport path from 'node:path';\n\nimport {EXIT_NOT_FOUND, LocalizeError} from './errors.js';\nimport type {Deps} from './run.js';\n\nexport interface Resolved {\n /** The package name, as it is imported and as `node_modules/<npmName>` is spelled. */\n npmName: string;\n /** A git URL that `git clone` accepts. */\n repo: string;\n /** Directory name for the clone. */\n checkoutName: string;\n /** Package location inside the clone, when the repository holds more than this one package. */\n subdir?: string;\n}\n\n/**\n * @typedef {object} Resolved\n * @property {string} npmName the package name, as it is imported and as\n * `node_modules/<npmName>` is spelled\n * @property {string} repo a git URL that `git clone` accepts\n * @property {string} checkoutName directory name for the clone\n * @property {string} [subdir] package location inside the clone, when the\n * repository holds more than this one package\n */\n\n/**\n * @param {string} name a full npm package name (`lodash`, `@scope/thing`)\n * @param {object} deps\n * @param {object} [override] `{repo, subdir}` from `--repo`/`--subdir` or from\n * a manifest entry recorded by an earlier run\n * @returns {Resolved}\n * @throws {LocalizeError} code 4 when the registry has no usable `repository`.\n */\nexport function resolvePackage(\n name: string,\n deps: Pick<Deps, 'run'>,\n override?: {repo?: string; subdir?: string},\n): Resolved {\n if (override?.repo) {\n return {\n npmName: name,\n repo: override.repo,\n checkoutName: checkoutNameFor(name),\n ...(override.subdir ? {subdir: normalizeSubdir(override.subdir)} : {}),\n };\n }\n\n const meta = fetchMetadata(name, deps);\n const {url, directory} = readRepositoryField(meta, name);\n\n return {\n npmName: name,\n repo: url,\n checkoutName: checkoutNameFor(name),\n ...(directory ? {subdir: directory} : {}),\n };\n}\n\n/** Ask npm for the published metadata. `npm view` inherits the user's registry config. */\nfunction fetchMetadata(name, deps) {\n const r = deps.run('npm', ['view', name, 'repository', 'name', '--json']);\n if (r.status !== 0) {\n throw new LocalizeError(\n `cannot read registry metadata for \"${name}\":\\n${(r.stderr || r.stdout).trim()}\\n` +\n `localize resolves a repository only from the published package. If the package is not ` +\n `published, or you are not authenticated for its registry, pass the repository ` +\n `explicitly: linked localize ${name} --repo <git-url>`,\n EXIT_NOT_FOUND,\n );\n }\n // A published package with no `repository` field at all: npm exits 0 and\n // prints nothing. That is \"missing\", not \"malformed\" -- let the caller's\n // missing-field refusal handle it, since it is the one that names the fix.\n if (r.stdout.trim() === '') return null;\n try {\n return JSON.parse(r.stdout);\n } catch (e) {\n throw new LocalizeError(\n `npm returned metadata for \"${name}\" that is not JSON (${e.message}).`,\n EXIT_NOT_FOUND,\n );\n }\n}\n\n/**\n * Pull the repository out of the metadata and normalise it to something\n * `git clone` accepts.\n *\n * Refuses rather than guesses when the field is absent. Plenty of published\n * packages have no `repository` at all, and there is no honest way to find\n * their source from the registry -- so the answer is an explicit override, not\n * a search.\n */\nfunction readRepositoryField(meta, name) {\n // `npm view <name> <field> --json` returns the bare field value; with several\n // fields it returns an object keyed by field name. Accept either, and accept\n // the array npm returns when several versions match.\n let node = meta;\n if (Array.isArray(node)) node = node[0];\n if (node && typeof node === 'object' && 'repository' in node)\n node = node.repository;\n\n const missing = () =>\n new LocalizeError(\n `\"${name}\" publishes no usable \"repository\" field, so there is nothing to clone.\\n` +\n `localize never guesses at a repository URL. Pass it once:\\n` +\n ` linked localize ${name} --repo <git-url> [--subdir <path-inside-the-repo>]\\n` +\n `It is recorded in local-packages.json, so you only pass it the first time.`,\n EXIT_NOT_FOUND,\n );\n\n if (!node) throw missing();\n\n let raw;\n let directory;\n if (typeof node === 'string') {\n raw = node;\n } else if (typeof node === 'object') {\n raw = typeof node.url === 'string' ? node.url : undefined;\n directory = typeof node.directory === 'string' ? node.directory : undefined;\n }\n if (!raw) throw missing();\n\n return {\n url: normalizeGitUrl(raw),\n directory: directory ? normalizeSubdir(directory) : undefined,\n };\n}\n\n/**\n * npm accepts several spellings in `repository.url`; `git clone` accepts fewer.\n *\n * Handled: the `git+` prefix npm recommends, the `git://` scheme many older\n * packages still carry, and the `github:owner/repo` / bare `owner/repo`\n * shorthands npm normalises on publish but not always in a package's source.\n */\nexport function normalizeGitUrl(raw: string): string {\n let url = raw.trim();\n if (url.startsWith('git+')) url = url.slice(4);\n if (url.endsWith('.git/')) url = url.slice(0, -1);\n\n const shorthand = /^(?:github:)?([\\w.-]+)\\/([\\w.-]+)$/.exec(url);\n if (shorthand)\n return `https://github.com/${shorthand[1]}/${shorthand[2]}.git`;\n\n if (url.startsWith('git://')) return 'https://' + url.slice('git://'.length);\n return url;\n}\n\n/** A repo-relative POSIX path with no leading `./` and no escape. */\nfunction normalizeSubdir(dir) {\n const clean = path.posix\n .normalize(dir.replace(/\\\\/g, '/'))\n .replace(/^\\.\\//, '')\n .replace(/\\/+$/, '');\n if (clean === '.' || clean === '') return undefined;\n if (clean.startsWith('..') || path.posix.isAbsolute(clean)) {\n throw new LocalizeError(\n `the package directory \"${dir}\" points outside the repository. Refusing to use it.`,\n EXIT_NOT_FOUND,\n );\n }\n return clean;\n}\n\n/**\n * Directory name for the clone. The scope is flattened rather than dropped, so\n * `@a/thing` and `@b/thing` can both be localized in one tree -- dropping it\n * would make the second clone silently reuse the first.\n */\nexport function checkoutNameFor(npmName: string): string {\n return npmName.startsWith('@')\n ? npmName.slice(1).replace(/\\//g, '-')\n : npmName;\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single seam through which localize reaches the outside world.
|
|
3
|
+
*
|
|
4
|
+
* Every subprocess -- `git`, `npm`, a configured build command -- goes through
|
|
5
|
+
* the `run` function on the deps object. Tests may replace it, but note that
|
|
6
|
+
* the real-localize test deliberately does NOT: a stubbed runner cannot show
|
|
7
|
+
* that `npm install` ran in the right directory, which is the one property
|
|
8
|
+
* this tool has to get right.
|
|
9
|
+
*/
|
|
10
|
+
export interface RunOptions {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
shell?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface RunResult {
|
|
16
|
+
status: number;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
}
|
|
20
|
+
export type Run = (cmd: string, args: string[], opts?: RunOptions) => RunResult;
|
|
21
|
+
/**
|
|
22
|
+
* Everything localize needs from the outside world. A public contract: callers
|
|
23
|
+
* pass their own to point localize at another root or capture its output.
|
|
24
|
+
*/
|
|
25
|
+
export interface Deps {
|
|
26
|
+
appRoot: string;
|
|
27
|
+
run: Run;
|
|
28
|
+
log: (message: string) => void;
|
|
29
|
+
warn: (message: string) => void;
|
|
30
|
+
error: (message: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export declare function makeRun(defaultCwd: string): Run;
|
|
33
|
+
/** Build the default dependency bundle for a consumer root. */
|
|
34
|
+
export declare function defaultDeps(appRoot?: string): Deps;
|
|
35
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/localize/run.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,KAAK,SAAS,CAAC;AAEhF;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC;IACT,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAe/C;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,OAAO,GAAE,MAAsB,GAAG,IAAI,CAQjE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
export function makeRun(defaultCwd) {
|
|
4
|
+
return function run(cmd, args, opts = {}) {
|
|
5
|
+
var _a, _b, _c, _d, _e;
|
|
6
|
+
const r = spawnSync(cmd, args, {
|
|
7
|
+
cwd: (_a = opts.cwd) !== null && _a !== void 0 ? _a : defaultCwd,
|
|
8
|
+
encoding: 'utf8',
|
|
9
|
+
env: (_b = opts.env) !== null && _b !== void 0 ? _b : process.env,
|
|
10
|
+
shell: (_c = opts.shell) !== null && _c !== void 0 ? _c : false,
|
|
11
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
status: r.status === null ? 1 : r.status,
|
|
15
|
+
stdout: (_d = r.stdout) !== null && _d !== void 0 ? _d : '',
|
|
16
|
+
stderr: (_e = r.stderr) !== null && _e !== void 0 ? _e : (r.error ? String(r.error.message) : ''),
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Build the default dependency bundle for a consumer root. */
|
|
21
|
+
export function defaultDeps(appRoot = process.cwd()) {
|
|
22
|
+
return {
|
|
23
|
+
appRoot,
|
|
24
|
+
run: makeRun(appRoot),
|
|
25
|
+
log: (m) => console.log(m),
|
|
26
|
+
warn: (m) => console.warn(m),
|
|
27
|
+
error: (m) => console.error(m),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/localize/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAC7C,OAAO,OAAO,MAAM,cAAc,CAAC;AAqCnC,MAAM,UAAU,OAAO,CAAC,UAAkB;IACxC,OAAO,SAAS,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,OAAmB,EAAE;;QAClD,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,GAAG,EAAE,MAAA,IAAI,CAAC,GAAG,mCAAI,UAAU;YAC3B,QAAQ,EAAE,MAAM;YAChB,GAAG,EAAE,MAAA,IAAI,CAAC,GAAG,mCAAI,OAAO,CAAC,GAAG;YAC5B,KAAK,EAAE,MAAA,IAAI,CAAC,KAAK,mCAAI,KAAK;YAC1B,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;SAC5B,CAAC,CAAC;QACH,OAAO;YACL,MAAM,EAAE,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM;YACxC,MAAM,EAAE,MAAA,CAAC,CAAC,MAAM,mCAAI,EAAE;YACtB,MAAM,EAAE,MAAA,CAAC,CAAC,MAAM,mCAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7D,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,WAAW,CAAC,UAAkB,OAAO,CAAC,GAAG,EAAE;IACzD,OAAO;QACL,OAAO;QACP,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC;QACrB,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1B,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5B,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;KAC/B,CAAC;AACJ,CAAC","sourcesContent":["import {spawnSync} from 'node:child_process';\nimport process from 'node:process';\n\n/**\n * The single seam through which localize reaches the outside world.\n *\n * Every subprocess -- `git`, `npm`, a configured build command -- goes through\n * the `run` function on the deps object. Tests may replace it, but note that\n * the real-localize test deliberately does NOT: a stubbed runner cannot show\n * that `npm install` ran in the right directory, which is the one property\n * this tool has to get right.\n */\nexport interface RunOptions {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n shell?: boolean;\n}\n\nexport interface RunResult {\n status: number;\n stdout: string;\n stderr: string;\n}\n\nexport type Run = (cmd: string, args: string[], opts?: RunOptions) => RunResult;\n\n/**\n * Everything localize needs from the outside world. A public contract: callers\n * pass their own to point localize at another root or capture its output.\n */\nexport interface Deps {\n appRoot: string;\n run: Run;\n log: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport function makeRun(defaultCwd: string): Run {\n return function run(cmd, args, opts: RunOptions = {}) {\n const r = spawnSync(cmd, args, {\n cwd: opts.cwd ?? defaultCwd,\n encoding: 'utf8',\n env: opts.env ?? process.env,\n shell: opts.shell ?? false,\n maxBuffer: 64 * 1024 * 1024,\n });\n return {\n status: r.status === null ? 1 : r.status,\n stdout: r.stdout ?? '',\n stderr: r.stderr ?? (r.error ? String(r.error.message) : ''),\n };\n };\n}\n\n/** Build the default dependency bundle for a consumer root. */\nexport function defaultDeps(appRoot: string = process.cwd()): Deps {\n return {\n appRoot,\n run: makeRun(appRoot),\n log: (m) => console.log(m),\n warn: (m) => console.warn(m),\n error: (m) => console.error(m),\n };\n}\n"]}
|
|
@@ -14,6 +14,12 @@ export interface LocalizedDedupe {
|
|
|
14
14
|
names: string[];
|
|
15
15
|
skipped: SkippedDedupe[];
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* `providedPackages` (localize/provided.ts) -- the one rule pruning and the
|
|
19
|
+
* run-time check use too -- minus the Vite-only exclusion: a checkout's
|
|
20
|
+
* dependency some registry install nests its own copy of. A localized package
|
|
21
|
+
* and react / react-dom are never excluded that way.
|
|
22
|
+
*/
|
|
17
23
|
export declare function localizedDedupe(appRoot?: string): Promise<LocalizedDedupe>;
|
|
18
24
|
/** One line for the dev log about the ranges the app does not satisfy; null when there are none. */
|
|
19
25
|
export declare function describeSkipped(skipped: SkippedDedupe[]): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localized-dedupe.d.ts","sourceRoot":"","sources":["../../src/localized-dedupe.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"localized-dedupe.d.ts","sourceRoot":"","sources":["../../src/localized-dedupe.ts"],"names":[],"mappings":"AAwCA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC3B,wDAAwD;IACxD,IAAI,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE,MAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CAc1B;AAED,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,GAAG,IAAI,CAavE"}
|
|
@@ -38,64 +38,37 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
38
38
|
* nests when the hoisted copy does not satisfy that package, so deduping the
|
|
39
39
|
* name would hand it the wrong version.
|
|
40
40
|
*
|
|
41
|
-
* Plain Node, `tsx` and test runners never read this list; for them
|
|
42
|
-
*
|
|
41
|
+
* Plain Node, `tsx`, `tsc` and test runners never read this list; for them the
|
|
42
|
+
* checkout's copies are removed from disk instead -- by every localize install
|
|
43
|
+
* (`ensure`) and by the run-time check before `linked start`, `script`,
|
|
44
|
+
* `call`, `build-all` and `linked localize --ensure` (localize/ensure.ts).
|
|
43
45
|
*/
|
|
44
46
|
import fs from 'node:fs';
|
|
45
47
|
import path from 'node:path';
|
|
46
|
-
import
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
];
|
|
48
|
+
import { providedPackages } from './localize/provided.js';
|
|
49
|
+
/**
|
|
50
|
+
* `providedPackages` (localize/provided.ts) -- the one rule pruning and the
|
|
51
|
+
* run-time check use too -- minus the Vite-only exclusion: a checkout's
|
|
52
|
+
* dependency some registry install nests its own copy of. A localized package
|
|
53
|
+
* and react / react-dom are never excluded that way.
|
|
54
|
+
*/
|
|
54
55
|
export function localizedDedupe() {
|
|
55
56
|
return __awaiter(this, arguments, void 0, function* (appRoot = process.cwd()) {
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const skipped = [];
|
|
60
|
-
// name -> every localized checkout's runtime range for it
|
|
61
|
-
const asks = new Map();
|
|
62
|
-
for (const from of localized) {
|
|
63
|
-
const pkg = (_a = readJson(path.join(appRoot, 'node_modules', from, 'package.json'))) !== null && _a !== void 0 ? _a : {};
|
|
64
|
-
for (const field of RUNTIME_FIELDS) {
|
|
65
|
-
for (const [dep, range] of Object.entries((_b = pkg[field]) !== null && _b !== void 0 ? _b : {})) {
|
|
66
|
-
if (names.has(dep))
|
|
67
|
-
continue;
|
|
68
|
-
const list = (_c = asks.get(dep)) !== null && _c !== void 0 ? _c : [];
|
|
69
|
-
if (!list.some((a) => a.from === from))
|
|
70
|
-
list.push({ from, range });
|
|
71
|
-
asks.set(dep, list);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const nested = asks.size
|
|
57
|
+
const provided = providedPackages(appRoot);
|
|
58
|
+
const skipped = [...provided.skipped];
|
|
59
|
+
const nested = provided.dependencies.length
|
|
76
60
|
? nestedCopies(path.join(appRoot, 'node_modules'))
|
|
77
61
|
: new Map();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (!(
|
|
81
|
-
continue;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
skipped.push({ name, reason: 'range', asks: unmet, appVersion });
|
|
86
|
-
}
|
|
87
|
-
else if (nested.has(name)) {
|
|
88
|
-
skipped.push({ name, reason: 'nested', nestedAt: nested.get(name) });
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
names.add(name);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
for (const name of ['react', 'react-dom']) {
|
|
95
|
-
if (yield readInstalledPkg(name, appRoot))
|
|
96
|
-
names.add(name);
|
|
62
|
+
const drop = new Set();
|
|
63
|
+
for (const name of provided.dependencies) {
|
|
64
|
+
if (!nested.has(name))
|
|
65
|
+
continue;
|
|
66
|
+
skipped.push({ name, reason: 'nested', nestedAt: nested.get(name) });
|
|
67
|
+
if (name !== 'react' && name !== 'react-dom')
|
|
68
|
+
drop.add(name);
|
|
97
69
|
}
|
|
98
|
-
|
|
70
|
+
const names = provided.names.filter((name) => !drop.has(name));
|
|
71
|
+
return { names, skipped };
|
|
99
72
|
});
|
|
100
73
|
}
|
|
101
74
|
/** One line for the dev log about the ranges the app does not satisfy; null when there are none. */
|
|
@@ -109,14 +82,6 @@ export function describeSkipped(skipped) {
|
|
|
109
82
|
.map((s) => `${s.name} (${s.asks.map((a) => `${a.from} asks ${a.range}`).join(', ')}; app ${s.appVersion})`)
|
|
110
83
|
.join(', '));
|
|
111
84
|
}
|
|
112
|
-
function satisfies(version, range) {
|
|
113
|
-
try {
|
|
114
|
-
return (semver.validRange(range) !== null && semver.satisfies(version, range));
|
|
115
|
-
}
|
|
116
|
-
catch (_a) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
85
|
/**
|
|
121
86
|
* Every package name nested below the top level of `nm` — a registry install's
|
|
122
87
|
* private copy — mapped to one place it sits. Symlinks (localized checkouts,
|
|
@@ -171,12 +136,4 @@ function packageNames(nm) {
|
|
|
171
136
|
}
|
|
172
137
|
return out;
|
|
173
138
|
}
|
|
174
|
-
function readJson(file) {
|
|
175
|
-
try {
|
|
176
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
177
|
-
}
|
|
178
|
-
catch (_a) {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
139
|
//# sourceMappingURL=localized-dedupe.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localized-dedupe.js","sourceRoot":"","sources":["../../src/localized-dedupe.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What Vite's `resolve.dedupe` lists: the packages a LOCALIZED checkout could\n * otherwise load a second copy of.\n *\n * The duplicates come from localization, not from being a linked package. A\n * localized checkout (`linked localize`) is a git clone under the app root with\n * its own `npm install`, so its `node_modules` holds its own copy of what it\n * depends on — often older than the app's, and a registry copy of any sibling\n * that is localized too. Vite serves the checkout's source and resolves its\n * imports from there, so without a dedupe the dev server loads those copies next\n * to the app's (measured: three `@_linked/core` in one SSR process).\n *\n * So the list is:\n *\n * - every localized package — always, whatever a range says: the live checkout\n * is the point of localizing it, for every importer;\n * - every runtime dependency a localized checkout declares (`dependencies`,\n * `peerDependencies`, `optionalDependencies` — what its source imports) that\n * the app has at a version satisfying EVERY localized checkout's range for it;\n * - `react` and `react-dom` when the app has them.\n *\n * A dedupe resolves the name from the app root for EVERY importer and checks no\n * version — measured: a checkout asking `motion@^13` got the app's 12.43.0. Two\n * cases are therefore left out:\n *\n * - a range the app's version does not satisfy: the checkout keeps its own copy\n * (reported, see `describeSkipped`);\n * - a name some registry install in the app nests its own copy of. npm only\n * nests when the hoisted copy does not satisfy that package, so deduping the\n * name would hand it the wrong version.\n *\n * Plain Node, `tsx` and test runners never read this list; for them\n * `linked localize` removes the checkout's copies from disk instead.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport semver from 'semver';\nimport {readInstalledPkg} from './installed-packages.js';\nimport {localizedPackageNames} from './lifecycle.js';\n\nconst RUNTIME_FIELDS = [\n 'dependencies',\n 'peerDependencies',\n 'optionalDependencies',\n];\n\nexport interface SkippedDedupe {\n name: string;\n reason: 'range' | 'nested';\n /** For `range`: who asks what, and what the app has. */\n asks?: {from: string; range: string}[];\n appVersion?: string;\n /** For `nested`: one registry install that carries its own copy. */\n nestedAt?: string;\n}\n\nexport interface LocalizedDedupe {\n names: string[];\n skipped: SkippedDedupe[];\n}\n\nexport async function localizedDedupe(\n appRoot: string = process.cwd(),\n): Promise<LocalizedDedupe> {\n const localized = localizedPackageNames(appRoot).filter((name) =>\n fs.existsSync(path.join(appRoot, 'node_modules', name, 'package.json')),\n );\n const names = new Set<string>(localized);\n const skipped: SkippedDedupe[] = [];\n\n // name -> every localized checkout's runtime range for it\n const asks = new Map<string, {from: string; range: string}[]>();\n for (const from of localized) {\n const pkg =\n readJson(path.join(appRoot, 'node_modules', from, 'package.json')) ?? {};\n for (const field of RUNTIME_FIELDS) {\n for (const [dep, range] of Object.entries<string>(pkg[field] ?? {})) {\n if (names.has(dep)) continue;\n const list = asks.get(dep) ?? [];\n if (!list.some((a) => a.from === from)) list.push({from, range});\n asks.set(dep, list);\n }\n }\n }\n\n const nested = asks.size\n ? nestedCopies(path.join(appRoot, 'node_modules'))\n : new Map();\n for (const [name, ranges] of asks) {\n const app = await readInstalledPkg(name, appRoot);\n if (!app?.json?.version) continue; // nothing at the root to dedupe to\n const appVersion = app.json.version;\n const unmet = ranges.filter((a) => !satisfies(appVersion, a.range));\n if (unmet.length) {\n skipped.push({name, reason: 'range', asks: unmet, appVersion});\n } else if (nested.has(name)) {\n skipped.push({name, reason: 'nested', nestedAt: nested.get(name)});\n } else {\n names.add(name);\n }\n }\n\n for (const name of ['react', 'react-dom']) {\n if (await readInstalledPkg(name, appRoot)) names.add(name);\n }\n return {names: [...names].sort(), skipped};\n}\n\n/** One line for the dev log about the ranges the app does not satisfy; null when there are none. */\nexport function describeSkipped(skipped: SkippedDedupe[]): string | null {\n const range = skipped.filter((s) => s.reason === 'range');\n if (!range.length) return null;\n return (\n `[linked] dedupe: ${range.length} package${range.length === 1 ? '' : 's'} a localized checkout ` +\n `keeps its own copy of, because the app's version is outside its range: ` +\n range\n .map(\n (s) =>\n `${s.name} (${s.asks!.map((a) => `${a.from} asks ${a.range}`).join(', ')}; app ${s.appVersion})`,\n )\n .join(', ')\n );\n}\n\nfunction satisfies(version: string, range: string): boolean {\n try {\n return (\n semver.validRange(range) !== null && semver.satisfies(version, range)\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Every package name nested below the top level of `nm` — a registry install's\n * private copy — mapped to one place it sits. Symlinks (localized checkouts,\n * workspaces) are not followed: their trees are not the app's install.\n */\nfunction nestedCopies(nm: string): Map<string, string> {\n const out = new Map<string, string>();\n const walk = (dir: string, depth: number) => {\n for (const name of packageNames(dir)) {\n const pkgDir = path.join(dir, name);\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(pkgDir);\n } catch {\n continue;\n }\n if (stat.isSymbolicLink()) continue;\n if (depth > 0 && !out.has(name))\n out.set(name, path.relative(path.dirname(nm), pkgDir));\n walk(path.join(pkgDir, 'node_modules'), depth + 1);\n }\n };\n walk(nm, 0);\n return out;\n}\n\nfunction packageNames(nm: string): string[] {\n let entries: string[];\n try {\n entries = fs.readdirSync(nm);\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n if (entry.startsWith('.')) continue;\n if (entry.startsWith('@')) {\n try {\n for (const sub of fs.readdirSync(path.join(nm, entry)))\n out.push(`${entry}/${sub}`);\n } catch {\n /* not a directory */\n }\n } else {\n out.push(entry);\n }\n }\n return out;\n}\n\nfunction readJson(file: string): any {\n try {\n return JSON.parse(fs.readFileSync(file, 'utf8'));\n } catch {\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"localized-dedupe.js","sourceRoot":"","sources":["../../src/localized-dedupe.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What Vite's `resolve.dedupe` lists: the packages a LOCALIZED checkout could\n * otherwise load a second copy of.\n *\n * The duplicates come from localization, not from being a linked package. A\n * localized checkout (`linked localize`) is a git clone under the app root with\n * its own `npm install`, so its `node_modules` holds its own copy of what it\n * depends on — often older than the app's, and a registry copy of any sibling\n * that is localized too. Vite serves the checkout's source and resolves its\n * imports from there, so without a dedupe the dev server loads those copies next\n * to the app's (measured: three `@_linked/core` in one SSR process).\n *\n * So the list is:\n *\n * - every localized package — always, whatever a range says: the live checkout\n * is the point of localizing it, for every importer;\n * - every runtime dependency a localized checkout declares (`dependencies`,\n * `peerDependencies`, `optionalDependencies` — what its source imports) that\n * the app has at a version satisfying EVERY localized checkout's range for it;\n * - `react` and `react-dom` when the app has them.\n *\n * A dedupe resolves the name from the app root for EVERY importer and checks no\n * version — measured: a checkout asking `motion@^13` got the app's 12.43.0. Two\n * cases are therefore left out:\n *\n * - a range the app's version does not satisfy: the checkout keeps its own copy\n * (reported, see `describeSkipped`);\n * - a name some registry install in the app nests its own copy of. npm only\n * nests when the hoisted copy does not satisfy that package, so deduping the\n * name would hand it the wrong version.\n *\n * Plain Node, `tsx`, `tsc` and test runners never read this list; for them the\n * checkout's copies are removed from disk instead -- by every localize install\n * (`ensure`) and by the run-time check before `linked start`, `script`,\n * `call`, `build-all` and `linked localize --ensure` (localize/ensure.ts).\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport {providedPackages} from './localize/provided.js';\n\nexport interface SkippedDedupe {\n name: string;\n reason: 'range' | 'nested';\n /** For `range`: who asks what, and what the app has. */\n asks?: {from: string; range: string}[];\n appVersion?: string;\n /** For `nested`: one registry install that carries its own copy. */\n nestedAt?: string;\n}\n\nexport interface LocalizedDedupe {\n names: string[];\n skipped: SkippedDedupe[];\n}\n\n/**\n * `providedPackages` (localize/provided.ts) -- the one rule pruning and the\n * run-time check use too -- minus the Vite-only exclusion: a checkout's\n * dependency some registry install nests its own copy of. A localized package\n * and react / react-dom are never excluded that way.\n */\nexport async function localizedDedupe(\n appRoot: string = process.cwd(),\n): Promise<LocalizedDedupe> {\n const provided = providedPackages(appRoot);\n const skipped: SkippedDedupe[] = [...provided.skipped];\n const nested = provided.dependencies.length\n ? nestedCopies(path.join(appRoot, 'node_modules'))\n : new Map<string, string>();\n const drop = new Set<string>();\n for (const name of provided.dependencies) {\n if (!nested.has(name)) continue;\n skipped.push({name, reason: 'nested', nestedAt: nested.get(name)});\n if (name !== 'react' && name !== 'react-dom') drop.add(name);\n }\n const names = provided.names.filter((name) => !drop.has(name));\n return {names, skipped};\n}\n\n/** One line for the dev log about the ranges the app does not satisfy; null when there are none. */\nexport function describeSkipped(skipped: SkippedDedupe[]): string | null {\n const range = skipped.filter((s) => s.reason === 'range');\n if (!range.length) return null;\n return (\n `[linked] dedupe: ${range.length} package${range.length === 1 ? '' : 's'} a localized checkout ` +\n `keeps its own copy of, because the app's version is outside its range: ` +\n range\n .map(\n (s) =>\n `${s.name} (${s.asks!.map((a) => `${a.from} asks ${a.range}`).join(', ')}; app ${s.appVersion})`,\n )\n .join(', ')\n );\n}\n\n/**\n * Every package name nested below the top level of `nm` — a registry install's\n * private copy — mapped to one place it sits. Symlinks (localized checkouts,\n * workspaces) are not followed: their trees are not the app's install.\n */\nfunction nestedCopies(nm: string): Map<string, string> {\n const out = new Map<string, string>();\n const walk = (dir: string, depth: number) => {\n for (const name of packageNames(dir)) {\n const pkgDir = path.join(dir, name);\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(pkgDir);\n } catch {\n continue;\n }\n if (stat.isSymbolicLink()) continue;\n if (depth > 0 && !out.has(name))\n out.set(name, path.relative(path.dirname(nm), pkgDir));\n walk(path.join(pkgDir, 'node_modules'), depth + 1);\n }\n };\n walk(nm, 0);\n return out;\n}\n\nfunction packageNames(nm: string): string[] {\n let entries: string[];\n try {\n entries = fs.readdirSync(nm);\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n if (entry.startsWith('.')) continue;\n if (entry.startsWith('@')) {\n try {\n for (const sub of fs.readdirSync(path.join(nm, entry)))\n out.push(`${entry}/${sub}`);\n } catch {\n /* not a directory */\n }\n } else {\n out.push(entry);\n }\n }\n return out;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@_linked/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.0",
|
|
4
4
|
"packageManager": "npm@11.21.0",
|
|
5
5
|
"description": "Command line tools for the @_linked/* packages and apps",
|
|
6
6
|
"main": "lib/esm/index.js",
|
|
@@ -71,7 +71,6 @@
|
|
|
71
71
|
],
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@_linked/core": "^2.22.8",
|
|
74
|
-
"@_linked/localize": "^0.3.0",
|
|
75
74
|
"@tailwindcss/vite": "^4.1.4",
|
|
76
75
|
"@types/node": "^20.12.7",
|
|
77
76
|
"@types/react": "^19.3.0",
|