@_linked/cli 1.29.0 → 1.30.1
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 +33 -0
- package/lib/esm/app-release/build-backend.d.ts +66 -9
- package/lib/esm/app-release/build-backend.d.ts.map +1 -1
- package/lib/esm/app-release/build-backend.js +206 -32
- package/lib/esm/app-release/build-backend.js.map +1 -1
- package/lib/esm/cli-methods.d.ts.map +1 -1
- package/lib/esm/cli-methods.js +8 -0
- package/lib/esm/cli-methods.js.map +1 -1
- package/lib/esm/utils/shapeNames.d.ts +2 -0
- package/lib/esm/utils/shapeNames.d.ts.map +1 -1
- package/lib/esm/utils/shapeNames.js +8 -0
- package/lib/esm/utils/shapeNames.js.map +1 -1
- package/lib/esm/utils/shapeReferences.d.ts +97 -0
- package/lib/esm/utils/shapeReferences.d.ts.map +1 -0
- package/lib/esm/utils/shapeReferences.js +308 -0
- package/lib/esm/utils/shapeReferences.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shapeNames.d.ts","sourceRoot":"","sources":["../../../src/utils/shapeNames.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,aAAS,KACR,YAAY,EASd,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,aAAa,MAAM,KAClB,OAAO,CAAC,IAAI,GAAG,MAAM,CA2BvB,CAAC"}
|
|
1
|
+
{"version":3,"file":"shapeNames.d.ts","sourceRoot":"","sources":["../../../src/utils/shapeNames.ts"],"names":[],"mappings":"AAkBA,sFAAsF;AACtF,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,KAAG,OAK9C,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,aAAS,KACR,YAAY,EASd,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,aAAa,MAAM,KAClB,OAAO,CAAC,IAAI,GAAG,MAAM,CA2BvB,CAAC"}
|
|
@@ -22,6 +22,14 @@ import path from 'path';
|
|
|
22
22
|
const RENAME_TRIGGER = /process\.env|import\.meta\.(?:env|hot)/;
|
|
23
23
|
/** `Foo = __decorate([ …decorators… ], Foo)`, as tsc emits it. */
|
|
24
24
|
const DECORATED = /\b([A-Za-z_$][\w$]*) = __decorate\(\[([\s\S]*?)\], \1\)/g;
|
|
25
|
+
/** Whether a compiled module declares a shape: a tsc-emitted `@linkedShape` class. */
|
|
26
|
+
export const declaresShape = (source) => {
|
|
27
|
+
for (const [, , decorators] of source.matchAll(DECORATED)) {
|
|
28
|
+
if (/\blinkedShape\b/.test(decorators))
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
25
33
|
/**
|
|
26
34
|
* Shapes in one compiled module whose identity would change if a bundler
|
|
27
35
|
* renamed their class.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shapeNames.js","sourceRoot":"","sources":["../../../src/utils/shapeNames.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG,wCAAwC,CAAC;AAEhE,kEAAkE;AAClE,MAAM,SAAS,GAAG,0DAA0D,CAAC;
|
|
1
|
+
{"version":3,"file":"shapeNames.js","sourceRoot":"","sources":["../../../src/utils/shapeNames.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\n/**\n * Text that makes Vite run an extra `esbuild.transform` over a compiled module.\n *\n * Vite's `vite:define` plugin transforms any module containing a `define` key\n * (`process.env` and `import.meta.env` / `import.meta.hot` are its own keys in\n * a client build), and in dev it rewrites `process.env.NODE_ENV` in client\n * modules the same way. That pass does not set `keepNames`, and esbuild\n * renames the inner binding of the class expression tsc emits for a decorated\n * class: `let Foo = class Foo` becomes `class Foo2`.\n */\nconst RENAME_TRIGGER = /process\\.env|import\\.meta\\.(?:env|hot)/;\n\n/** `Foo = __decorate([ …decorators… ], Foo)`, as tsc emits it. */\nconst DECORATED = /\\b([A-Za-z_$][\\w$]*) = __decorate\\(\\[([\\s\\S]*?)\\], \\1\\)/g;\n\n/** Whether a compiled module declares a shape: a tsc-emitted `@linkedShape` class. */\nexport const declaresShape = (source: string): boolean => {\n for (const [, , decorators] of source.matchAll(DECORATED)) {\n if (/\\blinkedShape\\b/.test(decorators)) return true;\n }\n return false;\n};\n\nexport interface ExposedShape {\n file: string;\n className: string;\n}\n\n/**\n * Shapes in one compiled module whose identity would change if a bundler\n * renamed their class.\n *\n * A shape's IRI is built from its class name unless `@linkedShape({name})`\n * gives one. Source files are safe: esbuild lowers their decorators to an\n * anonymous `let Foo = class {`, which takes its name from the binding. Only\n * published `lib/` JavaScript carries the self-named class expression, so a\n * compiled shape is exposed exactly when it has no explicit name and its module\n * contains a trigger. `createViteConfig` pins these names\n * (`plugins/pin-compiled-class-names.ts`), but other bundlers and hand-written\n * Vite configs do not, so the explicit name is still the fix. This is how\n * `@_linked/server`'s `LinkedServer` and `LincdAPI` came to register as\n * `LinkedServer2` / `LincdAPI2`.\n */\nexport const findRenameableShapes = (\n source: string,\n file = '',\n): ExposedShape[] => {\n if (!RENAME_TRIGGER.test(source)) return [];\n const found: ExposedShape[] = [];\n for (const [, className, decorators] of source.matchAll(DECORATED)) {\n if (!/\\blinkedShape\\b/.test(decorators)) continue;\n if (/\\blinkedShape\\(\\s*\\{[\\s\\S]*?\\bname\\s*:/.test(decorators)) continue;\n found.push({file, className});\n }\n return found;\n};\n\n/**\n * Build step: warn about every exposed shape in a package's compiled ESM.\n *\n * A warning, not a failure: it is only a hazard when a consumer's bundler\n * actually rewrites the module, and naming the shape is a change to the\n * package's source that its owner has to make.\n */\nexport const checkShapeNames = async (\n packagePath: string,\n): Promise<true | string> => {\n const libDir = path.join(packagePath, 'lib', 'esm');\n if (!fs.existsSync(libDir)) return true;\n const exposed: ExposedShape[] = [];\n const walk = (dir: string) => {\n for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) walk(full);\n else if (entry.name.endsWith('.js')) {\n exposed.push(\n ...findRenameableShapes(\n fs.readFileSync(full, 'utf8'),\n path.relative(libDir, full),\n ),\n );\n }\n }\n };\n walk(libDir);\n if (exposed.length === 0) return true;\n const listed = exposed.map((s) => `${s.className} (${s.file})`).join(', ');\n return (\n `${exposed.length} shape(s) take their identity from a class name that a ` +\n `consumer's bundler may rename, because the module also reads process.env or ` +\n `import.meta.env: ${listed}. Give each an explicit name: ` +\n `@linkedShape({name: '<ClassName>'}).`\n );\n};\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checking shape references: does loading a compiled shape module, on its own,
|
|
3
|
+
* register every shape its properties point at?
|
|
4
|
+
*
|
|
5
|
+
* A property can name its value shape by `[package, name]` instead of by class
|
|
6
|
+
* (`shape: ['@_linked/schema', 'ImageObject']`), usually because importing the
|
|
7
|
+
* class would close an import cycle. That names the shape without loading it.
|
|
8
|
+
* A shape registers only when its module is evaluated, and consumers deep-import
|
|
9
|
+
* single shape modules (`@_linked/schema/shapes/Thing`), so nothing guarantees
|
|
10
|
+
* the named shape is registered when a query traverses the property. The query
|
|
11
|
+
* then throws `Shape class not found for …/ImageObject` — in a production bundle,
|
|
12
|
+
* far from the cause. A reference to a package under an old name
|
|
13
|
+
* (`['lincd-sioc', …]`) can never resolve at all.
|
|
14
|
+
*
|
|
15
|
+
* So each module is loaded the way a consumer's fresh process would load it: in
|
|
16
|
+
* a child `node` of its own, with nothing else imported first. Separate processes
|
|
17
|
+
* rather than one process re-importing: ESM has no module-cache eviction, and the
|
|
18
|
+
* shape registry lives on `globalThis`, so a second module in the same process
|
|
19
|
+
* would see every shape the first one registered.
|
|
20
|
+
*/
|
|
21
|
+
/** One module that could not be evaluated. */
|
|
22
|
+
export interface ShapeModuleLoadError {
|
|
23
|
+
module: string;
|
|
24
|
+
error: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A property whose value shape was not registered after loading `modules`
|
|
28
|
+
* (each on its own). One entry per distinct reference, however many modules
|
|
29
|
+
* showed it.
|
|
30
|
+
*/
|
|
31
|
+
export interface UnresolvedShapeReference {
|
|
32
|
+
shape: string;
|
|
33
|
+
property: string;
|
|
34
|
+
valueShape: string;
|
|
35
|
+
modules: string[];
|
|
36
|
+
/**
|
|
37
|
+
* What the target package's entry does for it: `registered` — loading the
|
|
38
|
+
* entry registers it, so the referencing module just does not import it;
|
|
39
|
+
* `missing` — not even the entry registers it, so the name or package is
|
|
40
|
+
* wrong; `not-a-dependency` — the IRI's package is neither this package nor
|
|
41
|
+
* one of its dependencies; `unknown` — the entry could not be loaded.
|
|
42
|
+
*/
|
|
43
|
+
target: 'registered' | 'missing' | 'not-a-dependency' | 'unknown';
|
|
44
|
+
/** The package the IRI's slug maps to, when one does. */
|
|
45
|
+
targetPackage?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ShapeReferenceReport {
|
|
48
|
+
modules: string[];
|
|
49
|
+
loadErrors: ShapeModuleLoadError[];
|
|
50
|
+
unresolved: UnresolvedShapeReference[];
|
|
51
|
+
/** Unresolved references that match {@link KNOWN_UNRESOLVED}. */
|
|
52
|
+
known: UnresolvedShapeReference[];
|
|
53
|
+
/** Set when the registry could not be inspected, so nothing was checked. */
|
|
54
|
+
skipped?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* References known to be unresolved, reported as warnings rather than failures.
|
|
58
|
+
*
|
|
59
|
+
* This is deliberately a list in the CLI rather than an opt-out a package can
|
|
60
|
+
* write for itself: every entry is a shipped bug with a tracked fix, not an
|
|
61
|
+
* accepted design. No package was found with a legitimate reason to leave a
|
|
62
|
+
* reference unresolved; add an opt-out mechanism when one turns up.
|
|
63
|
+
*
|
|
64
|
+
* - `@_linked/core`: `PropertyShape.in` names `List` by `[package, name]`
|
|
65
|
+
* because `List`'s module depends on the SHACL shapes. Only core's entry
|
|
66
|
+
* loads `List`, so a process that deep-imports core without its entry cannot
|
|
67
|
+
* traverse `sh:in`. Tracked as core's backlog-045; core builds with `linked
|
|
68
|
+
* build-all`, which must not fail on it meanwhile. Remove this entry once
|
|
69
|
+
* core registers `List` wherever `PropertyShape` is registered.
|
|
70
|
+
*/
|
|
71
|
+
export declare const KNOWN_UNRESOLVED: {
|
|
72
|
+
package: string;
|
|
73
|
+
shape: string;
|
|
74
|
+
property: string;
|
|
75
|
+
valueShape: string;
|
|
76
|
+
}[];
|
|
77
|
+
/** arch-02: the `{slug}` in `{baseUri}shape/{slug}/{Name}`. Mirrors core's `packageNameToSlug`. */
|
|
78
|
+
export declare const packageNameToSlug: (packageName: string) => string;
|
|
79
|
+
/** Every compiled module that declares a shape: all of `shapes/`, plus decorated classes elsewhere. */
|
|
80
|
+
export declare const findShapeModules: (libEsm: string) => string[];
|
|
81
|
+
/**
|
|
82
|
+
* Load each shape module of the package at `packagePath` in isolation and
|
|
83
|
+
* collect load errors and unresolved property value shapes.
|
|
84
|
+
*/
|
|
85
|
+
export declare const inspectShapeReferences: (packagePath: string, { concurrency, timeoutMs, }?: {
|
|
86
|
+
concurrency?: number;
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
}) => Promise<ShapeReferenceReport>;
|
|
89
|
+
/**
|
|
90
|
+
* Build step. Load errors and unresolved references fail the build: each is a
|
|
91
|
+
* bug the package would ship, and it only surfaces in a consumer's bundle. The
|
|
92
|
+
* references in {@link KNOWN_UNRESOLVED} warn instead.
|
|
93
|
+
*/
|
|
94
|
+
export declare const checkShapeReferences: (packagePath: string, options?: Parameters<typeof inspectShapeReferences>[1]) => Promise<true | string | {
|
|
95
|
+
error: string;
|
|
96
|
+
}>;
|
|
97
|
+
//# sourceMappingURL=shapeReferences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapeReferences.d.ts","sourceRoot":"","sources":["../../../src/utils/shapeReferences.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;;;OAMG;IACH,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAAC;IAClE,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,oBAAoB,EAAE,CAAC;IACnC,UAAU,EAAE,wBAAwB,EAAE,CAAC;IACvC,iEAAiE;IACjE,KAAK,EAAE,wBAAwB,EAAE,CAAC;IAClC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,EAAE;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,EAOA,CAAC;AAEF,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,GAAI,aAAa,MAAM,KAAG,MAKtC,CAAC;AAqInB,uGAAuG;AACvG,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,KAAG,MAAM,EAuBvD,CAAC;AAoBF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,aAAa,MAAM,EACnB;;;CAGM,KACL,OAAO,CAAC,oBAAoB,CA2F9B,CAAC;AAsBF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,aAAa,MAAM,EACnB,UAAU,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,KACrD,OAAO,CAAC,IAAI,GAAG,MAAM,GAAG;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CA4BzC,CAAC"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { execFile } from 'child_process';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { declaresShape } from './shapeNames.js';
|
|
15
|
+
/**
|
|
16
|
+
* References known to be unresolved, reported as warnings rather than failures.
|
|
17
|
+
*
|
|
18
|
+
* This is deliberately a list in the CLI rather than an opt-out a package can
|
|
19
|
+
* write for itself: every entry is a shipped bug with a tracked fix, not an
|
|
20
|
+
* accepted design. No package was found with a legitimate reason to leave a
|
|
21
|
+
* reference unresolved; add an opt-out mechanism when one turns up.
|
|
22
|
+
*
|
|
23
|
+
* - `@_linked/core`: `PropertyShape.in` names `List` by `[package, name]`
|
|
24
|
+
* because `List`'s module depends on the SHACL shapes. Only core's entry
|
|
25
|
+
* loads `List`, so a process that deep-imports core without its entry cannot
|
|
26
|
+
* traverse `sh:in`. Tracked as core's backlog-045; core builds with `linked
|
|
27
|
+
* build-all`, which must not fail on it meanwhile. Remove this entry once
|
|
28
|
+
* core registers `List` wherever `PropertyShape` is registered.
|
|
29
|
+
*/
|
|
30
|
+
export const KNOWN_UNRESOLVED = [
|
|
31
|
+
{
|
|
32
|
+
package: '@_linked/core',
|
|
33
|
+
shape: '/shape/core/PropertyShape',
|
|
34
|
+
property: 'in',
|
|
35
|
+
valueShape: '/shape/core/List',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
/** arch-02: the `{slug}` in `{baseUri}shape/{slug}/{Name}`. Mirrors core's `packageNameToSlug`. */
|
|
39
|
+
export const packageNameToSlug = (packageName) => packageName
|
|
40
|
+
.replace(/^@[^/]+\//, '')
|
|
41
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
42
|
+
.replace(/^-+|-+$/g, '')
|
|
43
|
+
.toLowerCase();
|
|
44
|
+
/** The slug in a node-shape IRI, or undefined when it is not shaped like one. */
|
|
45
|
+
const slugOf = (iri) => { var _a; return (_a = iri.match(/\/shape\/([^/]+)\/[^/]+$/)) === null || _a === void 0 ? void 0 : _a[1]; };
|
|
46
|
+
/**
|
|
47
|
+
* A loader hook for the child: asset imports (`import styles from
|
|
48
|
+
* './X.module.css'`) evaluate to a stub. Node cannot load them and a consumer's
|
|
49
|
+
* bundler always can, so without this a shape module whose file also holds a
|
|
50
|
+
* component would look broken when it is not. A CSS-module stub maps every
|
|
51
|
+
* class name to itself.
|
|
52
|
+
*/
|
|
53
|
+
const ASSET_HOOK = `
|
|
54
|
+
const ASSET = /\\.(css|scss|sass|less|styl|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|eot)$/i;
|
|
55
|
+
export async function load(url, context, next) {
|
|
56
|
+
if (url.startsWith('file:') && ASSET.test(new URL(url).pathname)) {
|
|
57
|
+
return {format: 'module', shortCircuit: true,
|
|
58
|
+
source: 'export default new Proxy({}, {get: (_, k) => typeof k === "string" ? k : undefined});'};
|
|
59
|
+
}
|
|
60
|
+
return next(url, context);
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
/**
|
|
64
|
+
* The script each child runs. Plain JS, passed with `-e`, so it runs the same
|
|
65
|
+
* from `src` under a test runner as from the published `lib`.
|
|
66
|
+
*
|
|
67
|
+
* It reads the registry from `globalThis.__linkedShapeRegistry` rather than
|
|
68
|
+
* importing `@_linked/core`: the module under test decides which copy of core
|
|
69
|
+
* it loads, and the registry is shared by every copy.
|
|
70
|
+
*/
|
|
71
|
+
const PROBE = `
|
|
72
|
+
import {register} from 'node:module';
|
|
73
|
+
register('data:text/javascript,' + encodeURIComponent(${JSON.stringify(ASSET_HOOK)}));
|
|
74
|
+
const MARK = '__LINKED_SHAPE_REFERENCES__';
|
|
75
|
+
const out = {error: null, unresolved: [], registry: true};
|
|
76
|
+
const reg = () => globalThis.__linkedShapeRegistry;
|
|
77
|
+
const ownSlug = process.env.LINKED_PROBE_SLUG;
|
|
78
|
+
const report = () => {
|
|
79
|
+
process.stdout.write('\\n' + MARK + JSON.stringify(out) + '\\n', () => process.exit(0));
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
await import(process.env.LINKED_PROBE_MODULE);
|
|
83
|
+
const r = reg();
|
|
84
|
+
if (!r || !r.nodeShapeRegistry) {
|
|
85
|
+
out.registry = false;
|
|
86
|
+
} else {
|
|
87
|
+
for (const [id, ns] of r.nodeShapeRegistry) {
|
|
88
|
+
if (!id.includes('/shape/' + ownSlug + '/')) continue;
|
|
89
|
+
for (const ps of ns.propertyShapes || []) {
|
|
90
|
+
const v = ps.valueShape && ps.valueShape.id;
|
|
91
|
+
if (v && !r.nodeShapeRegistry.has(v)) {
|
|
92
|
+
out.unresolved.push({shape: id, property: ps.label, valueShape: v});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// For each unresolved target, load its package's entry and look again: that
|
|
97
|
+
// tells a missing import apart from a name that can never resolve.
|
|
98
|
+
const entries = JSON.parse(process.env.LINKED_PROBE_ENTRIES || '{}');
|
|
99
|
+
const tried = {};
|
|
100
|
+
for (const u of out.unresolved) {
|
|
101
|
+
const slug = (u.valueShape.match(/\\/shape\\/([^/]+)\\/[^/]+$/) || [])[1];
|
|
102
|
+
const entry = entries[slug];
|
|
103
|
+
if (!entry) { u.target = 'not-a-dependency'; continue; }
|
|
104
|
+
u.targetPackage = entry.name;
|
|
105
|
+
if (!(slug in tried)) {
|
|
106
|
+
try { await import(entry.specifier); tried[slug] = true; }
|
|
107
|
+
catch (e) { tried[slug] = false; }
|
|
108
|
+
}
|
|
109
|
+
u.target = !tried[slug] ? 'unknown'
|
|
110
|
+
: reg().nodeShapeRegistry.has(u.valueShape) ? 'registered' : 'missing';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
out.error = e && e.stack ? e.stack.split('\\n').slice(0, 4).join('\\n') : String(e);
|
|
115
|
+
}
|
|
116
|
+
report();
|
|
117
|
+
`;
|
|
118
|
+
const MARK = '__LINKED_SHAPE_REFERENCES__';
|
|
119
|
+
const runProbe = (modulePath, packagePath, env, timeoutMs) => new Promise((resolve) => {
|
|
120
|
+
execFile(process.execPath, ['--input-type=module', '-e', PROBE], {
|
|
121
|
+
cwd: packagePath,
|
|
122
|
+
env: Object.assign(Object.assign(Object.assign({}, process.env), env), { LINKED_PROBE_MODULE: 'file://' + modulePath }),
|
|
123
|
+
timeout: timeoutMs,
|
|
124
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
125
|
+
}, (err, stdout, stderr) => {
|
|
126
|
+
const line = String(stdout)
|
|
127
|
+
.split('\n')
|
|
128
|
+
.reverse()
|
|
129
|
+
.find((l) => l.startsWith(MARK));
|
|
130
|
+
if (line) {
|
|
131
|
+
resolve(JSON.parse(line.slice(MARK.length)));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const why = err && err.killed
|
|
135
|
+
? `did not finish loading within ${timeoutMs}ms`
|
|
136
|
+
: String(stderr).trim().split('\n').slice(-4).join('\n') ||
|
|
137
|
+
String(err || 'exited without reporting');
|
|
138
|
+
resolve({ error: why, registry: true, unresolved: [] });
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
/** Every compiled module that declares a shape: all of `shapes/`, plus decorated classes elsewhere. */
|
|
142
|
+
export const findShapeModules = (libEsm) => {
|
|
143
|
+
const found = [];
|
|
144
|
+
const walk = (dir) => {
|
|
145
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
146
|
+
const full = path.join(dir, entry.name);
|
|
147
|
+
if (entry.isDirectory()) {
|
|
148
|
+
walk(full);
|
|
149
|
+
}
|
|
150
|
+
else if (entry.name.endsWith('.js') &&
|
|
151
|
+
!/\.(test|spec|class)\.js$/.test(entry.name)) {
|
|
152
|
+
const rel = path.relative(libEsm, full);
|
|
153
|
+
if (rel.split(path.sep)[0] === 'shapes' ||
|
|
154
|
+
declaresShape(fs.readFileSync(full, 'utf8'))) {
|
|
155
|
+
found.push(full);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
if (fs.existsSync(libEsm))
|
|
161
|
+
walk(libEsm);
|
|
162
|
+
return found.sort();
|
|
163
|
+
};
|
|
164
|
+
/** The name the package registers its shapes under: `linkedPackage('<name>')` in `package.js`. */
|
|
165
|
+
const runtimePackageName = (libEsm, pkgJson) => {
|
|
166
|
+
const pkgModule = path.join(libEsm, 'package.js');
|
|
167
|
+
if (fs.existsSync(pkgModule)) {
|
|
168
|
+
const m = fs
|
|
169
|
+
.readFileSync(pkgModule, 'utf8')
|
|
170
|
+
.match(/linkedPackage\(\s*['"]([^'"]+)['"]/);
|
|
171
|
+
if (m)
|
|
172
|
+
return m[1];
|
|
173
|
+
}
|
|
174
|
+
return pkgJson.name;
|
|
175
|
+
};
|
|
176
|
+
const DEPENDENCY_FIELDS = [
|
|
177
|
+
'dependencies',
|
|
178
|
+
'peerDependencies',
|
|
179
|
+
'optionalDependencies',
|
|
180
|
+
];
|
|
181
|
+
/**
|
|
182
|
+
* Load each shape module of the package at `packagePath` in isolation and
|
|
183
|
+
* collect load errors and unresolved property value shapes.
|
|
184
|
+
*/
|
|
185
|
+
export const inspectShapeReferences = (packagePath_1, ...args_1) => __awaiter(void 0, [packagePath_1, ...args_1], void 0, function* (packagePath, { concurrency = Math.min(8, Math.max(1, os.cpus().length - 1)), timeoutMs = 60000, } = {}) {
|
|
186
|
+
const libEsm = path.join(packagePath, 'lib', 'esm');
|
|
187
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'), 'utf8'));
|
|
188
|
+
const modules = findShapeModules(libEsm);
|
|
189
|
+
const report = {
|
|
190
|
+
modules: modules.map((m) => path.relative(libEsm, m)),
|
|
191
|
+
loadErrors: [],
|
|
192
|
+
unresolved: [],
|
|
193
|
+
known: [],
|
|
194
|
+
};
|
|
195
|
+
if (modules.length === 0)
|
|
196
|
+
return report;
|
|
197
|
+
const ownName = runtimePackageName(libEsm, pkgJson);
|
|
198
|
+
// The entry each slug's package is loaded through for the second look. This
|
|
199
|
+
// package's own entry is loaded by path: a package cannot always import
|
|
200
|
+
// itself by name.
|
|
201
|
+
const entries = {};
|
|
202
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
203
|
+
for (const dep of Object.keys(pkgJson[field] || {})) {
|
|
204
|
+
entries[packageNameToSlug(dep)] = { name: dep, specifier: dep };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const ownEntry = path.join(packagePath, pkgJson.module || pkgJson.main || 'lib/esm/index.js');
|
|
208
|
+
for (const name of new Set([ownName, pkgJson.name])) {
|
|
209
|
+
entries[packageNameToSlug(name)] = {
|
|
210
|
+
name,
|
|
211
|
+
specifier: fs.existsSync(ownEntry) ? 'file://' + ownEntry : name,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const env = {
|
|
215
|
+
LINKED_PROBE_SLUG: packageNameToSlug(ownName),
|
|
216
|
+
LINKED_PROBE_ENTRIES: JSON.stringify(entries),
|
|
217
|
+
};
|
|
218
|
+
const results = new Array(modules.length);
|
|
219
|
+
let next = 0;
|
|
220
|
+
yield Promise.all(Array.from({ length: Math.min(concurrency, modules.length) }, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
221
|
+
while (next < modules.length) {
|
|
222
|
+
const i = next++;
|
|
223
|
+
results[i] = yield runProbe(modules[i], packagePath, env, timeoutMs);
|
|
224
|
+
}
|
|
225
|
+
})));
|
|
226
|
+
// No registry on `globalThis` after loading a module that declares a shape
|
|
227
|
+
// means a core older than 2.22.3, whose registry is module-scoped. A module
|
|
228
|
+
// under `shapes/` that declares none (a store, a helper) registers nothing.
|
|
229
|
+
const stale = results.findIndex((r, i) => !r.error &&
|
|
230
|
+
!r.registry &&
|
|
231
|
+
declaresShape(fs.readFileSync(modules[i], 'utf8')));
|
|
232
|
+
if (stale !== -1) {
|
|
233
|
+
report.skipped =
|
|
234
|
+
`no shape registry on globalThis after loading ${report.modules[stale]}: the ` +
|
|
235
|
+
'@_linked/core it loads is older than 2.22.3, whose registry cannot be inspected from outside';
|
|
236
|
+
return report;
|
|
237
|
+
}
|
|
238
|
+
const byReference = new Map();
|
|
239
|
+
results.forEach((r, i) => {
|
|
240
|
+
const module = report.modules[i];
|
|
241
|
+
if (r.error) {
|
|
242
|
+
report.loadErrors.push({ module, error: r.error });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const u of r.unresolved) {
|
|
246
|
+
const key = `${u.shape}\u0000${u.property}\u0000${u.valueShape}`;
|
|
247
|
+
const existing = byReference.get(key);
|
|
248
|
+
if (existing)
|
|
249
|
+
existing.modules.push(module);
|
|
250
|
+
else
|
|
251
|
+
byReference.set(key, Object.assign(Object.assign({}, u), { modules: [module] }));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
for (const ref of byReference.values()) {
|
|
255
|
+
const isKnown = KNOWN_UNRESOLVED.some((k) => k.package === ownName &&
|
|
256
|
+
ref.shape.endsWith(k.shape) &&
|
|
257
|
+
ref.property === k.property &&
|
|
258
|
+
ref.valueShape.endsWith(k.valueShape));
|
|
259
|
+
(isKnown ? report.known : report.unresolved).push(ref);
|
|
260
|
+
}
|
|
261
|
+
return report;
|
|
262
|
+
});
|
|
263
|
+
const shortIri = (iri) => iri.replace(/^.*\/shape\//, '');
|
|
264
|
+
const modulesList = (modules) => modules.length <= 3
|
|
265
|
+
? modules.join(', ')
|
|
266
|
+
: `${modules.slice(0, 3).join(', ')} and ${modules.length - 3} more`;
|
|
267
|
+
const describeReference = (ref) => {
|
|
268
|
+
const label = `${shortIri(ref.shape)}.${ref.property} -> ${ref.valueShape}`;
|
|
269
|
+
const fix = ref.target === 'registered'
|
|
270
|
+
? `${ref.targetPackage}'s entry registers it, so the module that declares the property must import the module that declares ${shortIri(ref.valueShape)}`
|
|
271
|
+
: ref.target === 'missing'
|
|
272
|
+
? `not registered even by ${ref.targetPackage}'s entry: the shape name is wrong or ${ref.targetPackage} does not define it`
|
|
273
|
+
: ref.target === 'not-a-dependency'
|
|
274
|
+
? `'${slugOf(ref.valueShape)}' is neither this package nor one of its dependencies (an old package name?)`
|
|
275
|
+
: `${ref.targetPackage}'s entry could not be loaded to tell why`;
|
|
276
|
+
return ` ${label}\n unresolved after loading ${modulesList(ref.modules)}\n ${fix}`;
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* Build step. Load errors and unresolved references fail the build: each is a
|
|
280
|
+
* bug the package would ship, and it only surfaces in a consumer's bundle. The
|
|
281
|
+
* references in {@link KNOWN_UNRESOLVED} warn instead.
|
|
282
|
+
*/
|
|
283
|
+
export const checkShapeReferences = (packagePath, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
284
|
+
const report = yield inspectShapeReferences(packagePath, options);
|
|
285
|
+
if (report.skipped)
|
|
286
|
+
return `skipped: ${report.skipped}`;
|
|
287
|
+
const problems = [];
|
|
288
|
+
if (report.loadErrors.length > 0) {
|
|
289
|
+
problems.push(`${report.loadErrors.length} shape module(s) throw when loaded on their own:\n` +
|
|
290
|
+
report.loadErrors
|
|
291
|
+
.map((e) => ` ${e.module}: ${e.error.split('\n').join('\n ')}`)
|
|
292
|
+
.join('\n'));
|
|
293
|
+
}
|
|
294
|
+
if (report.unresolved.length > 0) {
|
|
295
|
+
problems.push(`${report.unresolved.length} property value shape(s) are not registered when the module that ` +
|
|
296
|
+
`declares the property is loaded on its own, so a query traversing them throws ` +
|
|
297
|
+
`"Shape class not found":\n` +
|
|
298
|
+
report.unresolved.map(describeReference).join('\n'));
|
|
299
|
+
}
|
|
300
|
+
if (problems.length > 0)
|
|
301
|
+
return { error: problems.join('\n') };
|
|
302
|
+
if (report.known.length > 0) {
|
|
303
|
+
return (`${report.known.length} known unresolved reference(s), tracked for a fix:\n` +
|
|
304
|
+
report.known.map(describeReference).join('\n'));
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
});
|
|
308
|
+
//# sourceMappingURL=shapeReferences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapeReferences.js","sourceRoot":"","sources":["../../../src/utils/shapeReferences.ts"],"names":[],"mappings":"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","sourcesContent":["import {execFile} from 'child_process';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport {declaresShape} from './shapeNames.js';\n\n/**\n * Checking shape references: does loading a compiled shape module, on its own,\n * register every shape its properties point at?\n *\n * A property can name its value shape by `[package, name]` instead of by class\n * (`shape: ['@_linked/schema', 'ImageObject']`), usually because importing the\n * class would close an import cycle. That names the shape without loading it.\n * A shape registers only when its module is evaluated, and consumers deep-import\n * single shape modules (`@_linked/schema/shapes/Thing`), so nothing guarantees\n * the named shape is registered when a query traverses the property. The query\n * then throws `Shape class not found for …/ImageObject` — in a production bundle,\n * far from the cause. A reference to a package under an old name\n * (`['lincd-sioc', …]`) can never resolve at all.\n *\n * So each module is loaded the way a consumer's fresh process would load it: in\n * a child `node` of its own, with nothing else imported first. Separate processes\n * rather than one process re-importing: ESM has no module-cache eviction, and the\n * shape registry lives on `globalThis`, so a second module in the same process\n * would see every shape the first one registered.\n */\n\n/** One module that could not be evaluated. */\nexport interface ShapeModuleLoadError {\n module: string;\n error: string;\n}\n\n/**\n * A property whose value shape was not registered after loading `modules`\n * (each on its own). One entry per distinct reference, however many modules\n * showed it.\n */\nexport interface UnresolvedShapeReference {\n shape: string;\n property: string;\n valueShape: string;\n modules: string[];\n /**\n * What the target package's entry does for it: `registered` — loading the\n * entry registers it, so the referencing module just does not import it;\n * `missing` — not even the entry registers it, so the name or package is\n * wrong; `not-a-dependency` — the IRI's package is neither this package nor\n * one of its dependencies; `unknown` — the entry could not be loaded.\n */\n target: 'registered' | 'missing' | 'not-a-dependency' | 'unknown';\n /** The package the IRI's slug maps to, when one does. */\n targetPackage?: string;\n}\n\nexport interface ShapeReferenceReport {\n modules: string[];\n loadErrors: ShapeModuleLoadError[];\n unresolved: UnresolvedShapeReference[];\n /** Unresolved references that match {@link KNOWN_UNRESOLVED}. */\n known: UnresolvedShapeReference[];\n /** Set when the registry could not be inspected, so nothing was checked. */\n skipped?: string;\n}\n\n/**\n * References known to be unresolved, reported as warnings rather than failures.\n *\n * This is deliberately a list in the CLI rather than an opt-out a package can\n * write for itself: every entry is a shipped bug with a tracked fix, not an\n * accepted design. No package was found with a legitimate reason to leave a\n * reference unresolved; add an opt-out mechanism when one turns up.\n *\n * - `@_linked/core`: `PropertyShape.in` names `List` by `[package, name]`\n * because `List`'s module depends on the SHACL shapes. Only core's entry\n * loads `List`, so a process that deep-imports core without its entry cannot\n * traverse `sh:in`. Tracked as core's backlog-045; core builds with `linked\n * build-all`, which must not fail on it meanwhile. Remove this entry once\n * core registers `List` wherever `PropertyShape` is registered.\n */\nexport const KNOWN_UNRESOLVED: {\n package: string;\n shape: string;\n property: string;\n valueShape: string;\n}[] = [\n {\n package: '@_linked/core',\n shape: '/shape/core/PropertyShape',\n property: 'in',\n valueShape: '/shape/core/List',\n },\n];\n\n/** arch-02: the `{slug}` in `{baseUri}shape/{slug}/{Name}`. Mirrors core's `packageNameToSlug`. */\nexport const packageNameToSlug = (packageName: string): string =>\n packageName\n .replace(/^@[^/]+\\//, '')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toLowerCase();\n\n/** The slug in a node-shape IRI, or undefined when it is not shaped like one. */\nconst slugOf = (iri: string): string | undefined =>\n iri.match(/\\/shape\\/([^/]+)\\/[^/]+$/)?.[1];\n\n/**\n * A loader hook for the child: asset imports (`import styles from\n * './X.module.css'`) evaluate to a stub. Node cannot load them and a consumer's\n * bundler always can, so without this a shape module whose file also holds a\n * component would look broken when it is not. A CSS-module stub maps every\n * class name to itself.\n */\nconst ASSET_HOOK = `\nconst ASSET = /\\\\.(css|scss|sass|less|styl|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|eot)$/i;\nexport async function load(url, context, next) {\n if (url.startsWith('file:') && ASSET.test(new URL(url).pathname)) {\n return {format: 'module', shortCircuit: true,\n source: 'export default new Proxy({}, {get: (_, k) => typeof k === \"string\" ? k : undefined});'};\n }\n return next(url, context);\n}\n`;\n\n/**\n * The script each child runs. Plain JS, passed with `-e`, so it runs the same\n * from `src` under a test runner as from the published `lib`.\n *\n * It reads the registry from `globalThis.__linkedShapeRegistry` rather than\n * importing `@_linked/core`: the module under test decides which copy of core\n * it loads, and the registry is shared by every copy.\n */\nconst PROBE = `\nimport {register} from 'node:module';\nregister('data:text/javascript,' + encodeURIComponent(${JSON.stringify(ASSET_HOOK)}));\nconst MARK = '__LINKED_SHAPE_REFERENCES__';\nconst out = {error: null, unresolved: [], registry: true};\nconst reg = () => globalThis.__linkedShapeRegistry;\nconst ownSlug = process.env.LINKED_PROBE_SLUG;\nconst report = () => {\n process.stdout.write('\\\\n' + MARK + JSON.stringify(out) + '\\\\n', () => process.exit(0));\n};\ntry {\n await import(process.env.LINKED_PROBE_MODULE);\n const r = reg();\n if (!r || !r.nodeShapeRegistry) {\n out.registry = false;\n } else {\n for (const [id, ns] of r.nodeShapeRegistry) {\n if (!id.includes('/shape/' + ownSlug + '/')) continue;\n for (const ps of ns.propertyShapes || []) {\n const v = ps.valueShape && ps.valueShape.id;\n if (v && !r.nodeShapeRegistry.has(v)) {\n out.unresolved.push({shape: id, property: ps.label, valueShape: v});\n }\n }\n }\n // For each unresolved target, load its package's entry and look again: that\n // tells a missing import apart from a name that can never resolve.\n const entries = JSON.parse(process.env.LINKED_PROBE_ENTRIES || '{}');\n const tried = {};\n for (const u of out.unresolved) {\n const slug = (u.valueShape.match(/\\\\/shape\\\\/([^/]+)\\\\/[^/]+$/) || [])[1];\n const entry = entries[slug];\n if (!entry) { u.target = 'not-a-dependency'; continue; }\n u.targetPackage = entry.name;\n if (!(slug in tried)) {\n try { await import(entry.specifier); tried[slug] = true; }\n catch (e) { tried[slug] = false; }\n }\n u.target = !tried[slug] ? 'unknown'\n : reg().nodeShapeRegistry.has(u.valueShape) ? 'registered' : 'missing';\n }\n }\n} catch (e) {\n out.error = e && e.stack ? e.stack.split('\\\\n').slice(0, 4).join('\\\\n') : String(e);\n}\nreport();\n`;\n\ninterface ProbeResult {\n error: string | null;\n registry: boolean;\n unresolved: {\n shape: string;\n property: string;\n valueShape: string;\n target: UnresolvedShapeReference['target'];\n targetPackage?: string;\n }[];\n}\n\nconst MARK = '__LINKED_SHAPE_REFERENCES__';\n\nconst runProbe = (\n modulePath: string,\n packagePath: string,\n env: Record<string, string>,\n timeoutMs: number,\n): Promise<ProbeResult> =>\n new Promise((resolve) => {\n execFile(\n process.execPath,\n ['--input-type=module', '-e', PROBE],\n {\n cwd: packagePath,\n env: {\n ...process.env,\n ...env,\n LINKED_PROBE_MODULE: 'file://' + modulePath,\n },\n timeout: timeoutMs,\n maxBuffer: 64 * 1024 * 1024,\n },\n (err, stdout, stderr) => {\n const line = String(stdout)\n .split('\\n')\n .reverse()\n .find((l) => l.startsWith(MARK));\n if (line) {\n resolve(JSON.parse(line.slice(MARK.length)));\n return;\n }\n const why =\n err && (err as any).killed\n ? `did not finish loading within ${timeoutMs}ms`\n : String(stderr).trim().split('\\n').slice(-4).join('\\n') ||\n String(err || 'exited without reporting');\n resolve({error: why, registry: true, unresolved: []});\n },\n );\n });\n\n/** Every compiled module that declares a shape: all of `shapes/`, plus decorated classes elsewhere. */\nexport const findShapeModules = (libEsm: string): string[] => {\n const found: string[] = [];\n const walk = (dir: string) => {\n for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (\n entry.name.endsWith('.js') &&\n !/\\.(test|spec|class)\\.js$/.test(entry.name)\n ) {\n const rel = path.relative(libEsm, full);\n if (\n rel.split(path.sep)[0] === 'shapes' ||\n declaresShape(fs.readFileSync(full, 'utf8'))\n ) {\n found.push(full);\n }\n }\n }\n };\n if (fs.existsSync(libEsm)) walk(libEsm);\n return found.sort();\n};\n\n/** The name the package registers its shapes under: `linkedPackage('<name>')` in `package.js`. */\nconst runtimePackageName = (libEsm: string, pkgJson: any): string => {\n const pkgModule = path.join(libEsm, 'package.js');\n if (fs.existsSync(pkgModule)) {\n const m = fs\n .readFileSync(pkgModule, 'utf8')\n .match(/linkedPackage\\(\\s*['\"]([^'\"]+)['\"]/);\n if (m) return m[1];\n }\n return pkgJson.name;\n};\n\nconst DEPENDENCY_FIELDS = [\n 'dependencies',\n 'peerDependencies',\n 'optionalDependencies',\n];\n\n/**\n * Load each shape module of the package at `packagePath` in isolation and\n * collect load errors and unresolved property value shapes.\n */\nexport const inspectShapeReferences = async (\n packagePath: string,\n {\n concurrency = Math.min(8, Math.max(1, os.cpus().length - 1)),\n timeoutMs = 60000,\n } = {},\n): Promise<ShapeReferenceReport> => {\n const libEsm = path.join(packagePath, 'lib', 'esm');\n const pkgJson = JSON.parse(\n fs.readFileSync(path.join(packagePath, 'package.json'), 'utf8'),\n );\n const modules = findShapeModules(libEsm);\n const report: ShapeReferenceReport = {\n modules: modules.map((m) => path.relative(libEsm, m)),\n loadErrors: [],\n unresolved: [],\n known: [],\n };\n if (modules.length === 0) return report;\n\n const ownName = runtimePackageName(libEsm, pkgJson);\n // The entry each slug's package is loaded through for the second look. This\n // package's own entry is loaded by path: a package cannot always import\n // itself by name.\n const entries: Record<string, {name: string; specifier: string}> = {};\n for (const field of DEPENDENCY_FIELDS) {\n for (const dep of Object.keys(pkgJson[field] || {})) {\n entries[packageNameToSlug(dep)] = {name: dep, specifier: dep};\n }\n }\n const ownEntry = path.join(\n packagePath,\n pkgJson.module || pkgJson.main || 'lib/esm/index.js',\n );\n for (const name of new Set([ownName, pkgJson.name])) {\n entries[packageNameToSlug(name)] = {\n name,\n specifier: fs.existsSync(ownEntry) ? 'file://' + ownEntry : name,\n };\n }\n const env = {\n LINKED_PROBE_SLUG: packageNameToSlug(ownName),\n LINKED_PROBE_ENTRIES: JSON.stringify(entries),\n };\n\n const results: ProbeResult[] = new Array(modules.length);\n let next = 0;\n await Promise.all(\n Array.from({length: Math.min(concurrency, modules.length)}, async () => {\n while (next < modules.length) {\n const i = next++;\n results[i] = await runProbe(modules[i], packagePath, env, timeoutMs);\n }\n }),\n );\n\n // No registry on `globalThis` after loading a module that declares a shape\n // means a core older than 2.22.3, whose registry is module-scoped. A module\n // under `shapes/` that declares none (a store, a helper) registers nothing.\n const stale = results.findIndex(\n (r, i) =>\n !r.error &&\n !r.registry &&\n declaresShape(fs.readFileSync(modules[i], 'utf8')),\n );\n if (stale !== -1) {\n report.skipped =\n `no shape registry on globalThis after loading ${report.modules[stale]}: the ` +\n '@_linked/core it loads is older than 2.22.3, whose registry cannot be inspected from outside';\n return report;\n }\n\n const byReference = new Map<string, UnresolvedShapeReference>();\n results.forEach((r, i) => {\n const module = report.modules[i];\n if (r.error) {\n report.loadErrors.push({module, error: r.error});\n return;\n }\n for (const u of r.unresolved) {\n const key = `${u.shape}\\u0000${u.property}\\u0000${u.valueShape}`;\n const existing = byReference.get(key);\n if (existing) existing.modules.push(module);\n else byReference.set(key, {...u, modules: [module]});\n }\n });\n for (const ref of byReference.values()) {\n const isKnown = KNOWN_UNRESOLVED.some(\n (k) =>\n k.package === ownName &&\n ref.shape.endsWith(k.shape) &&\n ref.property === k.property &&\n ref.valueShape.endsWith(k.valueShape),\n );\n (isKnown ? report.known : report.unresolved).push(ref);\n }\n return report;\n};\n\nconst shortIri = (iri: string) => iri.replace(/^.*\\/shape\\//, '');\n\nconst modulesList = (modules: string[]) =>\n modules.length <= 3\n ? modules.join(', ')\n : `${modules.slice(0, 3).join(', ')} and ${modules.length - 3} more`;\n\nconst describeReference = (ref: UnresolvedShapeReference): string => {\n const label = `${shortIri(ref.shape)}.${ref.property} -> ${ref.valueShape}`;\n const fix =\n ref.target === 'registered'\n ? `${ref.targetPackage}'s entry registers it, so the module that declares the property must import the module that declares ${shortIri(ref.valueShape)}`\n : ref.target === 'missing'\n ? `not registered even by ${ref.targetPackage}'s entry: the shape name is wrong or ${ref.targetPackage} does not define it`\n : ref.target === 'not-a-dependency'\n ? `'${slugOf(ref.valueShape)}' is neither this package nor one of its dependencies (an old package name?)`\n : `${ref.targetPackage}'s entry could not be loaded to tell why`;\n return ` ${label}\\n unresolved after loading ${modulesList(ref.modules)}\\n ${fix}`;\n};\n\n/**\n * Build step. Load errors and unresolved references fail the build: each is a\n * bug the package would ship, and it only surfaces in a consumer's bundle. The\n * references in {@link KNOWN_UNRESOLVED} warn instead.\n */\nexport const checkShapeReferences = async (\n packagePath: string,\n options?: Parameters<typeof inspectShapeReferences>[1],\n): Promise<true | string | {error: string}> => {\n const report = await inspectShapeReferences(packagePath, options);\n if (report.skipped) return `skipped: ${report.skipped}`;\n const problems: string[] = [];\n if (report.loadErrors.length > 0) {\n problems.push(\n `${report.loadErrors.length} shape module(s) throw when loaded on their own:\\n` +\n report.loadErrors\n .map((e) => ` ${e.module}: ${e.error.split('\\n').join('\\n ')}`)\n .join('\\n'),\n );\n }\n if (report.unresolved.length > 0) {\n problems.push(\n `${report.unresolved.length} property value shape(s) are not registered when the module that ` +\n `declares the property is loaded on its own, so a query traversing them throws ` +\n `\"Shape class not found\":\\n` +\n report.unresolved.map(describeReference).join('\\n'),\n );\n }\n if (problems.length > 0) return {error: problems.join('\\n')};\n if (report.known.length > 0) {\n return (\n `${report.known.length} known unresolved reference(s), tracked for a fix:\\n` +\n report.known.map(describeReference).join('\\n')\n );\n }\n return true;\n};\n"]}
|