@_linked/cli 1.28.1 → 1.29.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.
@@ -0,0 +1,59 @@
1
+ /** Cheap pre-filter: a self-named class expression bound to a variable. */
2
+ const SELF_NAMED_CLASS = /\b(?:let|var|const)\s+([\w$]+)\s*=\s*(?:[\w$]+\s*=\s*)*class\s+\1\b/;
3
+ const JS_ID = /\.[cm]?js$/;
4
+ /**
5
+ * Returns `code` with the runtime name of every top-level tsc-style decorated
6
+ * class expression pinned, or `null` when there is nothing to pin (or the
7
+ * module does not parse as JavaScript).
8
+ */
9
+ export function pinDecoratedClassNames(code, parse) {
10
+ var _a;
11
+ if (!code.includes('__decorate(') || !SELF_NAMED_CLASS.test(code))
12
+ return null;
13
+ let ast;
14
+ try {
15
+ ast = parse(code);
16
+ }
17
+ catch (_b) {
18
+ return null;
19
+ }
20
+ const pins = [];
21
+ for (let node of ast.body) {
22
+ if (node.type === 'ExportNamedDeclaration' && node.declaration)
23
+ node = node.declaration;
24
+ if (node.type !== 'VariableDeclaration')
25
+ continue;
26
+ for (const decl of node.declarations) {
27
+ if (decl.id.type !== 'Identifier')
28
+ continue;
29
+ // `let Foo = Foo_1 = class Foo` — tsc's form when the class refers to itself.
30
+ let init = decl.init;
31
+ while (init && init.type === 'AssignmentExpression')
32
+ init = init.right;
33
+ if ((init === null || init === void 0 ? void 0 : init.type) === 'ClassExpression' && ((_a = init.id) === null || _a === void 0 ? void 0 : _a.name) === decl.id.name) {
34
+ pins.push({ at: node.end, name: decl.id.name });
35
+ }
36
+ }
37
+ }
38
+ if (pins.length === 0)
39
+ return null;
40
+ let out = code;
41
+ for (const { at, name } of pins.reverse()) {
42
+ const pin = ` Object.defineProperty(${name}, "name", {value: ${JSON.stringify(name)}, configurable: true});`;
43
+ out = out.slice(0, at) + pin + out.slice(at);
44
+ }
45
+ return out;
46
+ }
47
+ export function pinCompiledClassNames() {
48
+ return {
49
+ name: 'linked:pin-compiled-class-names',
50
+ enforce: 'pre',
51
+ transform(code, id) {
52
+ if (!JS_ID.test(id.split('?', 1)[0]))
53
+ return null;
54
+ const pinned = pinDecoratedClassNames(code, (c) => this.parse(c));
55
+ return pinned === null ? null : { code: pinned, map: null };
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=pin-compiled-class-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pin-compiled-class-names.js","sourceRoot":"","sources":["../../../src/plugins/pin-compiled-class-names.ts"],"names":[],"mappings":"AAoCA,2EAA2E;AAC3E,MAAM,gBAAgB,GACpB,qEAAqE,CAAC;AACxE,MAAM,KAAK,GAAG,YAAY,CAAC;AAE3B;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAY,EACZ,KAAe;;IAEf,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC/D,OAAO,IAAI,CAAC;IACd,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAiC,EAAE,CAAC;IAC9C,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,wBAAwB,IAAI,IAAI,CAAC,WAAW;YAC5D,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;QAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,qBAAqB;YAAE,SAAS;QAClD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACrC,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,YAAY;gBAAE,SAAS;YAC5C,8EAA8E;YAC9E,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACrB,OAAO,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,sBAAsB;gBAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;YACvE,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,MAAK,iBAAiB,IAAI,CAAA,MAAA,IAAI,CAAC,EAAE,0CAAE,IAAI,MAAK,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;gBACvE,IAAI,CAAC,IAAI,CAAC,EAAC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAC,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,EAAC,EAAE,EAAE,IAAI,EAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,0BAA0B,IAAI,qBAAqB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,yBAAyB,CAAC;QAC7G,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,IAAI,EAAE,iCAAiC;QACvC,OAAO,EAAE,KAAK;QACd,SAAS,CAAC,IAAI,EAAE,EAAE;YAChB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YAClD,MAAM,MAAM,GAAG,sBAAsB,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAClE,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAC,CAAC;QAC5D,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// Keeps the runtime name of tsc-compiled decorated classes stable through Vite.\n//\n// A shape's IRI is built from its class name when `@linkedShape` is given no\n// explicit `name`, so anything that renames a shape class changes the shape's\n// identity. For a decorated class tsc emits\n//\n// let Foo = class Foo { ... };\n// Foo = __decorate([linkedShape], Foo);\n//\n// and esbuild, whenever it re-prints such a module, renames the inner binding\n// to avoid the shadowing: `let Foo = class Foo2`. The class is then called\n// `Foo2` at runtime — before any decorator sees it — and registers under the\n// wrong IRI. Vite re-prints a module with esbuild, without `keepNames`, in its\n// `vite:define` pass (client build: any module mentioning `process.env`,\n// `import.meta.env`, `import.meta.hot` or an app `define` key) and in the dev\n// client whenever a module mentions `process.env.NODE_ENV`. Neither pass can be\n// given `keepNames`, and `esbuild.keepNames` in the Vite config does not reach\n// them: it only reaches the minifier, which then keeps the already-wrong name.\n//\n// TypeScript source is not affected — esbuild lowers its 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.\n//\n// So before any of that runs, this plugin pins each such class's name with a\n// string literal placed right after its declaration — before the property and\n// class decorators run — which no later renaming can touch:\n//\n// let Foo = class Foo { ... }; Object.defineProperty(Foo, \"name\", {value: \"Foo\", configurable: true});\n//\n// The insertion is on the declaration's own last line and moves no code, so\n// existing source maps stay valid (`map: null`).\nimport type {Plugin} from 'vite';\n\n/** An ESTree parser — the plugin context's `this.parse` (Rollup's parser). */\nexport type ParseAst = (code: string) => any;\n\n/** Cheap pre-filter: a self-named class expression bound to a variable. */\nconst SELF_NAMED_CLASS =\n /\\b(?:let|var|const)\\s+([\\w$]+)\\s*=\\s*(?:[\\w$]+\\s*=\\s*)*class\\s+\\1\\b/;\nconst JS_ID = /\\.[cm]?js$/;\n\n/**\n * Returns `code` with the runtime name of every top-level tsc-style decorated\n * class expression pinned, or `null` when there is nothing to pin (or the\n * module does not parse as JavaScript).\n */\nexport function pinDecoratedClassNames(\n code: string,\n parse: ParseAst,\n): string | null {\n if (!code.includes('__decorate(') || !SELF_NAMED_CLASS.test(code))\n return null;\n let ast: any;\n try {\n ast = parse(code);\n } catch {\n return null;\n }\n const pins: {at: number; name: string}[] = [];\n for (let node of ast.body) {\n if (node.type === 'ExportNamedDeclaration' && node.declaration)\n node = node.declaration;\n if (node.type !== 'VariableDeclaration') continue;\n for (const decl of node.declarations) {\n if (decl.id.type !== 'Identifier') continue;\n // `let Foo = Foo_1 = class Foo` — tsc's form when the class refers to itself.\n let init = decl.init;\n while (init && init.type === 'AssignmentExpression') init = init.right;\n if (init?.type === 'ClassExpression' && init.id?.name === decl.id.name) {\n pins.push({at: node.end, name: decl.id.name});\n }\n }\n }\n if (pins.length === 0) return null;\n let out = code;\n for (const {at, name} of pins.reverse()) {\n const pin = ` Object.defineProperty(${name}, \"name\", {value: ${JSON.stringify(name)}, configurable: true});`;\n out = out.slice(0, at) + pin + out.slice(at);\n }\n return out;\n}\n\nexport function pinCompiledClassNames(): Plugin {\n return {\n name: 'linked:pin-compiled-class-names',\n enforce: 'pre',\n transform(code, id) {\n if (!JS_ID.test(id.split('?', 1)[0])) return null;\n const pinned = pinDecoratedClassNames(code, (c) => this.parse(c));\n return pinned === null ? null : {code: pinned, map: null};\n },\n };\n}\n"]}
@@ -7,10 +7,13 @@ export interface ExposedShape {
7
7
  * renamed their class.
8
8
  *
9
9
  * A shape's IRI is built from its class name unless `@linkedShape({name})`
10
- * gives one. `keepNames` in Vite's own TypeScript transform protects source
11
- * files, but published `lib/` JavaScript never goes through that transform —
12
- * only through the define pass above. So a compiled shape is exposed exactly
13
- * when it has no explicit name and its module contains a trigger. This is how
10
+ * gives one. Source files are safe: esbuild lowers their decorators to an
11
+ * anonymous `let Foo = class {`, which takes its name from the binding. Only
12
+ * published `lib/` JavaScript carries the self-named class expression, so a
13
+ * compiled shape is exposed exactly when it has no explicit name and its module
14
+ * contains a trigger. `createViteConfig` pins these names
15
+ * (`plugins/pin-compiled-class-names.ts`), but other bundlers and hand-written
16
+ * Vite configs do not, so the explicit name is still the fix. This is how
14
17
  * `@_linked/server`'s `LinkedServer` and `LincdAPI` came to register as
15
18
  * `LinkedServer2` / `LincdAPI2`.
16
19
  */
@@ -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;;;;;;;;;;;GAWG;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,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"}
@@ -27,10 +27,13 @@ const DECORATED = /\b([A-Za-z_$][\w$]*) = __decorate\(\[([\s\S]*?)\], \1\)/g;
27
27
  * renamed their class.
28
28
  *
29
29
  * A shape's IRI is built from its class name unless `@linkedShape({name})`
30
- * gives one. `keepNames` in Vite's own TypeScript transform protects source
31
- * files, but published `lib/` JavaScript never goes through that transform —
32
- * only through the define pass above. So a compiled shape is exposed exactly
33
- * when it has no explicit name and its module contains a trigger. This is how
30
+ * gives one. Source files are safe: esbuild lowers their decorators to an
31
+ * anonymous `let Foo = class {`, which takes its name from the binding. Only
32
+ * published `lib/` JavaScript carries the self-named class expression, so a
33
+ * compiled shape is exposed exactly when it has no explicit name and its module
34
+ * contains a trigger. `createViteConfig` pins these names
35
+ * (`plugins/pin-compiled-class-names.ts`), but other bundlers and hand-written
36
+ * Vite configs do not, so the explicit name is still the fix. This is how
34
37
  * `@_linked/server`'s `LinkedServer` and `LincdAPI` came to register as
35
38
  * `LinkedServer2` / `LincdAPI2`.
36
39
  */
@@ -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;AAO7E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAAc,EACd,IAAI,GAAG,EAAE,EACO,EAAE;IAClB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACnE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QAClD,IAAI,wCAAwC,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QACxE,KAAK,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,SAAS,EAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,WAAmB,EACK,EAAE;IAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE;QAC3B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAC,aAAa,EAAE,IAAI,EAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,IAAI,CAAC,IAAI,CAAC,CAAC;iBAC/B,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpC,OAAO,CAAC,IAAI,CACV,GAAG,oBAAoB,CACrB,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAC5B,CACF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,CAAC;IACb,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,CACL,GAAG,OAAO,CAAC,MAAM,yDAAyD;QAC1E,8EAA8E;QAC9E,oBAAoB,MAAM,gCAAgC;QAC1D,sCAAsC,CACvC,CAAC;AACJ,CAAC,CAAA,CAAC","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\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. `keepNames` in Vite's own TypeScript transform protects source\n * files, but published `lib/` JavaScript never goes through that transform —\n * only through the define pass above. So a compiled shape is exposed exactly\n * when it has no explicit name and its module contains a trigger. 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"]}
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;AAO7E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAAc,EACd,IAAI,GAAG,EAAE,EACO,EAAE;IAClB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACnE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QAClD,IAAI,wCAAwC,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QACxE,KAAK,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,SAAS,EAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,WAAmB,EACK,EAAE;IAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE;QAC3B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAC,aAAa,EAAE,IAAI,EAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,IAAI,CAAC,IAAI,CAAC,CAAC;iBAC/B,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpC,OAAO,CAAC,IAAI,CACV,GAAG,oBAAoB,CACrB,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAC5B,CACF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,CAAC;IACb,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,CACL,GAAG,OAAO,CAAC,MAAM,yDAAyD;QAC1E,8EAA8E;QAC9E,oBAAoB,MAAM,gCAAgC;QAC1D,sCAAsC,CACvC,CAAC;AACJ,CAAC,CAAA,CAAC","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\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"]}
@@ -126,6 +126,22 @@ export declare function discoverLinkedSourceDependencies(cwd?: string): Promise<
126
126
  * dependencies for.
127
127
  */
128
128
  export declare function discoverWorkspaces(extraGlobs?: string[], cwd?: string): Promise<WorkspaceEntry[]>;
129
+ /**
130
+ * Packages whose entry `linked start` hands to `vite.ssrLoadModule` by name
131
+ * (`@_linked/server/shapes/LinkedServer`, see `commands/start.ts`).
132
+ *
133
+ * Vite transforms an explicitly requested entry, and every file it reaches by
134
+ * RELATIVE import, whether or not the package is external. The same package's
135
+ * bare-specifier imports from anywhere else (the app's `src`, the storage
136
+ * config) are externalized to Node. Left external, the package is split across
137
+ * two loaders, and which copy a file ends up in depends on import order: a file
138
+ * the module runner has already evaluated is reused, one it has not is loaded
139
+ * by Node, and Node then loads that file's relative imports itself. Measured in
140
+ * CN: `package.js` and `ontologies/lincd-server.js` evaluated once by each
141
+ * loader, and `LocalFileStore.js` too once the package's `backend` was loaded.
142
+ * Bundling the package makes Vite the only loader for it.
143
+ */
144
+ export declare const SSR_ENTRY_PACKAGES: string[];
129
145
  /**
130
146
  * `ssr.noExternal` for the dev SSR runner. With source workspaces, only those
131
147
  * package names are bundled by Vite; everything else in node_modules (including
@@ -134,6 +150,7 @@ export declare function discoverWorkspaces(extraGlobs?: string[], cwd?: string):
134
150
  * Installed packages that depend on a workspace (`dependents`, from
135
151
  * `workspaceDependents`) are bundled too, so they share the Vite-loaded workspace.
136
152
  * Standalone (no workspaces): the context-holding framework packages are bundled.
153
+ * In both modes, `SSR_ENTRY_PACKAGES` are bundled.
137
154
  */
138
155
  export declare function ssrNoExternal(workspaces: {
139
156
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"vite-config.d.ts","sourceRoot":"","sources":["../../src/vite-config.ts"],"names":[],"mappings":"AAWA,OAAO,EAAC,YAAY,EAAC,MAAM,MAAM,CAAC;AAMlC,OAAO,KAAK,EAAC,MAAM,EAAa,MAAM,MAAM,CAAC;AAuB7C;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,IAAI,MAAM,KAAG,MAAM,GAAG,SAyDtD,CAAC;AAEF,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAQnD;AAED,MAAM,WAAW,uBAAuB;IACtC,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,OAAO,CAAC,EAAE,UAAU,GAAG,kBAAkB,CAAC;IAC1C,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAwCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAC,EAAE,EAC7C,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAsEnB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,gCAAgC,CACpD,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC,CAmC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,GAAE,MAAM,EAAO,EACzB,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC,CAyD3B;AAwCD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,EAAE,EAC5B,UAAU,GAAE,MAAM,EAAO,GACxB,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAGrB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,GAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,GAAG,MAAM,CAAM,GAC1D,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,uBAA4B,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CA8WpG"}
1
+ {"version":3,"file":"vite-config.d.ts","sourceRoot":"","sources":["../../src/vite-config.ts"],"names":[],"mappings":"AAWA,OAAO,EAAC,YAAY,EAAC,MAAM,MAAM,CAAC;AAOlC,OAAO,KAAK,EAAC,MAAM,EAAa,MAAM,MAAM,CAAC;AAuB7C;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,IAAI,MAAM,KAAG,MAAM,GAAG,SAyDtD,CAAC;AAEF,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAQnD;AAED,MAAM,WAAW,uBAAuB;IACtC,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,OAAO,CAAC,EAAE,UAAU,GAAG,kBAAkB,CAAC;IAC1C,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAwCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAC,EAAE,EAC7C,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAsEnB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,gCAAgC,CACpD,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC,CAmC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,GAAE,MAAM,EAAO,EACzB,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,cAAc,EAAE,CAAC,CAyD3B;AAwCD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,UAAsB,CAAC;AAEtD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,EAAE,EAC5B,UAAU,GAAE,MAAM,EAAO,GACxB,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAKrB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,GAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,GAAG,MAAM,CAAM,GAC1D,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,uBAA4B,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAuXpG"}
@@ -24,6 +24,7 @@ import fsExtra from 'fs-extra';
24
24
  import path from 'node:path';
25
25
  import { generateScopedName } from './utils.js';
26
26
  import { parseWorkspacePatterns, isWorkspacePathNegated } from './workspace-globs.js';
27
+ import { pinCompiledClassNames } from './plugins/pin-compiled-class-names.js';
27
28
  /**
28
29
  * Framework packages that MUST be single-instance per runtime — they hold
29
30
  * module-level state (`@_linked/core`'s shape registry, `LinkedStorage`, the query
@@ -468,6 +469,22 @@ function resolveWorkspaceSpecifier(specifier, workspaces) {
468
469
  return null;
469
470
  });
470
471
  }
472
+ /**
473
+ * Packages whose entry `linked start` hands to `vite.ssrLoadModule` by name
474
+ * (`@_linked/server/shapes/LinkedServer`, see `commands/start.ts`).
475
+ *
476
+ * Vite transforms an explicitly requested entry, and every file it reaches by
477
+ * RELATIVE import, whether or not the package is external. The same package's
478
+ * bare-specifier imports from anywhere else (the app's `src`, the storage
479
+ * config) are externalized to Node. Left external, the package is split across
480
+ * two loaders, and which copy a file ends up in depends on import order: a file
481
+ * the module runner has already evaluated is reused, one it has not is loaded
482
+ * by Node, and Node then loads that file's relative imports itself. Measured in
483
+ * CN: `package.js` and `ontologies/lincd-server.js` evaluated once by each
484
+ * loader, and `LocalFileStore.js` too once the package's `backend` was loaded.
485
+ * Bundling the package makes Vite the only loader for it.
486
+ */
487
+ export const SSR_ENTRY_PACKAGES = ['@_linked/server'];
471
488
  /**
472
489
  * `ssr.noExternal` for the dev SSR runner. With source workspaces, only those
473
490
  * package names are bundled by Vite; everything else in node_modules (including
@@ -476,11 +493,13 @@ function resolveWorkspaceSpecifier(specifier, workspaces) {
476
493
  * Installed packages that depend on a workspace (`dependents`, from
477
494
  * `workspaceDependents`) are bundled too, so they share the Vite-loaded workspace.
478
495
  * Standalone (no workspaces): the context-holding framework packages are bundled.
496
+ * In both modes, `SSR_ENTRY_PACKAGES` are bundled.
479
497
  */
480
498
  export function ssrNoExternal(workspaces, dependents = []) {
481
- if (workspaces.length === 0)
482
- return [/^@_linked\/server-utils$/, /^@_linked\/react$/];
483
- return [...new Set([...workspaces.map((w) => w.name), ...dependents])];
499
+ if (workspaces.length === 0) {
500
+ return [/^@_linked\/server-utils$/, /^@_linked\/react$/, ...SSR_ENTRY_PACKAGES];
501
+ }
502
+ return [...new Set([...workspaces.map((w) => w.name), ...dependents, ...SSR_ENTRY_PACKAGES])];
484
503
  }
485
504
  /**
486
505
  * The `process.env.*` values inlined into the browser bundle — and only there.
@@ -615,6 +634,9 @@ export function createViteConfig(opts = {}) {
615
634
  },
616
635
  }
617
636
  : null,
637
+ // Published lib JS: keep decorated shape classes named `Foo`, not the
638
+ // `Foo2` Vite's esbuild re-prints would give them — see the plugin.
639
+ pinCompiledClassNames(),
618
640
  react({
619
641
  babel: {
620
642
  parserOpts: {
@@ -660,6 +682,10 @@ export function createViteConfig(opts = {}) {
660
682
  // `.../BackendAPIStore`, and every Server.call on that shape 501'd.
661
683
  // The same mangling made registration report `Shape undefined does not
662
684
  // extend base class`.
685
+ //
686
+ // Vite honours this in the minifier only: its per-file TypeScript
687
+ // transform and its define pass both run esbuild with keepNames off.
688
+ // `pinCompiledClassNames` above covers what those passes rename.
663
689
  keepNames: true,
664
690
  } }, (isStandalone
665
691
  ? {
@@ -719,8 +745,9 @@ export function createViteConfig(opts = {}) {
719
745
  // DIFFERENT module instances → two `AppContext` objects → `useAppContext()` sees
720
746
  // no provider (null) → "Cannot destructure 'isNativeApp'" and a blank "SSR timed
721
747
  // out". Bundling makes them one instance in Vite's SSR module graph (matching how
722
- // workspace mode bundles everything). Native-dep packages (`server`/`fuseki`) stay
723
- // external so their prebuilt binaries load via Node.
748
+ // workspace mode bundles everything). `fuseki` stays external; `server` is bundled
749
+ // because `start` loads its entry through Vite (`SSR_ENTRY_PACKAGES`). Their native
750
+ // dependencies (e.g. `sharp`) are separate packages and stay external either way.
724
751
  //
725
752
  // WORKSPACE: bundle ONLY the discovered source workspaces (they resolve to `src/`
726
753
  // for HMR). Published framework packages installed under node_modules (lib-only)
@@ -730,7 +757,8 @@ export function createViteConfig(opts = {}) {
730
757
  // as `@_linked/fuseki/shapes/FusekiStore` through native `import()`, which pulled
731
758
  // a SECOND Node-loaded core and split the shape registry. Installed packages
732
759
  // that depend on a workspace (e.g. published fuseki when core itself is a
733
- // workspace) are bundled too, so they import the Vite-loaded workspace.
760
+ // workspace) are bundled too, so they import the Vite-loaded workspace. The one
761
+ // published exception is `SSR_ENTRY_PACKAGES`, which Vite loads regardless.
734
762
  //
735
763
  // NOT DEV (a release build of the backend): nothing is force-bundled. The
736
764
  // compiled backend is loaded by Node next to the installed