@animus-ui/extract 0.1.9 → 0.1.12
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/README.md +48 -1
- package/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
- package/crates/extract-v2/index.d.ts +17 -98
- package/dist/index.cjs +78 -2101
- package/dist/index.mjs +2 -2026
- package/dist/{analyze-project-args.d.ts → pipeline/analyze-project-args.d.ts} +0 -12
- package/dist/pipeline/analyze-project-args.d.ts.map +1 -0
- package/dist/pipeline/assemble-stylesheet.d.ts +23 -0
- package/dist/pipeline/assemble-stylesheet.d.ts.map +1 -0
- package/dist/pipeline/asset-placeholders.d.ts +4 -0
- package/dist/pipeline/asset-placeholders.d.ts.map +1 -0
- package/dist/pipeline/content-hash.d.ts +2 -0
- package/dist/pipeline/content-hash.d.ts.map +1 -0
- package/dist/pipeline/core-options.d.ts +96 -0
- package/dist/pipeline/core-options.d.ts.map +1 -0
- package/dist/pipeline/correlate-external-tokens.d.ts +38 -0
- package/dist/pipeline/correlate-external-tokens.d.ts.map +1 -0
- package/dist/pipeline/discover-files.d.ts +4 -0
- package/dist/pipeline/discover-files.d.ts.map +1 -0
- package/dist/pipeline/discover-packages.d.ts +55 -0
- package/dist/pipeline/discover-packages.d.ts.map +1 -0
- package/dist/pipeline/dynamic-prop-config.d.ts +22 -0
- package/dist/pipeline/dynamic-prop-config.d.ts.map +1 -0
- package/dist/pipeline/engine-adapter.d.ts +63 -0
- package/dist/pipeline/engine-adapter.d.ts.map +1 -0
- package/dist/pipeline/engine-retirement.d.ts +3 -0
- package/dist/pipeline/engine-retirement.d.ts.map +1 -0
- package/dist/pipeline/error-diagnostics.d.ts +8 -0
- package/dist/pipeline/error-diagnostics.d.ts.map +1 -0
- package/dist/{index.d.ts → pipeline/index.d.ts} +16 -6
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/internal-wire.d.ts +2 -0
- package/dist/pipeline/internal-wire.d.ts.map +1 -0
- package/dist/pipeline/manifest-diagnostics.d.ts +57 -0
- package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -0
- package/dist/pipeline/manifest-schema.d.ts +117 -0
- package/dist/pipeline/manifest-schema.d.ts.map +1 -0
- package/dist/pipeline/mdx-preprocessor.d.ts +25 -0
- package/dist/pipeline/mdx-preprocessor.d.ts.map +1 -0
- package/dist/pipeline/path-aliases.d.ts +15 -0
- package/dist/pipeline/path-aliases.d.ts.map +1 -0
- package/dist/{post-process-css.d.ts → pipeline/post-process-css.d.ts} +0 -8
- package/dist/pipeline/post-process-css.d.ts.map +1 -0
- package/dist/pipeline/prefix.d.ts +8 -0
- package/dist/pipeline/prefix.d.ts.map +1 -0
- package/dist/pipeline/replacement-plans.d.ts +22 -0
- package/dist/pipeline/replacement-plans.d.ts.map +1 -0
- package/dist/pipeline/resolve-asset.d.ts +11 -0
- package/dist/pipeline/resolve-asset.d.ts.map +1 -0
- package/dist/pipeline/run-analysis.d.ts +62 -0
- package/dist/pipeline/run-analysis.d.ts.map +1 -0
- package/dist/pipeline/source-corpus.d.ts +25 -0
- package/dist/pipeline/source-corpus.d.ts.map +1 -0
- package/dist/pipeline/source-identity.d.ts +63 -0
- package/dist/pipeline/source-identity.d.ts.map +1 -0
- package/dist/pipeline/source-ingestion.d.ts +168 -0
- package/dist/pipeline/source-ingestion.d.ts.map +1 -0
- package/dist/pipeline/static-css.d.ts +16 -0
- package/dist/pipeline/static-css.d.ts.map +1 -0
- package/dist/pipeline/structural-self-check.d.ts +12 -0
- package/dist/pipeline/structural-self-check.d.ts.map +1 -0
- package/dist/pipeline/svelte-source-adapter.d.ts +81 -0
- package/dist/pipeline/svelte-source-adapter.d.ts.map +1 -0
- package/dist/pipeline/system-config.d.ts +40 -0
- package/dist/pipeline/system-config.d.ts.map +1 -0
- package/dist/pipeline/system-props-module.d.ts +8 -0
- package/dist/pipeline/system-props-module.d.ts.map +1 -0
- package/dist/pipeline/timing-waterfall.d.ts +6 -0
- package/dist/pipeline/timing-waterfall.d.ts.map +1 -0
- package/dist/pipeline/tsconfig-paths.d.ts +7 -0
- package/dist/pipeline/tsconfig-paths.d.ts.map +1 -0
- package/dist/pipeline/unit-fallback.d.ts +2 -0
- package/dist/pipeline/unit-fallback.d.ts.map +1 -0
- package/dist/pipeline/utils.d.ts +3 -0
- package/dist/pipeline/utils.d.ts.map +1 -0
- package/dist/pipeline/watch-keys.d.ts +2 -0
- package/dist/pipeline/watch-keys.d.ts.map +1 -0
- package/dist/pipeline-DeEENy5C.mjs +2905 -0
- package/dist/pipeline-x1yBZ2UP.cjs +3386 -0
- package/dist/session/extraction-session.d.ts +240 -0
- package/dist/session/extraction-session.d.ts.map +1 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/published-set.d.ts +52 -0
- package/dist/session/published-set.d.ts.map +1 -0
- package/dist/session/resolve-packages.d.ts +7 -0
- package/dist/session/resolve-packages.d.ts.map +1 -0
- package/dist/session/session-paths.d.ts +97 -0
- package/dist/session/session-paths.d.ts.map +1 -0
- package/dist/session/singleton.d.ts +85 -0
- package/dist/session/singleton.d.ts.map +1 -0
- package/dist/session/timing.d.ts +2 -0
- package/dist/session/timing.d.ts.map +1 -0
- package/dist/session/turbopack-orchestrator.d.ts +27 -0
- package/dist/session/turbopack-orchestrator.d.ts.map +1 -0
- package/dist/session.cjs +1999 -0
- package/dist/session.mjs +1955 -0
- package/index-v2.js +0 -9
- package/package.json +22 -9
- package/dist/analyze-project-args.d.ts.map +0 -1
- package/dist/assemble-stylesheet.d.ts +0 -58
- package/dist/assemble-stylesheet.d.ts.map +0 -1
- package/dist/asset-placeholders.d.ts +0 -29
- package/dist/asset-placeholders.d.ts.map +0 -1
- package/dist/content-hash.d.ts +0 -9
- package/dist/content-hash.d.ts.map +0 -1
- package/dist/correlate-external-tokens.d.ts +0 -46
- package/dist/correlate-external-tokens.d.ts.map +0 -1
- package/dist/discover-files.d.ts +0 -10
- package/dist/discover-files.d.ts.map +0 -1
- package/dist/discover-packages.d.ts +0 -192
- package/dist/discover-packages.d.ts.map +0 -1
- package/dist/dynamic-prop-config.d.ts +0 -37
- package/dist/dynamic-prop-config.d.ts.map +0 -1
- package/dist/engine-adapter.d.ts +0 -97
- package/dist/engine-adapter.d.ts.map +0 -1
- package/dist/engine-retirement.d.ts +0 -22
- package/dist/engine-retirement.d.ts.map +0 -1
- package/dist/error-diagnostics.d.ts +0 -32
- package/dist/error-diagnostics.d.ts.map +0 -1
- package/dist/external-keyframes.d.ts +0 -23
- package/dist/external-keyframes.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/manifest-diagnostics.d.ts +0 -62
- package/dist/manifest-diagnostics.d.ts.map +0 -1
- package/dist/mdx-preprocessor.d.ts +0 -39
- package/dist/mdx-preprocessor.d.ts.map +0 -1
- package/dist/path-aliases.d.ts +0 -34
- package/dist/path-aliases.d.ts.map +0 -1
- package/dist/post-process-css.d.ts.map +0 -1
- package/dist/prefix.d.ts +0 -15
- package/dist/prefix.d.ts.map +0 -1
- package/dist/replacement-plans.d.ts +0 -46
- package/dist/replacement-plans.d.ts.map +0 -1
- package/dist/resolve-asset.d.ts +0 -14
- package/dist/resolve-asset.d.ts.map +0 -1
- package/dist/run-analysis.d.ts +0 -74
- package/dist/run-analysis.d.ts.map +0 -1
- package/dist/source-identity.d.ts +0 -93
- package/dist/source-identity.d.ts.map +0 -1
- package/dist/static-css.d.ts +0 -25
- package/dist/static-css.d.ts.map +0 -1
- package/dist/system-config.d.ts +0 -57
- package/dist/system-config.d.ts.map +0 -1
- package/dist/system-props-module.d.ts +0 -22
- package/dist/system-props-module.d.ts.map +0 -1
- package/dist/timing-waterfall.d.ts +0 -17
- package/dist/timing-waterfall.d.ts.map +0 -1
- package/dist/tsconfig-paths.d.ts +0 -8
- package/dist/tsconfig-paths.d.ts.map +0 -1
- package/dist/unit-fallback.d.ts +0 -7
- package/dist/unit-fallback.d.ts.map +0 -1
- package/dist/utils.d.ts +0 -3
- package/dist/utils.d.ts.map +0 -1
- package/dist/watch-keys.d.ts +0 -12
- package/dist/watch-keys.d.ts.map +0 -1
|
@@ -0,0 +1,2905 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path, { basename, dirname, join, normalize, posix } from "node:path";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "fs";
|
|
5
|
+
import { dirname as dirname$1, extname, isAbsolute, join as join$1, relative, resolve } from "path";
|
|
6
|
+
import { realpathSync as realpathSync$1 } from "node:fs";
|
|
7
|
+
import { createRequire as createRequire$1 } from "module";
|
|
8
|
+
import browserslist from "browserslist";
|
|
9
|
+
import { browserslistToTargets, transform } from "lightningcss";
|
|
10
|
+
import { isUnitlessProperty } from "@animus-ui/properties";
|
|
11
|
+
//#region \0rolldown/runtime.js
|
|
12
|
+
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region pipeline/assemble-stylesheet.ts
|
|
15
|
+
const ANIMUS_LAYERS = [
|
|
16
|
+
"anm-global",
|
|
17
|
+
"anm-base",
|
|
18
|
+
"anm-variants",
|
|
19
|
+
"anm-compounds",
|
|
20
|
+
"anm-states",
|
|
21
|
+
"anm-system",
|
|
22
|
+
"anm-custom"
|
|
23
|
+
];
|
|
24
|
+
function buildLayerDeclaration(layers, isCustom) {
|
|
25
|
+
return `@layer ${(isCustom ? layers : [...ANIMUS_LAYERS]).join(", ")};\n`;
|
|
26
|
+
}
|
|
27
|
+
function validateLayerOrder(layers) {
|
|
28
|
+
const expected = [...ANIMUS_LAYERS];
|
|
29
|
+
let cursor = 0;
|
|
30
|
+
for (const layer of layers) if (cursor < expected.length && layer === expected[cursor]) cursor++;
|
|
31
|
+
if (cursor < expected.length) {
|
|
32
|
+
const missing = expected.slice(cursor);
|
|
33
|
+
const found = expected.slice(0, cursor);
|
|
34
|
+
if (!expected.every((l) => layers.includes(l))) {
|
|
35
|
+
const absent = expected.filter((l) => !layers.includes(l));
|
|
36
|
+
throw new Error(`[animus-extract] Custom layers missing required layers: ${absent.join(", ")}. All 7 Animus layers must be present: ${expected.join(", ")}`);
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`[animus-extract] Custom layers have wrong order. Found ${found.join(", ")} but then expected ${missing[0]}. Required order: (${expected.join(" < ")}). You may interleave custom layers but must preserve this subsequence.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function stripLeadingLayerDeclaration(css) {
|
|
42
|
+
return css.replace(/^@layer\s+[^;{]+;\s*\n?/, "");
|
|
43
|
+
}
|
|
44
|
+
function assembleStylesheet(options) {
|
|
45
|
+
const hasCustomLayers = !!options.layers;
|
|
46
|
+
const layers = options.layers ?? ANIMUS_LAYERS;
|
|
47
|
+
if (options.layers) validateLayerOrder(options.layers);
|
|
48
|
+
const declaration = buildLayerDeclaration(layers, hasCustomLayers);
|
|
49
|
+
const variables = options.variableCss || "";
|
|
50
|
+
const componentCss = options.componentCss ? stripLeadingLayerDeclaration(options.componentCss) : "";
|
|
51
|
+
const body = [options.globalCss || "", componentCss].filter(Boolean).join("\n");
|
|
52
|
+
if (options.split) return {
|
|
53
|
+
declaration,
|
|
54
|
+
variables,
|
|
55
|
+
body
|
|
56
|
+
};
|
|
57
|
+
return [
|
|
58
|
+
declaration,
|
|
59
|
+
variables,
|
|
60
|
+
body
|
|
61
|
+
].filter(Boolean).join("\n");
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region pipeline/analyze-project-args.ts
|
|
65
|
+
/** @internal */
|
|
66
|
+
function buildAnalyzeProjectArgs(inputs) {
|
|
67
|
+
return [
|
|
68
|
+
inputs.filesJson,
|
|
69
|
+
inputs.scalesJson,
|
|
70
|
+
inputs.variableMapJson,
|
|
71
|
+
inputs.contextualVarsJson,
|
|
72
|
+
inputs.propConfigJson,
|
|
73
|
+
inputs.groupRegistryJson,
|
|
74
|
+
inputs.packageResolutionJson,
|
|
75
|
+
inputs.devMode,
|
|
76
|
+
inputs.emitterConfigJson,
|
|
77
|
+
inputs.selectorAliasesJson,
|
|
78
|
+
null,
|
|
79
|
+
inputs.globalStyleBlocksJson,
|
|
80
|
+
inputs.pathAliasesJson,
|
|
81
|
+
inputs.keyframesJson,
|
|
82
|
+
inputs.staticCssJson,
|
|
83
|
+
inputs.conditionAliasesJson,
|
|
84
|
+
inputs.externalDirsJson,
|
|
85
|
+
inputs.transformSourcesJson
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region pipeline/content-hash.ts
|
|
90
|
+
function contentHash(source) {
|
|
91
|
+
return createHash("md5").update(source).digest("hex");
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region pipeline/internal-wire.ts
|
|
95
|
+
function parseInternalWire(json, wire) {
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(json);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new TypeError(`[animus] ${wire} is not valid JSON. animus produces this wire, so this is an engine bug rather than a configuration error: ${String(error)}`, { cause: error });
|
|
101
|
+
}
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region pipeline/mdx-preprocessor.ts
|
|
106
|
+
/**
|
|
107
|
+
* MDX compiled to scanner-consumable JSX, plus the extension sets every
|
|
108
|
+
* driver imports. `@mdx-js/mdx` is an optional peer, so its import is lazy.
|
|
109
|
+
*/
|
|
110
|
+
const DEFAULT_EXTENSIONS = [
|
|
111
|
+
".ts",
|
|
112
|
+
".tsx",
|
|
113
|
+
".js",
|
|
114
|
+
".jsx",
|
|
115
|
+
".mdx"
|
|
116
|
+
];
|
|
117
|
+
/**
|
|
118
|
+
* The one engine-transform file gate every driver imports — the suffixes the
|
|
119
|
+
* engine's parser accepts. `.mdx` is preprocessed to `.tsx` before it.
|
|
120
|
+
*/
|
|
121
|
+
const ENGINE_TRANSFORM_EXTENSIONS = [
|
|
122
|
+
"ts",
|
|
123
|
+
"tsx",
|
|
124
|
+
"js",
|
|
125
|
+
"jsx",
|
|
126
|
+
"mjs"
|
|
127
|
+
];
|
|
128
|
+
const ENGINE_TRANSFORM_RE = new RegExp(`\\.(?:${ENGINE_TRANSFORM_EXTENSIONS.join("|")})$`);
|
|
129
|
+
function isEngineTransformExtension(path) {
|
|
130
|
+
return ENGINE_TRANSFORM_RE.test(path);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Compile MDX to scanner-consumable JSX. `missing-dep` means the consumer
|
|
134
|
+
* must install `@mdx-js/mdx`; hosts warn and skip the file on `error`.
|
|
135
|
+
*/
|
|
136
|
+
async function preprocessMdx(source, filename) {
|
|
137
|
+
const mdxMod = await import("@mdx-js/mdx").catch(() => null);
|
|
138
|
+
if (mdxMod === null) return { kind: "missing-dep" };
|
|
139
|
+
try {
|
|
140
|
+
const vfile = await mdxMod.compile(source, {
|
|
141
|
+
outputFormat: "program",
|
|
142
|
+
development: false,
|
|
143
|
+
jsx: true
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
kind: "ok",
|
|
147
|
+
source: `/* @mdx-source: ${filename} */\n${String(vfile)}`
|
|
148
|
+
};
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return {
|
|
151
|
+
kind: "error",
|
|
152
|
+
error: err instanceof Error ? err.message : String(err)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region pipeline/svelte-source-adapter.ts
|
|
158
|
+
const isCompilerReference = (value) => Object(value) === value;
|
|
159
|
+
const isCompilerCallable = (value) => {
|
|
160
|
+
if (!isCompilerReference(value)) return false;
|
|
161
|
+
try {
|
|
162
|
+
Function.prototype.toString.call(value);
|
|
163
|
+
return true;
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const isCompilerRecord = (value) => isCompilerReference(value) && !isCompilerCallable(value);
|
|
169
|
+
const acceptsCompilerPrimitive = (value, read) => {
|
|
170
|
+
if (isCompilerReference(value)) return false;
|
|
171
|
+
try {
|
|
172
|
+
read();
|
|
173
|
+
return true;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const isCompilerString = (value) => acceptsCompilerPrimitive(value, () => String.prototype.valueOf.call(value));
|
|
179
|
+
const isCompilerNumber = (value) => acceptsCompilerPrimitive(value, () => Number.prototype.valueOf.call(value));
|
|
180
|
+
function isNode(value) {
|
|
181
|
+
return isCompilerRecord(value) && isCompilerString(value.type) && isCompilerNumber(value.start) && isCompilerNumber(value.end);
|
|
182
|
+
}
|
|
183
|
+
function childNode(value) {
|
|
184
|
+
return isNode(value) ? value : null;
|
|
185
|
+
}
|
|
186
|
+
function isProgramNode(value) {
|
|
187
|
+
return isNode(value) && Array.isArray(value.body) && value.body.every((child) => isNode(child));
|
|
188
|
+
}
|
|
189
|
+
function isScriptNode(value) {
|
|
190
|
+
return isNode(value) && isProgramNode(value.content);
|
|
191
|
+
}
|
|
192
|
+
function parseSvelteAst(value) {
|
|
193
|
+
if (value.module !== void 0 && value.module !== null && !isScriptNode(value.module)) throw new TypeError("svelte/compiler returned an invalid module script");
|
|
194
|
+
if (value.instance !== void 0 && value.instance !== null && !isScriptNode(value.instance)) throw new TypeError("svelte/compiler returned an invalid instance script");
|
|
195
|
+
if (!isCompilerRecord(value.fragment)) throw new TypeError("svelte/compiler returned an invalid fragment");
|
|
196
|
+
return {
|
|
197
|
+
module: value.module,
|
|
198
|
+
instance: value.instance,
|
|
199
|
+
fragment: value.fragment
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function nodeName(node) {
|
|
203
|
+
return node?.type === "Identifier" && isCompilerString(node.name) ? node.name : null;
|
|
204
|
+
}
|
|
205
|
+
/** Adaptation touches one source at a time, so a last-value memo runs the
|
|
206
|
+
* all-ASCII check once per file rather than per span endpoint. */
|
|
207
|
+
let asciiMemoSource = null;
|
|
208
|
+
let asciiMemoValue = false;
|
|
209
|
+
function isAsciiOnly(source) {
|
|
210
|
+
if (asciiMemoSource !== source) {
|
|
211
|
+
asciiMemoSource = source;
|
|
212
|
+
asciiMemoValue = Buffer.byteLength(source) === source.length;
|
|
213
|
+
}
|
|
214
|
+
return asciiMemoValue;
|
|
215
|
+
}
|
|
216
|
+
function byteOffset(source, characterOffset) {
|
|
217
|
+
if (isAsciiOnly(source)) return characterOffset;
|
|
218
|
+
return Buffer.byteLength(source.slice(0, characterOffset));
|
|
219
|
+
}
|
|
220
|
+
function byteSpan(source, node) {
|
|
221
|
+
return {
|
|
222
|
+
start: byteOffset(source, node.start),
|
|
223
|
+
end: byteOffset(source, node.end)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function sourcePosition(source, characterOffset) {
|
|
227
|
+
let line = 1;
|
|
228
|
+
let lineStart = 0;
|
|
229
|
+
for (let index = 0; index < characterOffset; index += 1) {
|
|
230
|
+
const code = source.charCodeAt(index);
|
|
231
|
+
if (code === 13) {
|
|
232
|
+
if (index + 1 < characterOffset && source.charCodeAt(index + 1) === 10) index += 1;
|
|
233
|
+
line += 1;
|
|
234
|
+
lineStart = index + 1;
|
|
235
|
+
} else if (code === 10 || code === 8232 || code === 8233) {
|
|
236
|
+
line += 1;
|
|
237
|
+
lineStart = index + 1;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
line,
|
|
242
|
+
column: characterOffset - lineStart
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function sourceLocation(source, node) {
|
|
246
|
+
return {
|
|
247
|
+
start: sourcePosition(source, node.start),
|
|
248
|
+
end: sourcePosition(source, node.end)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function createsNestedBindingScope(node) {
|
|
252
|
+
return node.type === "BlockStatement" || node.type === "CatchClause" || node.type === "ClassBody" || node.type === "ClassDeclaration" || node.type === "ClassExpression" || node.type === "ForInStatement" || node.type === "ForOfStatement" || node.type === "ForStatement" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "StaticBlock" || node.type === "SwitchStatement" || node.type === "TSModuleBlock";
|
|
253
|
+
}
|
|
254
|
+
function walk(node, visit, nestedScopeDepth = 0) {
|
|
255
|
+
visit(node, nestedScopeDepth);
|
|
256
|
+
const childScopeDepth = nestedScopeDepth + (createsNestedBindingScope(node) ? 1 : 0);
|
|
257
|
+
for (const [key, value] of Object.entries(node)) {
|
|
258
|
+
if (key === "loc" || key === "start" || key === "end") continue;
|
|
259
|
+
if (Array.isArray(value)) {
|
|
260
|
+
for (const item of value) if (isNode(item)) walk(item, visit, childScopeDepth);
|
|
261
|
+
} else if (isNode(value)) walk(value, visit, childScopeDepth);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Svelte `Fragment` containers carry no `start`/`end`, so the `isNode` gate
|
|
266
|
+
* would stop at each one: descend plain objects, visit span-carrying nodes.
|
|
267
|
+
*/
|
|
268
|
+
function walkFragment(value, visit) {
|
|
269
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
270
|
+
const descend = (current) => {
|
|
271
|
+
if (Array.isArray(current)) {
|
|
272
|
+
for (const item of current) descend(item);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!isCompilerRecord(current)) return;
|
|
276
|
+
if (seen.has(current)) return;
|
|
277
|
+
seen.add(current);
|
|
278
|
+
if (isNode(current)) visit(current);
|
|
279
|
+
for (const [key, child] of Object.entries(current)) {
|
|
280
|
+
if (key === "loc" || key === "start" || key === "end" || key === "metadata" || key === "parent") continue;
|
|
281
|
+
descend(child);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
descend(value);
|
|
285
|
+
}
|
|
286
|
+
function importBindings(program) {
|
|
287
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const declaration of program.body) {
|
|
289
|
+
if (declaration.type !== "ImportDeclaration" || declaration.importKind === "type" || !Array.isArray(declaration.specifiers)) continue;
|
|
290
|
+
for (const value of declaration.specifiers) {
|
|
291
|
+
if (!isNode(value) || value.importKind === "type") continue;
|
|
292
|
+
const local = childNode(value.local);
|
|
293
|
+
const localName = nodeName(local);
|
|
294
|
+
if (!local || !localName) continue;
|
|
295
|
+
const sourceNode = childNode(declaration.source);
|
|
296
|
+
const importSource = sourceNode && isCompilerString(sourceNode.value) ? sourceNode.value : null;
|
|
297
|
+
if (!importSource) continue;
|
|
298
|
+
const namedImported = importedName(value);
|
|
299
|
+
if (value.type === "ImportSpecifier" && namedImported === null) continue;
|
|
300
|
+
const importedNode = childNode(value.imported);
|
|
301
|
+
const kind = value.type === "ImportSpecifier" ? importedNode?.type === "Literal" ? "string-named" : namedImported === "default" ? "named-default" : "named" : value.type === "ImportDefaultSpecifier" ? "default" : value.type === "ImportNamespaceSpecifier" ? "namespace" : null;
|
|
302
|
+
if (kind) bindings.set(localName, {
|
|
303
|
+
kind,
|
|
304
|
+
source: importSource,
|
|
305
|
+
imported: kind === "default" || kind === "named-default" ? "default" : kind === "namespace" ? "*" : namedImported,
|
|
306
|
+
declaration,
|
|
307
|
+
specifier: value,
|
|
308
|
+
local
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return bindings;
|
|
313
|
+
}
|
|
314
|
+
function diagnostic(code, message, originalPath, source, node, location) {
|
|
315
|
+
const result = {
|
|
316
|
+
code,
|
|
317
|
+
message,
|
|
318
|
+
originalPath
|
|
319
|
+
};
|
|
320
|
+
if (node) {
|
|
321
|
+
result.span = byteSpan(source, node);
|
|
322
|
+
result.location = location ?? sourceLocation(source, node);
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
function parseCompilerError(value) {
|
|
327
|
+
if (!isCompilerRecord(value)) return null;
|
|
328
|
+
const parsed = { positions: Array.isArray(value.position) ? value.position : [] };
|
|
329
|
+
if (isCompilerRecord(value.start)) parsed.start = value.start;
|
|
330
|
+
if (isCompilerRecord(value.end)) parsed.end = value.end;
|
|
331
|
+
return parsed;
|
|
332
|
+
}
|
|
333
|
+
function compilerParseErrorRange(error, source) {
|
|
334
|
+
const candidate = parseCompilerError(error);
|
|
335
|
+
if (candidate === null) return null;
|
|
336
|
+
const positions = candidate.positions;
|
|
337
|
+
const startCharacter = isCompilerNumber(candidate.start?.character) ? candidate.start.character : isCompilerNumber(positions[0]) ? positions[0] : null;
|
|
338
|
+
const endCharacter = isCompilerNumber(candidate.end?.character) ? candidate.end.character : isCompilerNumber(positions[1]) ? positions[1] : startCharacter;
|
|
339
|
+
if (startCharacter === null || endCharacter === null || startCharacter < 0 || endCharacter < startCharacter || endCharacter > source.length) return null;
|
|
340
|
+
const node = {
|
|
341
|
+
type: "SvelteParseError",
|
|
342
|
+
start: startCharacter,
|
|
343
|
+
end: endCharacter
|
|
344
|
+
};
|
|
345
|
+
const fallback = sourceLocation(source, node);
|
|
346
|
+
const point = (value, fallbackPoint) => ({
|
|
347
|
+
line: isCompilerNumber(value?.line) ? value.line : fallbackPoint.line,
|
|
348
|
+
column: isCompilerNumber(value?.column) ? value.column : fallbackPoint.column
|
|
349
|
+
});
|
|
350
|
+
return {
|
|
351
|
+
node,
|
|
352
|
+
location: {
|
|
353
|
+
start: point(candidate.start, fallback.start),
|
|
354
|
+
end: point(candidate.end, fallback.end)
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function calleeParts(node) {
|
|
359
|
+
if (node.type !== "CallExpression") return null;
|
|
360
|
+
const callee = childNode(node.callee);
|
|
361
|
+
if (!callee || callee.type !== "MemberExpression") return null;
|
|
362
|
+
const object = childNode(callee.object);
|
|
363
|
+
const property = childNode(callee.property);
|
|
364
|
+
if (!object || !property) return null;
|
|
365
|
+
if ((nodeName(property) ?? (property.type === "Literal" && property.value === "attrs" ? "attrs" : null)) !== "attrs") return null;
|
|
366
|
+
return {
|
|
367
|
+
object,
|
|
368
|
+
property,
|
|
369
|
+
computed: callee.computed === true,
|
|
370
|
+
optional: callee.optional === true || node.optional === true
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function resolverImportAccess(receiver, bindings) {
|
|
374
|
+
const directName = nodeName(receiver);
|
|
375
|
+
if (directName) {
|
|
376
|
+
const binding = bindings.get(directName);
|
|
377
|
+
if (!binding) return null;
|
|
378
|
+
return {
|
|
379
|
+
binding,
|
|
380
|
+
resolver: receiver,
|
|
381
|
+
displayName: directName,
|
|
382
|
+
request: {
|
|
383
|
+
source: binding.source,
|
|
384
|
+
imported: binding.imported,
|
|
385
|
+
local: directName,
|
|
386
|
+
access: {
|
|
387
|
+
kind: "direct",
|
|
388
|
+
importKind: binding.kind
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
if (receiver.type !== "MemberExpression" || receiver.computed === true || receiver.optional === true) return null;
|
|
394
|
+
const namespaceNode = childNode(receiver.object);
|
|
395
|
+
const memberNode = childNode(receiver.property);
|
|
396
|
+
const namespaceName = nodeName(namespaceNode);
|
|
397
|
+
const memberName = nodeName(memberNode) ?? (memberNode?.type === "Literal" && isCompilerString(memberNode.value) ? memberNode.value : null);
|
|
398
|
+
if (!namespaceNode || !namespaceName || !memberName) return null;
|
|
399
|
+
const binding = bindings.get(namespaceName);
|
|
400
|
+
if (!binding || binding.kind !== "namespace") return null;
|
|
401
|
+
return {
|
|
402
|
+
binding,
|
|
403
|
+
resolver: receiver,
|
|
404
|
+
displayName: `${namespaceName}.${memberName}`,
|
|
405
|
+
request: {
|
|
406
|
+
source: binding.source,
|
|
407
|
+
imported: memberName,
|
|
408
|
+
local: namespaceName,
|
|
409
|
+
access: {
|
|
410
|
+
kind: "namespace-member",
|
|
411
|
+
importKind: "namespace"
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function isSupportedResolverAccess(access) {
|
|
417
|
+
return access.binding.kind === "named" && access.request.access.kind === "direct";
|
|
418
|
+
}
|
|
419
|
+
function resolverCallSite(node, bindings) {
|
|
420
|
+
if (node.type !== "CallExpression") return null;
|
|
421
|
+
const parts = calleeParts(node);
|
|
422
|
+
if (parts) {
|
|
423
|
+
const access = resolverImportAccess(parts.object, bindings);
|
|
424
|
+
if (!access) return null;
|
|
425
|
+
return {
|
|
426
|
+
access,
|
|
427
|
+
form: "attrs",
|
|
428
|
+
computed: parts.computed,
|
|
429
|
+
optional: parts.optional
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const callee = childNode(node.callee);
|
|
433
|
+
if (!callee) return null;
|
|
434
|
+
const access = resolverImportAccess(callee, bindings);
|
|
435
|
+
if (!access) return null;
|
|
436
|
+
return {
|
|
437
|
+
access,
|
|
438
|
+
form: "callable",
|
|
439
|
+
computed: callee.type === "MemberExpression" && callee.computed === true,
|
|
440
|
+
optional: node.optional === true || callee.type === "MemberExpression" && callee.optional === true
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function argumentSpan(argumentsList, fallback) {
|
|
444
|
+
if (argumentsList.length === 0) return fallback;
|
|
445
|
+
return {
|
|
446
|
+
type: "ArgumentRange",
|
|
447
|
+
start: argumentsList[0].start,
|
|
448
|
+
end: argumentsList[argumentsList.length - 1].end
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function propertyName(node) {
|
|
452
|
+
const key = childNode(node.key);
|
|
453
|
+
if (!key) return null;
|
|
454
|
+
if (key.type === "Identifier" && isCompilerString(key.name)) return key.name;
|
|
455
|
+
if (key.type === "Literal" && isCompilerString(key.value)) return key.value;
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
function isJsxAttributeName(name) {
|
|
459
|
+
return /^[A-Za-z_$][A-Za-z0-9_$-]*$/.test(name);
|
|
460
|
+
}
|
|
461
|
+
function tsxIncompatibleValue(value) {
|
|
462
|
+
let incompatible = null;
|
|
463
|
+
walk(value, (node) => {
|
|
464
|
+
if (incompatible) return;
|
|
465
|
+
if (node.type === "TSTypeAssertion" || node.type === "ArrowFunctionExpression" && childNode(node.typeParameters) !== null) incompatible = node;
|
|
466
|
+
});
|
|
467
|
+
return incompatible;
|
|
468
|
+
}
|
|
469
|
+
function readProperties(argument, source, originalPath) {
|
|
470
|
+
if (!Array.isArray(argument.properties)) return diagnostic("SVELTE_ATTRS_ARGUMENT_UNRESOLVED", "Resolver .attrs() requires an object literal with statically named properties.", originalPath, source, argument);
|
|
471
|
+
const properties = [];
|
|
472
|
+
for (const value of argument.properties) {
|
|
473
|
+
if (!isNode(value)) continue;
|
|
474
|
+
if (value.type === "SpreadElement") return diagnostic("SVELTE_ATTRS_SPREAD_UNRESOLVED", "Resolver .attrs() object spreads are unresolved; enumerate properties explicitly.", originalPath, source, value);
|
|
475
|
+
if (value.type !== "Property") return diagnostic("SVELTE_ATTRS_PROPERTY_UNSUPPORTED", "Resolver .attrs() accepts ordinary object properties only.", originalPath, source, value);
|
|
476
|
+
if (value.computed === true) return diagnostic("SVELTE_ATTRS_COMPUTED_KEY", "Resolver .attrs() computed property names cannot be attributed safely.", originalPath, source, value);
|
|
477
|
+
if (value.method === true || value.kind !== "init") return diagnostic("SVELTE_ATTRS_PROPERTY_UNSUPPORTED", "Resolver .attrs() methods and accessors cannot be projected as attributes.", originalPath, source, value);
|
|
478
|
+
const key = childNode(value.key);
|
|
479
|
+
const propertyValue = childNode(value.value);
|
|
480
|
+
const name = propertyName(value);
|
|
481
|
+
if (!key || !propertyValue || !name || !isJsxAttributeName(name)) return diagnostic("SVELTE_ATTRS_PROPERTY_UNSUPPORTED", "Resolver .attrs() property names must be JSX-compatible identifier or string names.", originalPath, source, value);
|
|
482
|
+
const incompatibleValue = tsxIncompatibleValue(propertyValue);
|
|
483
|
+
if (incompatibleValue) return diagnostic("SVELTE_ATTRS_VALUE_UNSUPPORTED", "Resolver .attrs() values using angle-bracket type assertions or generic arrow parameters cannot be projected into TSX safely.", originalPath, source, incompatibleValue);
|
|
484
|
+
properties.push({
|
|
485
|
+
name,
|
|
486
|
+
property: value,
|
|
487
|
+
key,
|
|
488
|
+
value: propertyValue
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return properties;
|
|
492
|
+
}
|
|
493
|
+
var VirtualSourceBuilder = class {
|
|
494
|
+
originalSource;
|
|
495
|
+
chunks = [];
|
|
496
|
+
bytes = 0;
|
|
497
|
+
mappings = [];
|
|
498
|
+
constructor(originalSource) {
|
|
499
|
+
this.originalSource = originalSource;
|
|
500
|
+
}
|
|
501
|
+
append(text) {
|
|
502
|
+
this.chunks.push(text);
|
|
503
|
+
this.bytes += Buffer.byteLength(text);
|
|
504
|
+
}
|
|
505
|
+
appendMapped(text, kind, original) {
|
|
506
|
+
const start = this.bytes;
|
|
507
|
+
this.append(text);
|
|
508
|
+
this.mappings.push({
|
|
509
|
+
kind,
|
|
510
|
+
generated: {
|
|
511
|
+
start,
|
|
512
|
+
end: this.bytes
|
|
513
|
+
},
|
|
514
|
+
original: byteSpan(this.originalSource, original)
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
toString() {
|
|
518
|
+
return this.chunks.join("");
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
function importedName(specifier) {
|
|
522
|
+
const imported = childNode(specifier.imported);
|
|
523
|
+
if (!imported) return null;
|
|
524
|
+
if (imported.type === "Identifier" && isCompilerString(imported.name)) return imported.name;
|
|
525
|
+
if (imported.type === "Literal" && isCompilerString(imported.value)) return imported.value;
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
function renderImport(declaration, witnessedBindings, source) {
|
|
529
|
+
if (!Array.isArray(declaration.specifiers)) return null;
|
|
530
|
+
const names = [];
|
|
531
|
+
const locals = [];
|
|
532
|
+
for (const value of declaration.specifiers) {
|
|
533
|
+
if (!isNode(value) || value.type !== "ImportSpecifier") continue;
|
|
534
|
+
const localName = nodeName(childNode(value.local));
|
|
535
|
+
if (!localName || !witnessedBindings.has(localName)) continue;
|
|
536
|
+
names.push(source.slice(value.start, value.end));
|
|
537
|
+
locals.push(localName);
|
|
538
|
+
}
|
|
539
|
+
if (names.length === 0) return null;
|
|
540
|
+
const sourceNode = childNode(declaration.source);
|
|
541
|
+
const sourceText = sourceNode && isCompilerString(sourceNode.raw) ? sourceNode.raw : sourceNode && isCompilerString(sourceNode.value) ? JSON.stringify(sourceNode.value) : null;
|
|
542
|
+
return sourceText ? {
|
|
543
|
+
text: `import { ${names.join(", ")} } from ${sourceText};\n`,
|
|
544
|
+
locals
|
|
545
|
+
} : null;
|
|
546
|
+
}
|
|
547
|
+
/** Memoizes success only: a missing optional peer retries per file, so
|
|
548
|
+
* installing `svelte` mid-session recovers without a restart. */
|
|
549
|
+
let compilerMemo = null;
|
|
550
|
+
async function loadCompiler() {
|
|
551
|
+
if (compilerMemo) return compilerMemo;
|
|
552
|
+
compilerMemo = await import("svelte/compiler").catch(() => null);
|
|
553
|
+
return compilerMemo;
|
|
554
|
+
}
|
|
555
|
+
function projectScope(script, scope, source, originalPath, options, inherited, precomputedOwnBindings) {
|
|
556
|
+
const ownBindings = precomputedOwnBindings ?? importBindings(script.content);
|
|
557
|
+
const bindings = inherited ? new Map([...inherited.bindings, ...ownBindings]) : ownBindings;
|
|
558
|
+
const witnesses = [];
|
|
559
|
+
const diagnostics = [];
|
|
560
|
+
if (bindings.size > 0) walk(script.content, (node, nestedScopeDepth) => {
|
|
561
|
+
const site = resolverCallSite(node, bindings);
|
|
562
|
+
if (!site) return;
|
|
563
|
+
const { access, form } = site;
|
|
564
|
+
const attribution = options.attributeResolver(access.request);
|
|
565
|
+
if (attribution === "other") return;
|
|
566
|
+
const resolverName = access.displayName;
|
|
567
|
+
const callDisplay = form === "attrs" ? `${resolverName}.attrs()` : `${resolverName}()`;
|
|
568
|
+
if (attribution === "unsupported-resolver-form" || !isSupportedResolverAccess(access)) {
|
|
569
|
+
diagnostics.push(diagnostic("SVELTE_ATTRS_IMPORT_UNSUPPORTED", `Resolver '${resolverName}' uses an import/access form that the extraction engine cannot attribute to its exported .asClass() binding.`, originalPath, source, access.resolver));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (nestedScopeDepth > 0) {
|
|
573
|
+
diagnostics.push(diagnostic("SVELTE_ATTRS_SCOPE_UNSUPPORTED", `Resolver '${callDisplay}' is inside a nested binding scope; move the call to the top-level script scope so its import identity is unambiguous.`, originalPath, source, access.resolver));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (site.computed || site.optional) {
|
|
577
|
+
diagnostics.push(diagnostic("SVELTE_ATTRS_CALLEE_UNSUPPORTED", `Resolver '${resolverName}' must be called directly as ${resolverName}(...) or ${resolverName}.attrs(...).`, originalPath, source, node));
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const argumentsList = Array.isArray(node.arguments) ? node.arguments.filter(isNode) : [];
|
|
581
|
+
if (argumentsList.length === 0) {
|
|
582
|
+
witnesses.push({
|
|
583
|
+
binding: access.binding,
|
|
584
|
+
resolver: access.resolver,
|
|
585
|
+
properties: []
|
|
586
|
+
});
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (argumentsList.length !== 1 || argumentsList[0].type !== "ObjectExpression") {
|
|
590
|
+
diagnostics.push(diagnostic("SVELTE_ATTRS_ARGUMENT_UNRESOLVED", `Resolver '${callDisplay}' accepts no argument or one object literal in Svelte projection.`, originalPath, source, argumentSpan(argumentsList, node)));
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const properties = readProperties(argumentsList[0], source, originalPath);
|
|
594
|
+
if (!Array.isArray(properties)) {
|
|
595
|
+
diagnostics.push(properties);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
witnesses.push({
|
|
599
|
+
binding: access.binding,
|
|
600
|
+
resolver: access.resolver,
|
|
601
|
+
properties
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
if (diagnostics.length > 0 || witnesses.length === 0) return { diagnostics };
|
|
605
|
+
const witnessedBindings = new Set(witnesses.map((witness) => nodeName(witness.binding.local)));
|
|
606
|
+
const builder = new VirtualSourceBuilder(source);
|
|
607
|
+
const pendingNames = new Set(witnessedBindings);
|
|
608
|
+
const renderDeclarations = (declarations) => {
|
|
609
|
+
for (const declaration of declarations) {
|
|
610
|
+
if (declaration.type !== "ImportDeclaration") continue;
|
|
611
|
+
const rendered = renderImport(declaration, pendingNames, source);
|
|
612
|
+
if (!rendered) continue;
|
|
613
|
+
builder.append(rendered.text);
|
|
614
|
+
for (const local of rendered.locals) pendingNames.delete(local);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
renderDeclarations(script.content.body);
|
|
618
|
+
if (inherited) renderDeclarations(inherited.importDeclarations);
|
|
619
|
+
for (const witness of witnesses) {
|
|
620
|
+
const resolverName = nodeName(witness.resolver);
|
|
621
|
+
builder.append("<");
|
|
622
|
+
builder.appendMapped(resolverName, "resolver", witness.resolver);
|
|
623
|
+
for (const property of witness.properties) {
|
|
624
|
+
builder.append(" ");
|
|
625
|
+
builder.appendMapped(property.name, "attribute", property.key);
|
|
626
|
+
builder.append("={");
|
|
627
|
+
builder.appendMapped(source.slice(property.value.start, property.value.end), "value", property.value);
|
|
628
|
+
builder.append("}");
|
|
629
|
+
}
|
|
630
|
+
builder.append(" />;\n");
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
diagnostics: [],
|
|
634
|
+
entry: {
|
|
635
|
+
scope,
|
|
636
|
+
path: `${originalPath}.${scope}.tsx`,
|
|
637
|
+
source: builder.toString(),
|
|
638
|
+
mappings: builder.mappings
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Project Svelte script scopes into parser-only TSX witnesses from SOURCE,
|
|
644
|
+
* never compiler output. Unsupported call shapes fail the whole source closed.
|
|
645
|
+
*/
|
|
646
|
+
async function adaptSvelteSource(source, originalPath, options) {
|
|
647
|
+
const original = {
|
|
648
|
+
path: originalPath,
|
|
649
|
+
hash: contentHash(source)
|
|
650
|
+
};
|
|
651
|
+
const compiler = await loadCompiler();
|
|
652
|
+
if (!compiler) return {
|
|
653
|
+
kind: "missing-dep",
|
|
654
|
+
original,
|
|
655
|
+
dependency: "svelte/compiler"
|
|
656
|
+
};
|
|
657
|
+
let compilerAst;
|
|
658
|
+
try {
|
|
659
|
+
compilerAst = compiler.parse(source, {
|
|
660
|
+
filename: originalPath,
|
|
661
|
+
modern: true
|
|
662
|
+
});
|
|
663
|
+
} catch (error) {
|
|
664
|
+
const range = compilerParseErrorRange(error, source);
|
|
665
|
+
return {
|
|
666
|
+
kind: "error",
|
|
667
|
+
original,
|
|
668
|
+
diagnostics: [diagnostic("SVELTE_PARSE_ERROR", error instanceof Error ? error.message : String(error), originalPath, source, range?.node, range?.location)]
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const ast = parseSvelteAst(compilerAst);
|
|
672
|
+
const projections = [];
|
|
673
|
+
const moduleBindings = ast.module ? importBindings(ast.module.content) : void 0;
|
|
674
|
+
const instanceBindings = ast.instance ? importBindings(ast.instance.content) : void 0;
|
|
675
|
+
const moduleScope = ast.module && moduleBindings ? {
|
|
676
|
+
bindings: moduleBindings,
|
|
677
|
+
importDeclarations: ast.module.content.body.filter((declaration) => declaration.type === "ImportDeclaration")
|
|
678
|
+
} : void 0;
|
|
679
|
+
if (ast.module) projections.push(projectScope(ast.module, "module", source, originalPath, options, void 0, moduleBindings));
|
|
680
|
+
if (ast.instance) projections.push(projectScope(ast.instance, "instance", source, originalPath, options, moduleScope, instanceBindings));
|
|
681
|
+
const diagnostics = projections.flatMap((projection) => projection.diagnostics);
|
|
682
|
+
if (ast.fragment) {
|
|
683
|
+
const scriptBindings = instanceBindings ?? /* @__PURE__ */ new Map();
|
|
684
|
+
const fragmentBindings = moduleScope ? new Map([...moduleScope.bindings, ...scriptBindings]) : scriptBindings;
|
|
685
|
+
if (fragmentBindings.size > 0) walkFragment(ast.fragment, (node) => {
|
|
686
|
+
const site = resolverCallSite(node, fragmentBindings);
|
|
687
|
+
if (!site) return;
|
|
688
|
+
if (options.attributeResolver(site.access.request) === "other") return;
|
|
689
|
+
diagnostics.push(diagnostic("SVELTE_ATTRS_TEMPLATE_UNSUPPORTED", `Resolver '${site.access.displayName}' is called inside the template; assign the result to a const in the instance script and spread that instead.`, originalPath, source, node));
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
if (diagnostics.length > 0) return {
|
|
693
|
+
kind: "error",
|
|
694
|
+
original,
|
|
695
|
+
diagnostics
|
|
696
|
+
};
|
|
697
|
+
return {
|
|
698
|
+
kind: "ok",
|
|
699
|
+
original,
|
|
700
|
+
entries: projections.flatMap((projection) => projection.entry ? [projection.entry] : [])
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region pipeline/source-ingestion.ts
|
|
705
|
+
function isSerializedSourceEntry(value) {
|
|
706
|
+
if (!(value instanceof Object) || Array.isArray(value)) return false;
|
|
707
|
+
return String(value.path) === value.path && String(value.source) === value.source && (value.hash === void 0 || String(value.hash) === value.hash);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Throws on a payload that is not a corpus: an empty corpus is
|
|
711
|
+
* indistinguishable from "no files" and would publish an empty stylesheet.
|
|
712
|
+
*/
|
|
713
|
+
function parseFilesJson(filesJson, context) {
|
|
714
|
+
const candidate = parseInternalWire(filesJson, `${context} filesJson`);
|
|
715
|
+
if (!Array.isArray(candidate) || !candidate.every(isSerializedSourceEntry)) throw new TypeError(`[${context}] analysis files JSON must be an array of {path, source} entries`);
|
|
716
|
+
return candidate;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Advisory diagnostics warn in every mode and never quarantine their file:
|
|
720
|
+
* OXC recovers parse errors the host bundler itself accepts. All else is fatal.
|
|
721
|
+
*/
|
|
722
|
+
function isAdvisorySourceDiagnostic(diagnostic) {
|
|
723
|
+
return diagnostic.code === "SOURCE_NATIVE_PARSE_ERROR";
|
|
724
|
+
}
|
|
725
|
+
const RELATIVE_PROBE_SUFFIXES = [
|
|
726
|
+
"",
|
|
727
|
+
".ts",
|
|
728
|
+
".tsx",
|
|
729
|
+
".js",
|
|
730
|
+
".jsx",
|
|
731
|
+
"/index.ts",
|
|
732
|
+
"/index.tsx",
|
|
733
|
+
"/index.js",
|
|
734
|
+
"/index.jsx"
|
|
735
|
+
];
|
|
736
|
+
function extension(path) {
|
|
737
|
+
const basename = posix.basename(path);
|
|
738
|
+
const dot = basename.lastIndexOf(".");
|
|
739
|
+
return dot === -1 ? "" : basename.slice(dot).toLowerCase();
|
|
740
|
+
}
|
|
741
|
+
function canonicalResolverPath(path) {
|
|
742
|
+
return posix.normalize(path.replaceAll("\\", "/"));
|
|
743
|
+
}
|
|
744
|
+
const NODE_NEXT_EXTENSION_MAP = {
|
|
745
|
+
".js": [
|
|
746
|
+
".ts",
|
|
747
|
+
".tsx",
|
|
748
|
+
".jsx"
|
|
749
|
+
],
|
|
750
|
+
".mjs": [".mts"],
|
|
751
|
+
".cjs": [".cts"]
|
|
752
|
+
};
|
|
753
|
+
function resolveRelativeSource(importerPath, specifier, files) {
|
|
754
|
+
if (!specifier.startsWith(".")) return null;
|
|
755
|
+
const base = posix.normalize(posix.join(posix.dirname(canonicalResolverPath(importerPath)), specifier));
|
|
756
|
+
for (const suffix of RELATIVE_PROBE_SUFFIXES) {
|
|
757
|
+
const candidate = `${base}${suffix}`;
|
|
758
|
+
const actualPath = files.get(candidate);
|
|
759
|
+
if (actualPath !== void 0) return actualPath;
|
|
760
|
+
}
|
|
761
|
+
const explicitExtension = extension(base);
|
|
762
|
+
for (const sourceExtension of NODE_NEXT_EXTENSION_MAP[explicitExtension] ?? []) {
|
|
763
|
+
const candidate = `${base.slice(0, -explicitExtension.length)}${sourceExtension}`;
|
|
764
|
+
const actualPath = files.get(candidate);
|
|
765
|
+
if (actualPath !== void 0) return actualPath;
|
|
766
|
+
}
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
var ResolverExportIndex = class {
|
|
770
|
+
facts;
|
|
771
|
+
collisions;
|
|
772
|
+
files;
|
|
773
|
+
resolverBindings = /* @__PURE__ */ new Map();
|
|
774
|
+
/** Both memos live for one ingest only — the index is rebuilt per call,
|
|
775
|
+
* so neither needs invalidation. */
|
|
776
|
+
attributionMemo = /* @__PURE__ */ new Map();
|
|
777
|
+
resolveMemo = /* @__PURE__ */ new Map();
|
|
778
|
+
constructor(facts, analysisPaths) {
|
|
779
|
+
this.facts = facts;
|
|
780
|
+
const pathsByCanonical = /* @__PURE__ */ new Map();
|
|
781
|
+
for (const path of analysisPaths) {
|
|
782
|
+
const canonicalPath = canonicalResolverPath(path);
|
|
783
|
+
const paths = pathsByCanonical.get(canonicalPath) ?? [];
|
|
784
|
+
paths.push(path);
|
|
785
|
+
pathsByCanonical.set(canonicalPath, paths);
|
|
786
|
+
}
|
|
787
|
+
this.collisions = [...pathsByCanonical].filter(([, paths]) => paths.length > 1).map(([canonicalPath, paths]) => ({
|
|
788
|
+
canonicalPath,
|
|
789
|
+
paths
|
|
790
|
+
}));
|
|
791
|
+
const factPathsByCanonical = /* @__PURE__ */ new Map();
|
|
792
|
+
for (const path of Object.keys(facts.files)) {
|
|
793
|
+
const canonicalPath = canonicalResolverPath(path);
|
|
794
|
+
const paths = factPathsByCanonical.get(canonicalPath) ?? [];
|
|
795
|
+
paths.push(path);
|
|
796
|
+
factPathsByCanonical.set(canonicalPath, paths);
|
|
797
|
+
}
|
|
798
|
+
const files = /* @__PURE__ */ new Map();
|
|
799
|
+
for (const [canonicalPath, paths] of pathsByCanonical) {
|
|
800
|
+
const factPaths = factPathsByCanonical.get(canonicalPath) ?? [];
|
|
801
|
+
if (paths.length === 1 && factPaths.length === 1) files.set(canonicalPath, factPaths[0]);
|
|
802
|
+
}
|
|
803
|
+
this.files = files;
|
|
804
|
+
for (const [path, file] of Object.entries(facts.files)) this.resolverBindings.set(path, new Set(file.chains.filter((chain) => chain.descriptor.terminal === "asClass" && chain.descriptor.extractable && chain.fatalError === null).map((chain) => chain.descriptor.binding)));
|
|
805
|
+
}
|
|
806
|
+
attribute(importerPath, request) {
|
|
807
|
+
const memoKey = `${importerPath}\0${request.source}\0${request.imported}\0${request.access.kind}\0${request.access.importKind}`;
|
|
808
|
+
const memoized = this.attributionMemo.get(memoKey);
|
|
809
|
+
if (memoized !== void 0) return memoized;
|
|
810
|
+
const attribution = this.attributeUncached(importerPath, request);
|
|
811
|
+
this.attributionMemo.set(memoKey, attribution);
|
|
812
|
+
return attribution;
|
|
813
|
+
}
|
|
814
|
+
attributeUncached(importerPath, request) {
|
|
815
|
+
const importedFile = this.resolveSource(importerPath, request.source);
|
|
816
|
+
if (!importedFile) return "other";
|
|
817
|
+
if (this.resolveClassExport(importedFile, request.imported, /* @__PURE__ */ new Set()) === null) return "other";
|
|
818
|
+
return request.access.kind === "direct" && request.access.importKind === "named" ? "resolver" : "unsupported-resolver-form";
|
|
819
|
+
}
|
|
820
|
+
resolveSource(importerPath, specifier) {
|
|
821
|
+
const key = `${importerPath}\0${specifier}`;
|
|
822
|
+
let resolved = this.resolveMemo.get(key);
|
|
823
|
+
if (resolved === void 0) {
|
|
824
|
+
resolved = resolveRelativeSource(importerPath, specifier, this.files);
|
|
825
|
+
this.resolveMemo.set(key, resolved);
|
|
826
|
+
}
|
|
827
|
+
return resolved;
|
|
828
|
+
}
|
|
829
|
+
resolveClassExport(path, exportedName, seen) {
|
|
830
|
+
const identity = `${path}\0${exportedName}`;
|
|
831
|
+
if (seen.has(identity)) return null;
|
|
832
|
+
seen.add(identity);
|
|
833
|
+
const file = this.facts.files[path];
|
|
834
|
+
const exported = file?.exports.find((candidate) => candidate.exported === exportedName);
|
|
835
|
+
if (!file || !exported) return null;
|
|
836
|
+
if (exported.source === null) {
|
|
837
|
+
if (exported.local === null) return null;
|
|
838
|
+
if (this.resolverBindings.get(path)?.has(exported.local)) return exported.local;
|
|
839
|
+
const imported = file.imports.find((candidate) => candidate.local === exported.local);
|
|
840
|
+
if (!imported) return null;
|
|
841
|
+
const importedFile = this.resolveSource(path, imported.source);
|
|
842
|
+
return importedFile ? this.resolveClassExport(importedFile, imported.imported, seen) : null;
|
|
843
|
+
}
|
|
844
|
+
if (exported.original === null) return null;
|
|
845
|
+
const nextFile = this.resolveSource(path, exported.source);
|
|
846
|
+
return nextFile ? this.resolveClassExport(nextFile, exported.original, seen) : null;
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
/**
|
|
850
|
+
* Native and MDX entries must establish the resolver index before any Svelte
|
|
851
|
+
* projection; bare-package and aliased sources stay unattributed by design.
|
|
852
|
+
*/
|
|
853
|
+
async function ingestSourceEntries(rawEntries, options) {
|
|
854
|
+
const mdxAdapter = options.preprocessMdx ?? preprocessMdx;
|
|
855
|
+
const svelteAdapter = options.adaptSvelte ?? adaptSvelteSource;
|
|
856
|
+
const originalEntries = rawEntries.map((entry) => ({
|
|
857
|
+
...entry,
|
|
858
|
+
hash: entry.hash ?? contentHash(entry.source)
|
|
859
|
+
}));
|
|
860
|
+
const rawOriginalPaths = new Set(originalEntries.map((entry) => entry.path));
|
|
861
|
+
const analysisEntries = [];
|
|
862
|
+
const ownership = {};
|
|
863
|
+
const analysisOwner = /* @__PURE__ */ new Map();
|
|
864
|
+
const diagnostics = [];
|
|
865
|
+
const svelteEntries = [];
|
|
866
|
+
const addAnalysisEntry = (originalPath, path, source, hash) => {
|
|
867
|
+
analysisEntries.push({
|
|
868
|
+
path,
|
|
869
|
+
source,
|
|
870
|
+
hash: hash ?? contentHash(source)
|
|
871
|
+
});
|
|
872
|
+
ownership[originalPath].analysisPaths.push(path);
|
|
873
|
+
analysisOwner.set(path, originalPath);
|
|
874
|
+
};
|
|
875
|
+
const addAdaptedEntries = (originalPath, entries) => {
|
|
876
|
+
const pendingPaths = /* @__PURE__ */ new Set();
|
|
877
|
+
for (const entry of entries) {
|
|
878
|
+
const conflictingOriginalPath = rawOriginalPaths.has(entry.path) ? entry.path : analysisOwner.get(entry.path) ?? (pendingPaths.has(entry.path) ? originalPath : null);
|
|
879
|
+
if (conflictingOriginalPath !== null) {
|
|
880
|
+
diagnostics.push({
|
|
881
|
+
code: "SOURCE_ANALYSIS_PATH_COLLISION",
|
|
882
|
+
message: `Generated analysis path '${entry.path}' collides with '${conflictingOriginalPath}'. Rename one of the source files so adapted analysis paths remain unique.`,
|
|
883
|
+
originalPath,
|
|
884
|
+
analysisPath: entry.path,
|
|
885
|
+
conflictingOriginalPath
|
|
886
|
+
});
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
pendingPaths.add(entry.path);
|
|
890
|
+
}
|
|
891
|
+
for (const entry of entries) addAnalysisEntry(originalPath, entry.path, entry.source);
|
|
892
|
+
};
|
|
893
|
+
for (const original of originalEntries) {
|
|
894
|
+
ownership[original.path] = {
|
|
895
|
+
originalPath: original.path,
|
|
896
|
+
originalHash: original.hash,
|
|
897
|
+
analysisPaths: []
|
|
898
|
+
};
|
|
899
|
+
const kind = extension(original.path);
|
|
900
|
+
if (kind === ".svelte") {
|
|
901
|
+
svelteEntries.push(original);
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (kind !== ".mdx") {
|
|
905
|
+
addAnalysisEntry(original.path, original.path, original.source, original.hash);
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
const result = await mdxAdapter(original.source, original.path);
|
|
909
|
+
if (result.kind === "missing-dep") {
|
|
910
|
+
diagnostics.push({
|
|
911
|
+
code: "SOURCE_MDX_DEPENDENCY_MISSING",
|
|
912
|
+
message: "Install optional peer dependency '@mdx-js/mdx' to analyze opted-in MDX source.",
|
|
913
|
+
originalPath: original.path
|
|
914
|
+
});
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (result.kind === "error" || result.source === void 0) {
|
|
918
|
+
diagnostics.push({
|
|
919
|
+
code: "SOURCE_MDX_PARSE_ERROR",
|
|
920
|
+
message: result.error ?? "MDX preprocessing produced no source.",
|
|
921
|
+
originalPath: original.path
|
|
922
|
+
});
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
addAdaptedEntries(original.path, [{
|
|
926
|
+
path: `${original.path}.tsx`,
|
|
927
|
+
source: result.source
|
|
928
|
+
}]);
|
|
929
|
+
}
|
|
930
|
+
const facts = collectFileFacts(analysisEntries, options);
|
|
931
|
+
for (const [analysisPath, file] of Object.entries(facts.files)) {
|
|
932
|
+
const originalPath = analysisOwner.get(analysisPath) ?? analysisPath;
|
|
933
|
+
for (const message of file.parseDiagnostics) diagnostics.push({
|
|
934
|
+
code: "SOURCE_NATIVE_PARSE_ERROR",
|
|
935
|
+
message,
|
|
936
|
+
originalPath,
|
|
937
|
+
analysisPath
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
const resolvers = new ResolverExportIndex(facts, analysisEntries.map((entry) => entry.path));
|
|
941
|
+
for (const { canonicalPath, paths } of resolvers.collisions) for (let index = 0; index < paths.length; index += 1) {
|
|
942
|
+
const path = paths[index];
|
|
943
|
+
const conflictingPath = paths[(index + 1) % paths.length];
|
|
944
|
+
diagnostics.push({
|
|
945
|
+
code: "SOURCE_RESOLVER_IDENTITY_COLLISION",
|
|
946
|
+
message: `Resolver lookup paths '${path}' and '${conflictingPath}' normalize to the same private identity '${canonicalPath}'. Rename one of the source files so resolver identities remain unique.`,
|
|
947
|
+
originalPath: analysisOwner.get(path) ?? path,
|
|
948
|
+
canonicalPath,
|
|
949
|
+
conflictingOriginalPath: analysisOwner.get(conflictingPath) ?? conflictingPath
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
for (const original of svelteEntries) {
|
|
953
|
+
const result = await svelteAdapter(original.source, original.path, { attributeResolver: (request) => resolvers.attribute(original.path, request) });
|
|
954
|
+
if (result.kind === "missing-dep") {
|
|
955
|
+
diagnostics.push({
|
|
956
|
+
code: "SOURCE_SVELTE_DEPENDENCY_MISSING",
|
|
957
|
+
message: "Install optional peer dependency 'svelte' to analyze opted-in Svelte source.",
|
|
958
|
+
originalPath: original.path
|
|
959
|
+
});
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (result.kind === "error") {
|
|
963
|
+
diagnostics.push(...result.diagnostics);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
addAdaptedEntries(original.path, result.entries);
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
originalEntries,
|
|
970
|
+
analysisEntries,
|
|
971
|
+
ownership,
|
|
972
|
+
diagnostics
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* `parseCount` counts the parses performed by THIS call, so a fully-memoized
|
|
977
|
+
* pass reports zero.
|
|
978
|
+
*/
|
|
979
|
+
function collectFileFacts(analysisEntries, options) {
|
|
980
|
+
const cache = options.factsCache;
|
|
981
|
+
if (!cache) return JSON.parse(options.extractFacts(JSON.stringify(analysisEntries)));
|
|
982
|
+
const pending = analysisEntries.filter((entry) => cache.get(entry.path)?.hash !== entry.hash);
|
|
983
|
+
let parseCount = 0;
|
|
984
|
+
if (pending.length > 0) {
|
|
985
|
+
const fresh = JSON.parse(options.extractFacts(JSON.stringify(pending)));
|
|
986
|
+
parseCount = fresh.parseCount;
|
|
987
|
+
for (const entry of pending) {
|
|
988
|
+
const facts = fresh.files[entry.path];
|
|
989
|
+
if (facts) cache.set(entry.path, {
|
|
990
|
+
hash: entry.hash,
|
|
991
|
+
facts
|
|
992
|
+
});
|
|
993
|
+
else cache.delete(entry.path);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const currentPaths = new Set(analysisEntries.map((entry) => entry.path));
|
|
997
|
+
for (const path of [...cache.keys()]) if (!currentPaths.has(path)) cache.delete(path);
|
|
998
|
+
const files = {};
|
|
999
|
+
for (const entry of analysisEntries) {
|
|
1000
|
+
const cached = cache.get(entry.path);
|
|
1001
|
+
if (cached) files[entry.path] = cached.facts;
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
files,
|
|
1005
|
+
parseCount
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Drops each named original and the analysis children it owns. Every corpus
|
|
1010
|
+
* path applies it — skipping it on incremental passes freezes re-analysis.
|
|
1011
|
+
*/
|
|
1012
|
+
function withoutInvalidOriginals(result, invalidOriginals) {
|
|
1013
|
+
if (invalidOriginals.size === 0) return result;
|
|
1014
|
+
const ownership = Object.fromEntries(Object.entries(result.ownership).filter(([originalPath]) => !invalidOriginals.has(originalPath)));
|
|
1015
|
+
const analysisPaths = new Set(Object.values(ownership).flatMap((owner) => owner.analysisPaths));
|
|
1016
|
+
return {
|
|
1017
|
+
...result,
|
|
1018
|
+
originalEntries: result.originalEntries.filter((entry) => !invalidOriginals.has(entry.path)),
|
|
1019
|
+
analysisEntries: result.analysisEntries.filter((entry) => analysisPaths.has(entry.path)),
|
|
1020
|
+
ownership
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Diagnostics name ANALYSIS paths while ownership is recorded for originals,
|
|
1025
|
+
* so projection is required; the result REPLACES the caller's owner map.
|
|
1026
|
+
*/
|
|
1027
|
+
function projectExternalFileOwners(result, rawOwners) {
|
|
1028
|
+
const projected = {};
|
|
1029
|
+
for (const owner of Object.values(result.ownership)) {
|
|
1030
|
+
const packageOwner = rawOwners[owner.originalPath];
|
|
1031
|
+
if (!packageOwner) continue;
|
|
1032
|
+
projected[owner.originalPath] = packageOwner;
|
|
1033
|
+
for (const analysisPath of owner.analysisPaths) projected[analysisPath] = packageOwner;
|
|
1034
|
+
}
|
|
1035
|
+
return projected;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* One ingestion policy point for every host: a host supplies only a prefix, a
|
|
1039
|
+
* strict flag, and a warn sink; policy and per-host memos stay here.
|
|
1040
|
+
*/
|
|
1041
|
+
function createSourceIngestor(host) {
|
|
1042
|
+
const factsCache = /* @__PURE__ */ new Map();
|
|
1043
|
+
/** Non-strict dedupe: a quarantined original re-ingests on every later
|
|
1044
|
+
* corpus pass, so re-warning it on each save is noise. */
|
|
1045
|
+
const warnedByOriginal = /* @__PURE__ */ new Map();
|
|
1046
|
+
return {
|
|
1047
|
+
async ingest(entries) {
|
|
1048
|
+
const extractFacts = host.engineApi().extractFacts;
|
|
1049
|
+
if (extractFacts === void 0) throw new Error(`${host.prefix} native engine does not expose extractFacts required for source adaptation`);
|
|
1050
|
+
return ingestSourceEntries(entries, {
|
|
1051
|
+
extractFacts,
|
|
1052
|
+
factsCache
|
|
1053
|
+
});
|
|
1054
|
+
},
|
|
1055
|
+
surfaceDiagnostics(diagnostics) {
|
|
1056
|
+
const fatal = diagnostics.filter((diagnostic) => !isAdvisorySourceDiagnostic(diagnostic));
|
|
1057
|
+
const invalidOriginals = new Set(fatal.map((diagnostic) => diagnostic.originalPath));
|
|
1058
|
+
if (diagnostics.length === 0) return invalidOriginals;
|
|
1059
|
+
if (host.strict() && fatal.length > 0) {
|
|
1060
|
+
const lines = fatal.map((diagnostic) => `${diagnostic.code} ${diagnostic.originalPath}: ${diagnostic.message}`);
|
|
1061
|
+
throw new Error(`${host.prefix} ${lines.join(`\n${host.prefix} `)}`);
|
|
1062
|
+
}
|
|
1063
|
+
for (const diagnostic of diagnostics) {
|
|
1064
|
+
const line = `${diagnostic.code} ${diagnostic.originalPath}: ${diagnostic.message}`;
|
|
1065
|
+
let warned = warnedByOriginal.get(diagnostic.originalPath);
|
|
1066
|
+
if (!warned) {
|
|
1067
|
+
warned = /* @__PURE__ */ new Set();
|
|
1068
|
+
warnedByOriginal.set(diagnostic.originalPath, warned);
|
|
1069
|
+
}
|
|
1070
|
+
if (warned.has(line)) continue;
|
|
1071
|
+
warned.add(line);
|
|
1072
|
+
host.warn(`${host.prefix} ${line}`);
|
|
1073
|
+
}
|
|
1074
|
+
return invalidOriginals;
|
|
1075
|
+
},
|
|
1076
|
+
markPublished(result) {
|
|
1077
|
+
for (const originalPath of Object.keys(result.ownership)) warnedByOriginal.delete(originalPath);
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
//#endregion
|
|
1082
|
+
//#region pipeline/engine-adapter.ts
|
|
1083
|
+
/**
|
|
1084
|
+
* One engine API for both plugins. Imports neither the native binding nor any
|
|
1085
|
+
* plugin or bundler — every such dependency arrives injected.
|
|
1086
|
+
*/
|
|
1087
|
+
/**
|
|
1088
|
+
* Returns a function, invoked per call site, so `isV2()` and
|
|
1089
|
+
* `loadNativeEngine()` re-evaluate each time instead of being captured.
|
|
1090
|
+
*/
|
|
1091
|
+
function createV2EngineApi(deps) {
|
|
1092
|
+
const { label, isV2, loadNativeEngine, store } = deps;
|
|
1093
|
+
return () => {
|
|
1094
|
+
if (!isV2()) return loadNativeEngine();
|
|
1095
|
+
const native = loadNativeEngine();
|
|
1096
|
+
return {
|
|
1097
|
+
loadSystemModule: (...args) => native.loadSystemModule(...args),
|
|
1098
|
+
extractFacts: (filesJson) => native.extractFacts(filesJson),
|
|
1099
|
+
analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson, transformSourcesJson) => {
|
|
1100
|
+
const filesJson = deps.rehydrateFilesJson ? deps.rehydrateFilesJson(filesJsonRaw) : filesJsonRaw;
|
|
1101
|
+
const sent = /* @__PURE__ */ new Map();
|
|
1102
|
+
for (const entry of parseFilesJson(filesJson, label)) sent.set(entry.path, entry.source);
|
|
1103
|
+
store.setSentSources(sent);
|
|
1104
|
+
const emitterConfig = emitterConfigJson ? JSON.parse(emitterConfigJson) : {};
|
|
1105
|
+
store.setEngine(null);
|
|
1106
|
+
const config = {
|
|
1107
|
+
runtimeImport: emitterConfig.runtime_import ?? void 0,
|
|
1108
|
+
cssModuleId: emitterConfig.css_module_id ?? void 0,
|
|
1109
|
+
systemPropsModuleId: emitterConfig.system_props_module_id ?? void 0,
|
|
1110
|
+
themeJson: scalesJson,
|
|
1111
|
+
variableMapJson,
|
|
1112
|
+
contextualVarsJson: contextualVarsJson ?? void 0,
|
|
1113
|
+
configJson: propConfigJson,
|
|
1114
|
+
groupRegistryJson,
|
|
1115
|
+
selectorAliasesJson: selectorAliasesJson ?? void 0,
|
|
1116
|
+
conditionAliasesJson: conditionAliasesJson ?? void 0,
|
|
1117
|
+
globalStyleBlocksJson: globalStyleBlocksJson ?? void 0,
|
|
1118
|
+
keyframesJson: keyframesJson ?? void 0,
|
|
1119
|
+
packageResolutionJson: packageResolutionJson ?? void 0,
|
|
1120
|
+
pathAliasesJson: pathAliasesJson ?? void 0,
|
|
1121
|
+
staticCssJson: staticCssJson ?? void 0,
|
|
1122
|
+
externalDirsJson: externalDirsJson ?? void 0,
|
|
1123
|
+
transformSourcesJson: transformSourcesJson ?? void 0,
|
|
1124
|
+
devMode
|
|
1125
|
+
};
|
|
1126
|
+
const engine = new native.ExtractEngine(config);
|
|
1127
|
+
store.setEngine(engine);
|
|
1128
|
+
return engine.analyze(filesJson);
|
|
1129
|
+
},
|
|
1130
|
+
transformFile: (source, path, _manifest) => {
|
|
1131
|
+
const engine = store.getEngine();
|
|
1132
|
+
if (!engine) throw new Error(`[${label}] v2 transform before analyze — engine instance not initialized`);
|
|
1133
|
+
const sentMap = store.getSentSources();
|
|
1134
|
+
if (deps.passThroughUnknownPaths && sentMap && !sentMap.has(path)) return {
|
|
1135
|
+
code: source,
|
|
1136
|
+
hasComponents: false
|
|
1137
|
+
};
|
|
1138
|
+
const sent = sentMap?.get(path);
|
|
1139
|
+
if (sent !== void 0 && sent !== source && !store.getDriftWarned()) {
|
|
1140
|
+
store.setDriftWarned(true);
|
|
1141
|
+
console.warn(`[${label}] v2: transform-time source for ${path} differs from analyze-time source — an upstream transform may be reverted`);
|
|
1142
|
+
}
|
|
1143
|
+
return JSON.parse(engine.transformFile(path));
|
|
1144
|
+
},
|
|
1145
|
+
clearAnalysisCache: () => {
|
|
1146
|
+
const engine = store.getEngine();
|
|
1147
|
+
if (engine) engine.clearCache();
|
|
1148
|
+
store.setEngine(null);
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
//#endregion
|
|
1154
|
+
//#region pipeline/engine-retirement.ts
|
|
1155
|
+
const RETIRED_ENGINE_MESSAGE = "animus: extraction engine 'v1' was retired (openspec: retire-extract-v1) — v2 is the only engine; remove the engine option / ANIMUS_ENGINE override";
|
|
1156
|
+
function assertNoRetiredEngineSelection(engineOption) {
|
|
1157
|
+
if (engineOption === "v1" || process.env.ANIMUS_ENGINE === "v1") throw new Error(RETIRED_ENGINE_MESSAGE);
|
|
1158
|
+
}
|
|
1159
|
+
//#endregion
|
|
1160
|
+
//#region pipeline/core-options.ts
|
|
1161
|
+
const DRIVER_NAMESPACES = [
|
|
1162
|
+
"vite",
|
|
1163
|
+
"next",
|
|
1164
|
+
"cli"
|
|
1165
|
+
];
|
|
1166
|
+
const DRIVER_NAMESPACE_KEYS = new Set(DRIVER_NAMESPACES);
|
|
1167
|
+
/**
|
|
1168
|
+
* Applied unconditionally: `node_modules` belongs to external-package
|
|
1169
|
+
* collection, and `.next`/`.animus` hold output, never source.
|
|
1170
|
+
*/
|
|
1171
|
+
const STRUCTURAL_EXCLUDE = [
|
|
1172
|
+
"node_modules",
|
|
1173
|
+
".next",
|
|
1174
|
+
".animus"
|
|
1175
|
+
];
|
|
1176
|
+
/** Defaults a user `exclude` list replaces; the structural set it cannot. */
|
|
1177
|
+
const REPLACEABLE_DEFAULT_EXCLUDE = [
|
|
1178
|
+
"dist",
|
|
1179
|
+
".test.",
|
|
1180
|
+
".spec."
|
|
1181
|
+
];
|
|
1182
|
+
const DEFAULT_EXCLUDE = [...STRUCTURAL_EXCLUDE, ...REPLACEABLE_DEFAULT_EXCLUDE];
|
|
1183
|
+
const CORE_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
1184
|
+
"system",
|
|
1185
|
+
"exclude",
|
|
1186
|
+
"extensions",
|
|
1187
|
+
"strict",
|
|
1188
|
+
"verbose",
|
|
1189
|
+
"prefix",
|
|
1190
|
+
"targets",
|
|
1191
|
+
"minify",
|
|
1192
|
+
"staticCss",
|
|
1193
|
+
"layers",
|
|
1194
|
+
"engine",
|
|
1195
|
+
"mode",
|
|
1196
|
+
"root"
|
|
1197
|
+
]);
|
|
1198
|
+
/** Carries a stable `name` so drivers can map it to their own config-error
|
|
1199
|
+
* surface (the CLI's exit code 2). */
|
|
1200
|
+
var AnimusConfigError = class extends Error {
|
|
1201
|
+
constructor(message) {
|
|
1202
|
+
super(message);
|
|
1203
|
+
this.name = "AnimusConfigError";
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
/**
|
|
1207
|
+
* `ownKeys` is the caller's own top-level surface; `rejectKeys` names core
|
|
1208
|
+
* keys it does not honor, which must fail rather than be silently ignored.
|
|
1209
|
+
*/
|
|
1210
|
+
function assertKnownOptionKeys(raw, ownKeys = [], rejectKeys = [], opts = {}) {
|
|
1211
|
+
const surface = (message) => {
|
|
1212
|
+
if (opts.onUnknownKey === "warn") {
|
|
1213
|
+
opts.warn(message);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
throw new AnimusConfigError(message);
|
|
1217
|
+
};
|
|
1218
|
+
const own = new Set(ownKeys);
|
|
1219
|
+
for (const key of Object.keys(raw)) {
|
|
1220
|
+
if (raw[key] === void 0) continue;
|
|
1221
|
+
const rejected = rejectKeys.find((entry) => entry.key === key);
|
|
1222
|
+
if (rejected) {
|
|
1223
|
+
surface(`Option "${key}" is not supported by this driver: ${rejected.reason}`);
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (CORE_OPTION_KEYS.has(key) || DRIVER_NAMESPACE_KEYS.has(key) || own.has(key)) continue;
|
|
1227
|
+
const nearest = suggestNearest(key);
|
|
1228
|
+
surface(`Unknown option "${key}".${nearest ? ` Did you mean "${nearest}"?` : ""} Core keys: ${[...CORE_OPTION_KEYS].join(", ")}. Driver namespaces: ${DRIVER_NAMESPACES.join(", ")}.`);
|
|
1229
|
+
}
|
|
1230
|
+
const mode = raw["mode"];
|
|
1231
|
+
if (mode !== void 0 && mode !== "development" && mode !== "production") throw new AnimusConfigError(`Invalid mode "${String(mode)}" — expected "development" or "production".`);
|
|
1232
|
+
for (const { key, ok, expected } of CORE_VALUE_GATES) {
|
|
1233
|
+
const value = raw[key];
|
|
1234
|
+
if (value === void 0 || ok(value)) continue;
|
|
1235
|
+
throw new AnimusConfigError(`Invalid value for "${key}" — expected ${expected}, got ${JSON.stringify(value)}.`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Options are foreign JS, so a boxed `String`/`Boolean` must not pass: a
|
|
1240
|
+
* boxed value never behaves as the primitive its consumers expect.
|
|
1241
|
+
*/
|
|
1242
|
+
const isString = (value) => Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
1243
|
+
const isBoolean = (value) => Object(value) !== value && Object.prototype.toString.call(value) === "[object Boolean]";
|
|
1244
|
+
const isStringArray = (value) => Array.isArray(value) && value.every(isString);
|
|
1245
|
+
const isStringOrStringArray = (value) => isString(value) || isStringArray(value);
|
|
1246
|
+
const CORE_VALUE_GATES = [
|
|
1247
|
+
{
|
|
1248
|
+
key: "system",
|
|
1249
|
+
ok: isString,
|
|
1250
|
+
expected: "a string path"
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
key: "exclude",
|
|
1254
|
+
ok: isStringArray,
|
|
1255
|
+
expected: "an array of string patterns"
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
key: "extensions",
|
|
1259
|
+
ok: isStringArray,
|
|
1260
|
+
expected: "an array of string extensions"
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
key: "strict",
|
|
1264
|
+
ok: isBoolean,
|
|
1265
|
+
expected: "a boolean"
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
key: "verbose",
|
|
1269
|
+
ok: isBoolean,
|
|
1270
|
+
expected: "a boolean"
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
key: "minify",
|
|
1274
|
+
ok: isBoolean,
|
|
1275
|
+
expected: "a boolean"
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
key: "prefix",
|
|
1279
|
+
ok: isString,
|
|
1280
|
+
expected: "a string"
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
key: "targets",
|
|
1284
|
+
ok: isStringOrStringArray,
|
|
1285
|
+
expected: "a string or an array of strings"
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
key: "layers",
|
|
1289
|
+
ok: isStringArray,
|
|
1290
|
+
expected: "an array of layer names"
|
|
1291
|
+
}
|
|
1292
|
+
];
|
|
1293
|
+
function suggestNearest(key) {
|
|
1294
|
+
const lower = key.toLowerCase();
|
|
1295
|
+
let best = null;
|
|
1296
|
+
let bestScore = 3;
|
|
1297
|
+
for (const candidate of CORE_OPTION_KEYS) {
|
|
1298
|
+
const score = editDistance(lower, candidate.toLowerCase());
|
|
1299
|
+
if (score < bestScore) {
|
|
1300
|
+
bestScore = score;
|
|
1301
|
+
best = candidate;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return best;
|
|
1305
|
+
}
|
|
1306
|
+
function editDistance(a, b) {
|
|
1307
|
+
const dp = Array.from({ length: a.length + 1 }, (_, i) => [i]);
|
|
1308
|
+
for (let j = 1; j <= b.length; j++) dp[0][j] = j;
|
|
1309
|
+
for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
1310
|
+
return dp[a.length][b.length];
|
|
1311
|
+
}
|
|
1312
|
+
const GLOB_META = /[*?]/;
|
|
1313
|
+
/**
|
|
1314
|
+
* Globs compile to a RegExp over the root-relative path, matching the path or
|
|
1315
|
+
* any ancestor directory; plain patterns are substrings. `./` is stripped.
|
|
1316
|
+
*/
|
|
1317
|
+
function compilePattern(raw) {
|
|
1318
|
+
let pattern = raw.split("\\").join("/");
|
|
1319
|
+
while (pattern.startsWith("./")) pattern = pattern.slice(2);
|
|
1320
|
+
if (pattern === "") return () => false;
|
|
1321
|
+
if (!GLOB_META.test(pattern)) return (full, rel) => full.includes(pattern) || rel.includes(pattern);
|
|
1322
|
+
const regex = globToRegExp(pattern);
|
|
1323
|
+
return (_full, rel) => {
|
|
1324
|
+
const normalized = rel.split("\\").join("/");
|
|
1325
|
+
if (regex.test(normalized)) return true;
|
|
1326
|
+
for (let slash = normalized.indexOf("/"); slash !== -1; slash = normalized.indexOf("/", slash + 1)) if (regex.test(normalized.slice(0, slash))) return true;
|
|
1327
|
+
return false;
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
/** Minimal glob → RegExp: `**` spans segments, `*`/`?` stay within one. */
|
|
1331
|
+
function globToRegExp(glob) {
|
|
1332
|
+
let out = "";
|
|
1333
|
+
let i = 0;
|
|
1334
|
+
const normalized = glob.split("\\").join("/");
|
|
1335
|
+
while (i < normalized.length) {
|
|
1336
|
+
const ch = normalized[i];
|
|
1337
|
+
if (ch === "*") {
|
|
1338
|
+
if (normalized[i + 1] === "*") {
|
|
1339
|
+
if (normalized[i + 2] === "/") {
|
|
1340
|
+
out += "(?:[^/]+/)*";
|
|
1341
|
+
i += 3;
|
|
1342
|
+
} else {
|
|
1343
|
+
out += ".*";
|
|
1344
|
+
i += 2;
|
|
1345
|
+
}
|
|
1346
|
+
} else {
|
|
1347
|
+
out += "[^/]*";
|
|
1348
|
+
i += 1;
|
|
1349
|
+
}
|
|
1350
|
+
} else if (ch === "?") {
|
|
1351
|
+
out += "[^/]";
|
|
1352
|
+
i += 1;
|
|
1353
|
+
} else {
|
|
1354
|
+
out += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
1355
|
+
i += 1;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
return new RegExp(`^${out}$`);
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* A user list replaces only the replaceable defaults. Driver-owned
|
|
1362
|
+
* invariants belong in `extraStructural`; a user list would replace those.
|
|
1363
|
+
*/
|
|
1364
|
+
function createExcludeMatcher(userPatterns, extraStructural = []) {
|
|
1365
|
+
const merged = [...STRUCTURAL_EXCLUDE];
|
|
1366
|
+
for (const pattern of extraStructural) if (!merged.includes(pattern)) merged.push(pattern);
|
|
1367
|
+
for (const pattern of userPatterns ?? REPLACEABLE_DEFAULT_EXCLUDE) if (!merged.includes(pattern)) merged.push(pattern);
|
|
1368
|
+
const compiled = merged.map((pattern) => [pattern, compilePattern(pattern)]);
|
|
1369
|
+
const hits = new Map(merged.map((p) => [p, 0]));
|
|
1370
|
+
const explain = (full, rel) => {
|
|
1371
|
+
for (const [pattern, test] of compiled) if (test(full, rel)) {
|
|
1372
|
+
hits.set(pattern, (hits.get(pattern) ?? 0) + 1);
|
|
1373
|
+
return pattern;
|
|
1374
|
+
}
|
|
1375
|
+
return null;
|
|
1376
|
+
};
|
|
1377
|
+
return {
|
|
1378
|
+
patterns: merged,
|
|
1379
|
+
matches: (full, rel) => explain(full, rel) !== null,
|
|
1380
|
+
explain,
|
|
1381
|
+
stats: () => hits
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function resolveMode(explicit, driverDefault) {
|
|
1385
|
+
if (explicit === "development" || explicit === "production") return {
|
|
1386
|
+
mode: explicit,
|
|
1387
|
+
provenance: "explicit"
|
|
1388
|
+
};
|
|
1389
|
+
return {
|
|
1390
|
+
mode: driverDefault(),
|
|
1391
|
+
provenance: "driver-default"
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
//#endregion
|
|
1395
|
+
//#region pipeline/discover-files.ts
|
|
1396
|
+
function pathCharRank(code) {
|
|
1397
|
+
return code === 47 || code === 92 ? -1 : code;
|
|
1398
|
+
}
|
|
1399
|
+
function compareDiscoveryOrder(a, b) {
|
|
1400
|
+
const shared = Math.min(a.length, b.length);
|
|
1401
|
+
for (let i = 0; i < shared; i++) {
|
|
1402
|
+
const left = pathCharRank(a.charCodeAt(i));
|
|
1403
|
+
const right = pathCharRank(b.charCodeAt(i));
|
|
1404
|
+
if (left !== right) return left - right;
|
|
1405
|
+
}
|
|
1406
|
+
return a.length - b.length;
|
|
1407
|
+
}
|
|
1408
|
+
function discoverFiles(dir, rootDir, exclude, extensionsSet) {
|
|
1409
|
+
return walkFiles(dir, rootDir, exclude, extensionsSet).sort(compareDiscoveryOrder);
|
|
1410
|
+
}
|
|
1411
|
+
function walkFiles(dir, rootDir, exclude, extensionsSet) {
|
|
1412
|
+
const results = [];
|
|
1413
|
+
let entries;
|
|
1414
|
+
try {
|
|
1415
|
+
entries = readdirSync(dir, { encoding: "utf8" });
|
|
1416
|
+
} catch {
|
|
1417
|
+
return results;
|
|
1418
|
+
}
|
|
1419
|
+
const isExcluded = exclude ? (full, rel) => exclude.matches(full, rel) : () => false;
|
|
1420
|
+
for (const entry of entries) {
|
|
1421
|
+
const fullPath = join$1(dir, entry);
|
|
1422
|
+
if (isExcluded(fullPath, relative(rootDir, fullPath))) continue;
|
|
1423
|
+
let stat;
|
|
1424
|
+
try {
|
|
1425
|
+
stat = statSync(fullPath);
|
|
1426
|
+
} catch {
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
if (stat.isDirectory()) results.push(...walkFiles(fullPath, rootDir, exclude, extensionsSet));
|
|
1430
|
+
else if (extensionsSet.has(extname(entry))) results.push(fullPath);
|
|
1431
|
+
}
|
|
1432
|
+
return results;
|
|
1433
|
+
}
|
|
1434
|
+
//#endregion
|
|
1435
|
+
//#region pipeline/source-identity.ts
|
|
1436
|
+
/**
|
|
1437
|
+
* One derivation of source identity for discovery, watch ingestion, ownership
|
|
1438
|
+
* and deletion: event paths are lookup INPUTS only and never fork identity.
|
|
1439
|
+
*/
|
|
1440
|
+
/**
|
|
1441
|
+
* True when `target` is `root` or a descendant, decided by `relative()`
|
|
1442
|
+
* shape: string prefixing would let `/ui` claim `/ui-old`.
|
|
1443
|
+
*/
|
|
1444
|
+
function isPathWithinRoot(root, target, pathApi = path) {
|
|
1445
|
+
const rel = pathApi.relative(root, target);
|
|
1446
|
+
return rel === "" || !pathApi.isAbsolute(rel) && rel !== ".." && !rel.startsWith(".." + pathApi.sep);
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* True when two paths share a platform root (win32 drive letter or UNC
|
|
1450
|
+
* share; always true on posix). Drive letters compare case-insensitively.
|
|
1451
|
+
*/
|
|
1452
|
+
function sharesVolumeRoot(a, b, pathApi = path) {
|
|
1453
|
+
const volumeOf = (p) => pathApi.parse(pathApi.resolve(p)).root.toLowerCase();
|
|
1454
|
+
return volumeOf(a) === volumeOf(b);
|
|
1455
|
+
}
|
|
1456
|
+
function canonicalize(p) {
|
|
1457
|
+
try {
|
|
1458
|
+
return realpathSync$1(p);
|
|
1459
|
+
} catch {
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
function createSourceIdentity(rootDir) {
|
|
1464
|
+
const normalizedRootDir = path.normalize(rootDir);
|
|
1465
|
+
const canonicalRootDir = canonicalize(normalizedRootDir) ?? normalizedRootDir;
|
|
1466
|
+
const roots = [];
|
|
1467
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
1468
|
+
const owningRecord = (canonicalPath) => {
|
|
1469
|
+
let owner = null;
|
|
1470
|
+
for (const record of roots) {
|
|
1471
|
+
if (!isPathWithinRoot(record.canonical, canonicalPath)) continue;
|
|
1472
|
+
if (!owner || record.canonical.length > owner.canonical.length) owner = record;
|
|
1473
|
+
}
|
|
1474
|
+
return owner;
|
|
1475
|
+
};
|
|
1476
|
+
return {
|
|
1477
|
+
rootDir: normalizedRootDir,
|
|
1478
|
+
canonicalRootDir,
|
|
1479
|
+
registerExternalRoot(root) {
|
|
1480
|
+
const asRegistered = path.normalize(root);
|
|
1481
|
+
const canonical = canonicalize(asRegistered) ?? asRegistered;
|
|
1482
|
+
const existing = roots.find((record) => record.canonical === canonical);
|
|
1483
|
+
if (existing) return existing.canonical;
|
|
1484
|
+
roots.push({
|
|
1485
|
+
asRegistered,
|
|
1486
|
+
canonical
|
|
1487
|
+
});
|
|
1488
|
+
return canonical;
|
|
1489
|
+
},
|
|
1490
|
+
externalRoots() {
|
|
1491
|
+
return roots.map((record) => record.canonical);
|
|
1492
|
+
},
|
|
1493
|
+
resolveSourceId(inputPath) {
|
|
1494
|
+
const lexical = path.normalize(inputPath);
|
|
1495
|
+
const canonical = canonicalize(lexical);
|
|
1496
|
+
if (canonical === null) return null;
|
|
1497
|
+
const owner = owningRecord(canonical);
|
|
1498
|
+
const ownedBy = (record) => {
|
|
1499
|
+
const pathInRoot = path.relative(record.canonical, canonical);
|
|
1500
|
+
return {
|
|
1501
|
+
sourceKey: path.relative(normalizedRootDir, path.join(record.asRegistered, pathInRoot)),
|
|
1502
|
+
owningRoot: record.canonical,
|
|
1503
|
+
pathInRoot
|
|
1504
|
+
};
|
|
1505
|
+
};
|
|
1506
|
+
const ownerIsMoreSpecific = owner !== null && owner.canonical.length > canonicalRootDir.length;
|
|
1507
|
+
let resolved;
|
|
1508
|
+
if (!ownerIsMoreSpecific && isPathWithinRoot(canonicalRootDir, canonical)) {
|
|
1509
|
+
const key = path.relative(canonicalRootDir, canonical);
|
|
1510
|
+
resolved = {
|
|
1511
|
+
sourceKey: key,
|
|
1512
|
+
owningRoot: null,
|
|
1513
|
+
pathInRoot: key
|
|
1514
|
+
};
|
|
1515
|
+
} else if (owner) resolved = ownedBy(owner);
|
|
1516
|
+
else return null;
|
|
1517
|
+
aliases.set(lexical, resolved);
|
|
1518
|
+
aliases.set(canonical, resolved);
|
|
1519
|
+
return resolved;
|
|
1520
|
+
},
|
|
1521
|
+
resolveDeletedSourceId(inputPath) {
|
|
1522
|
+
return aliases.get(path.normalize(inputPath)) ?? null;
|
|
1523
|
+
},
|
|
1524
|
+
containingExternalRoot(inputPath) {
|
|
1525
|
+
const lexical = path.normalize(inputPath);
|
|
1526
|
+
let owner = null;
|
|
1527
|
+
for (const record of roots) {
|
|
1528
|
+
if (!(isPathWithinRoot(record.canonical, lexical) || isPathWithinRoot(record.asRegistered, lexical))) continue;
|
|
1529
|
+
if (!owner || record.canonical.length > owner.canonical.length) owner = record;
|
|
1530
|
+
}
|
|
1531
|
+
if (owner) return owner.canonical;
|
|
1532
|
+
const canonical = canonicalize(lexical);
|
|
1533
|
+
if (canonical === null) return null;
|
|
1534
|
+
for (const record of roots) {
|
|
1535
|
+
if (!isPathWithinRoot(record.canonical, canonical)) continue;
|
|
1536
|
+
if (!owner || record.canonical.length > owner.canonical.length) owner = record;
|
|
1537
|
+
}
|
|
1538
|
+
return owner?.canonical ?? null;
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
//#endregion
|
|
1543
|
+
//#region pipeline/discover-packages.ts
|
|
1544
|
+
function findPackageRoot(absEntryPath) {
|
|
1545
|
+
let pkgRoot = dirname$1(absEntryPath);
|
|
1546
|
+
while (pkgRoot !== dirname$1(pkgRoot) && !existsSync(join$1(pkgRoot, "package.json"))) pkgRoot = dirname$1(pkgRoot);
|
|
1547
|
+
return pkgRoot;
|
|
1548
|
+
}
|
|
1549
|
+
const PACKAGE_SRC_EXCLUDES = [
|
|
1550
|
+
"dist",
|
|
1551
|
+
"node_modules",
|
|
1552
|
+
".test.",
|
|
1553
|
+
".spec."
|
|
1554
|
+
];
|
|
1555
|
+
function isExcludedPackageRelativePath(rel) {
|
|
1556
|
+
return PACKAGE_SRC_EXCLUDES.some((pattern) => rel.includes(pattern));
|
|
1557
|
+
}
|
|
1558
|
+
function walkPackageSources(packageDir, extensionsSet) {
|
|
1559
|
+
return discoverFiles(packageDir, packageDir, void 0, extensionsSet).filter((absPath) => !isExcludedPackageRelativePath(relative(packageDir, absPath)));
|
|
1560
|
+
}
|
|
1561
|
+
const PACKAGE_OUTPUT_EXCLUDES = [
|
|
1562
|
+
"node_modules",
|
|
1563
|
+
".test.",
|
|
1564
|
+
".spec.",
|
|
1565
|
+
".d.ts",
|
|
1566
|
+
".map"
|
|
1567
|
+
];
|
|
1568
|
+
const isFile = (path) => {
|
|
1569
|
+
try {
|
|
1570
|
+
return statSync(path).isFile();
|
|
1571
|
+
} catch {
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
function resolveAbsolutePathSpecifier(absSpecifier, extensionsSet) {
|
|
1576
|
+
return [
|
|
1577
|
+
absSpecifier,
|
|
1578
|
+
...Array.from(extensionsSet, (ext) => absSpecifier + ext),
|
|
1579
|
+
...Array.from(extensionsSet, (ext) => join$1(absSpecifier, `index${ext}`))
|
|
1580
|
+
].find(isFile) ?? null;
|
|
1581
|
+
}
|
|
1582
|
+
function bareSpecifierPackageName(specifier) {
|
|
1583
|
+
const segments = specifier.split("/");
|
|
1584
|
+
return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
|
1585
|
+
}
|
|
1586
|
+
function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
|
|
1587
|
+
if (isAbsolute(specifier)) {
|
|
1588
|
+
const resolved = resolveAbsolutePathSpecifier(specifier, extensionsSet);
|
|
1589
|
+
if (!resolved) return null;
|
|
1590
|
+
return isPathWithinRoot(srcDir, resolved) ? resolved : null;
|
|
1591
|
+
}
|
|
1592
|
+
const packageName = bareSpecifierPackageName(specifier);
|
|
1593
|
+
const subpath = specifier.slice(packageName.length + 1);
|
|
1594
|
+
return resolveAbsolutePathSpecifier(join$1(srcDir, subpath || "index"), extensionsSet);
|
|
1595
|
+
}
|
|
1596
|
+
function distEntryIsStale(absEntry, srcDir, srcFiles) {
|
|
1597
|
+
if (isPathWithinRoot(srcDir, absEntry)) return false;
|
|
1598
|
+
let distMtime;
|
|
1599
|
+
try {
|
|
1600
|
+
distMtime = statSync(absEntry).mtimeMs;
|
|
1601
|
+
} catch {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
let newestSrcMtime = -Infinity;
|
|
1605
|
+
for (const srcFile of srcFiles) try {
|
|
1606
|
+
newestSrcMtime = Math.max(newestSrcMtime, statSync(srcFile).mtimeMs);
|
|
1607
|
+
} catch {}
|
|
1608
|
+
return distMtime < newestSrcMtime;
|
|
1609
|
+
}
|
|
1610
|
+
async function collectExternalPackageSources(opts) {
|
|
1611
|
+
const { specifiers, resolveSpecifier, rootDir, extensionsSet, hasEntry, onSourceRead, onUnreadable, onPackageResolved } = opts;
|
|
1612
|
+
const entries = [];
|
|
1613
|
+
const pushed = /* @__PURE__ */ new Set();
|
|
1614
|
+
const packageMap = {};
|
|
1615
|
+
const sourceEntries = /* @__PURE__ */ new Map();
|
|
1616
|
+
const packageDirs = [];
|
|
1617
|
+
const dirOwnerSets = {};
|
|
1618
|
+
const dirExtensions = {};
|
|
1619
|
+
const fileOwners = {};
|
|
1620
|
+
const outcomes = [];
|
|
1621
|
+
const claimDir = (dir, specifier) => {
|
|
1622
|
+
(dirOwnerSets[dir] ??= []).push(specifier);
|
|
1623
|
+
};
|
|
1624
|
+
const alreadyIngested = (relPath) => hasEntry(relPath) || pushed.has(relPath);
|
|
1625
|
+
for (const specifier of specifiers) {
|
|
1626
|
+
let absEntry;
|
|
1627
|
+
try {
|
|
1628
|
+
absEntry = await resolveSpecifier(specifier);
|
|
1629
|
+
} catch {
|
|
1630
|
+
absEntry = null;
|
|
1631
|
+
}
|
|
1632
|
+
if (!absEntry && isAbsolute(specifier)) absEntry = resolveAbsolutePathSpecifier(specifier, extensionsSet);
|
|
1633
|
+
if (!absEntry) {
|
|
1634
|
+
outcomes.push({
|
|
1635
|
+
specifier,
|
|
1636
|
+
outcome: "unresolvable",
|
|
1637
|
+
fileCount: 0
|
|
1638
|
+
});
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
const pkgRoot = findPackageRoot(absEntry);
|
|
1642
|
+
const srcDir = join$1(pkgRoot, "src");
|
|
1643
|
+
let fileCount = 0;
|
|
1644
|
+
let staleDist = false;
|
|
1645
|
+
if (existsSync(srcDir)) {
|
|
1646
|
+
packageDirs.push(srcDir);
|
|
1647
|
+
claimDir(srcDir, specifier);
|
|
1648
|
+
dirExtensions[srcDir] = [...extensionsSet];
|
|
1649
|
+
onPackageResolved?.(specifier, srcDir);
|
|
1650
|
+
const srcEntry = sourceEntryForSpecifier(specifier, srcDir, extensionsSet);
|
|
1651
|
+
if (srcEntry) {
|
|
1652
|
+
packageMap[specifier] = relative(rootDir, srcEntry);
|
|
1653
|
+
sourceEntries.set(specifier, srcEntry);
|
|
1654
|
+
} else packageMap[specifier] = relative(rootDir, absEntry);
|
|
1655
|
+
if (!isAbsolute(specifier)) {
|
|
1656
|
+
const packageName = bareSpecifierPackageName(specifier);
|
|
1657
|
+
if (packageName !== specifier && !(packageName in packageMap)) {
|
|
1658
|
+
const rootEntry = sourceEntryForSpecifier(packageName, srcDir, extensionsSet);
|
|
1659
|
+
if (rootEntry) {
|
|
1660
|
+
packageMap[packageName] = relative(rootDir, rootEntry);
|
|
1661
|
+
sourceEntries.set(packageName, rootEntry);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
const pkgFiles = walkPackageSources(srcDir, extensionsSet);
|
|
1666
|
+
staleDist = distEntryIsStale(absEntry, srcDir, pkgFiles);
|
|
1667
|
+
for (const pkgFile of pkgFiles) {
|
|
1668
|
+
const relPath = relative(rootDir, pkgFile);
|
|
1669
|
+
if (alreadyIngested(relPath)) {
|
|
1670
|
+
fileCount++;
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
let source;
|
|
1674
|
+
try {
|
|
1675
|
+
source = readFileSync(pkgFile, "utf-8");
|
|
1676
|
+
} catch (err) {
|
|
1677
|
+
onUnreadable(relPath, err);
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
onSourceRead?.(source, relPath, pkgFile);
|
|
1681
|
+
entries.push({
|
|
1682
|
+
path: relPath,
|
|
1683
|
+
source
|
|
1684
|
+
});
|
|
1685
|
+
pushed.add(relPath);
|
|
1686
|
+
fileOwners[relPath] ??= specifier;
|
|
1687
|
+
fileCount++;
|
|
1688
|
+
}
|
|
1689
|
+
} else {
|
|
1690
|
+
const outputDir = dirname$1(absEntry);
|
|
1691
|
+
packageDirs.push(outputDir);
|
|
1692
|
+
claimDir(outputDir, specifier);
|
|
1693
|
+
onPackageResolved?.(specifier, outputDir);
|
|
1694
|
+
packageMap[specifier] = relative(rootDir, absEntry);
|
|
1695
|
+
const outputExtensions = new Set(extensionsSet);
|
|
1696
|
+
outputExtensions.add(extname(absEntry));
|
|
1697
|
+
dirExtensions[outputDir] = [...outputExtensions];
|
|
1698
|
+
const outputFiles = discoverFiles(outputDir, outputDir, void 0, outputExtensions).filter((file) => {
|
|
1699
|
+
const relToOutput = relative(outputDir, file);
|
|
1700
|
+
return !PACKAGE_OUTPUT_EXCLUDES.some((pattern) => relToOutput.includes(pattern));
|
|
1701
|
+
});
|
|
1702
|
+
if (!outputFiles.includes(absEntry)) outputFiles.unshift(absEntry);
|
|
1703
|
+
for (const outputFile of outputFiles) {
|
|
1704
|
+
const outputRelPath = relative(rootDir, outputFile);
|
|
1705
|
+
if (alreadyIngested(outputRelPath)) {
|
|
1706
|
+
fileCount++;
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
let source;
|
|
1710
|
+
try {
|
|
1711
|
+
source = readFileSync(outputFile, "utf-8");
|
|
1712
|
+
} catch (err) {
|
|
1713
|
+
onUnreadable(outputRelPath, err);
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
onSourceRead?.(source, outputRelPath, outputFile);
|
|
1717
|
+
entries.push({
|
|
1718
|
+
path: outputRelPath,
|
|
1719
|
+
source
|
|
1720
|
+
});
|
|
1721
|
+
pushed.add(outputRelPath);
|
|
1722
|
+
fileOwners[outputRelPath] ??= specifier;
|
|
1723
|
+
fileCount++;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
outcomes.push({
|
|
1727
|
+
specifier,
|
|
1728
|
+
outcome: staleDist ? "stale-dist" : fileCount > 0 ? "resolved" : "empty",
|
|
1729
|
+
fileCount
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
return {
|
|
1733
|
+
entries,
|
|
1734
|
+
packageMap,
|
|
1735
|
+
sourceEntries,
|
|
1736
|
+
packageDirs,
|
|
1737
|
+
dirOwnerSets,
|
|
1738
|
+
dirExtensions,
|
|
1739
|
+
fileOwners,
|
|
1740
|
+
outcomes
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
function firstOwners(dirOwnerSets) {
|
|
1744
|
+
const owners = {};
|
|
1745
|
+
for (const [dir, specifiers] of Object.entries(dirOwnerSets)) if (specifiers.length > 0) owners[dir] = specifiers[0];
|
|
1746
|
+
return owners;
|
|
1747
|
+
}
|
|
1748
|
+
function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
|
|
1749
|
+
if (rejectedSpecifiers.size === 0) return collected;
|
|
1750
|
+
const rejectedDirs = Object.entries(collected.dirOwnerSets).filter(([, specs]) => specs.every((s) => rejectedSpecifiers.has(s))).map(([dir]) => dir);
|
|
1751
|
+
const underRejectedDir = (absPath) => rejectedDirs.some((dir) => isPathWithinRoot(dir, absPath));
|
|
1752
|
+
const targetRejected = (specifier, absTarget) => rejectedSpecifiers.has(specifier) || underRejectedDir(absTarget);
|
|
1753
|
+
const packageMap = {};
|
|
1754
|
+
for (const [specifier, relTarget] of Object.entries(collected.packageMap)) {
|
|
1755
|
+
if (targetRejected(specifier, resolve(rootDir, relTarget))) continue;
|
|
1756
|
+
packageMap[specifier] = relTarget;
|
|
1757
|
+
}
|
|
1758
|
+
const sourceEntries = /* @__PURE__ */ new Map();
|
|
1759
|
+
for (const [specifier, absEntry] of collected.sourceEntries) {
|
|
1760
|
+
if (targetRejected(specifier, absEntry)) continue;
|
|
1761
|
+
sourceEntries.set(specifier, absEntry);
|
|
1762
|
+
}
|
|
1763
|
+
const dirOwnerSets = {};
|
|
1764
|
+
for (const [dir, specs] of Object.entries(collected.dirOwnerSets)) {
|
|
1765
|
+
const kept = specs.filter((s) => !rejectedSpecifiers.has(s));
|
|
1766
|
+
if (kept.length === 0) continue;
|
|
1767
|
+
dirOwnerSets[dir] = kept;
|
|
1768
|
+
}
|
|
1769
|
+
const dirExtensions = {};
|
|
1770
|
+
for (const [dir, exts] of Object.entries(collected.dirExtensions)) {
|
|
1771
|
+
if (rejectedDirs.includes(dir)) continue;
|
|
1772
|
+
dirExtensions[dir] = exts;
|
|
1773
|
+
}
|
|
1774
|
+
const fileOwners = {};
|
|
1775
|
+
for (const [relPath, owner] of Object.entries(collected.fileOwners)) {
|
|
1776
|
+
if (rejectedSpecifiers.has(owner)) continue;
|
|
1777
|
+
fileOwners[relPath] = owner;
|
|
1778
|
+
}
|
|
1779
|
+
return {
|
|
1780
|
+
entries: collected.entries.filter((entry) => !rejectedSpecifiers.has(collected.fileOwners[entry.path])),
|
|
1781
|
+
packageMap,
|
|
1782
|
+
sourceEntries,
|
|
1783
|
+
packageDirs: collected.packageDirs.filter((dir) => !rejectedDirs.includes(dir)),
|
|
1784
|
+
dirOwnerSets,
|
|
1785
|
+
dirExtensions,
|
|
1786
|
+
fileOwners,
|
|
1787
|
+
outcomes: collected.outcomes
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
function unresolvableIncludesMessage(outcomes) {
|
|
1791
|
+
const unresolvable = outcomes.filter((record) => record.outcome === "unresolvable").map((record) => record.specifier);
|
|
1792
|
+
if (unresolvable.length === 0) return null;
|
|
1793
|
+
return `[animus-extract] unresolvable include specifier(s): ${unresolvable.join(", ")}`;
|
|
1794
|
+
}
|
|
1795
|
+
/** A stale dist silently skews merged registry content while discovery
|
|
1796
|
+
* compiles the fresh sources; null when no specifier is stale. */
|
|
1797
|
+
function staleDistIncludesMessage(outcomes) {
|
|
1798
|
+
const stale = outcomes.filter((record) => record.outcome === "stale-dist").map((record) => record.specifier);
|
|
1799
|
+
if (stale.length === 0) return null;
|
|
1800
|
+
return `[animus-extract] stale dist for include specifier(s): ${stale.join(", ")} — dist entry is older than the newest src/ file; rebuild the package(s) before extracting`;
|
|
1801
|
+
}
|
|
1802
|
+
function extractSystemFilePackages(systemFilePath) {
|
|
1803
|
+
let source;
|
|
1804
|
+
try {
|
|
1805
|
+
source = readFileSync(systemFilePath, "utf-8");
|
|
1806
|
+
} catch {
|
|
1807
|
+
return [];
|
|
1808
|
+
}
|
|
1809
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
1810
|
+
const constructorRegex = /createSystem\s*\(\s*\{[^}]*?\bincludes\s*:\s*\[([^\]]*)\]/gs;
|
|
1811
|
+
const chainRegex = /\.includes\s*\(\s*\[([^\]]*)\]\s*\)/gs;
|
|
1812
|
+
const collectIdentifiers = (regex) => {
|
|
1813
|
+
let match;
|
|
1814
|
+
while ((match = regex.exec(source)) !== null) {
|
|
1815
|
+
const inner = match[1];
|
|
1816
|
+
for (const token of inner.split(",")) {
|
|
1817
|
+
const id = token.trim();
|
|
1818
|
+
if (id && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(id)) identifiers.add(id);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
collectIdentifiers(constructorRegex);
|
|
1823
|
+
collectIdentifiers(chainRegex);
|
|
1824
|
+
const IDENT_START_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*/;
|
|
1825
|
+
const skipTrivia = (from) => {
|
|
1826
|
+
let pos = from;
|
|
1827
|
+
for (;;) {
|
|
1828
|
+
while (pos < source.length && /\s/.test(source[pos])) pos++;
|
|
1829
|
+
if (source.startsWith("//", pos)) {
|
|
1830
|
+
const newline = source.indexOf("\n", pos);
|
|
1831
|
+
pos = newline === -1 ? source.length : newline + 1;
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
if (source.startsWith("/*", pos)) {
|
|
1835
|
+
const close = source.indexOf("*/", pos + 2);
|
|
1836
|
+
pos = close === -1 ? source.length : close + 2;
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
return pos;
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
const consumeChainLinks = (from) => {
|
|
1843
|
+
let pos = from;
|
|
1844
|
+
for (;;) {
|
|
1845
|
+
let cursor = skipTrivia(pos);
|
|
1846
|
+
if (source[cursor] !== ".") return pos;
|
|
1847
|
+
cursor = skipTrivia(cursor + 1);
|
|
1848
|
+
const method = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
1849
|
+
if (method !== "extend" && method !== "from") return pos;
|
|
1850
|
+
cursor = skipTrivia(cursor + method.length);
|
|
1851
|
+
if (source[cursor] !== "(") return pos;
|
|
1852
|
+
cursor = skipTrivia(cursor + 1);
|
|
1853
|
+
const base = IDENT_START_RE.exec(source.slice(cursor))?.[0];
|
|
1854
|
+
if (!base) return pos;
|
|
1855
|
+
cursor = skipTrivia(cursor + base.length);
|
|
1856
|
+
while (source[cursor] === ".") {
|
|
1857
|
+
const afterDot = skipTrivia(cursor + 1);
|
|
1858
|
+
const segment = IDENT_START_RE.exec(source.slice(afterDot))?.[0];
|
|
1859
|
+
if (!segment) break;
|
|
1860
|
+
cursor = skipTrivia(afterDot + segment.length);
|
|
1861
|
+
}
|
|
1862
|
+
if (source[cursor] === ",") cursor = skipTrivia(cursor + 1);
|
|
1863
|
+
if (source[cursor] !== ")") return pos;
|
|
1864
|
+
identifiers.add(base);
|
|
1865
|
+
pos = cursor + 1;
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
const boundIdentifierBefore = (index) => {
|
|
1869
|
+
const match = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*$/.exec(source.slice(0, index));
|
|
1870
|
+
return match ? match[1] : null;
|
|
1871
|
+
};
|
|
1872
|
+
const chainRootIdentifiers = /* @__PURE__ */ new Set();
|
|
1873
|
+
const createSystemAnchor = /createSystem\s*\(/g;
|
|
1874
|
+
let anchorMatch;
|
|
1875
|
+
while ((anchorMatch = createSystemAnchor.exec(source)) !== null) {
|
|
1876
|
+
let pos = anchorMatch.index + anchorMatch[0].length;
|
|
1877
|
+
let depth = 1;
|
|
1878
|
+
while (pos < source.length && depth > 0) {
|
|
1879
|
+
const ch = source[pos];
|
|
1880
|
+
if (ch === "(") depth++;
|
|
1881
|
+
else if (ch === ")") depth--;
|
|
1882
|
+
pos++;
|
|
1883
|
+
}
|
|
1884
|
+
consumeChainLinks(pos);
|
|
1885
|
+
const bound = boundIdentifierBefore(anchorMatch.index);
|
|
1886
|
+
if (bound) chainRootIdentifiers.add(bound);
|
|
1887
|
+
}
|
|
1888
|
+
const scannedRoots = /* @__PURE__ */ new Set();
|
|
1889
|
+
for (;;) {
|
|
1890
|
+
const pendingRoots = [...chainRootIdentifiers].filter((root) => !scannedRoots.has(root));
|
|
1891
|
+
if (pendingRoots.length === 0) break;
|
|
1892
|
+
for (const root of pendingRoots) {
|
|
1893
|
+
scannedRoots.add(root);
|
|
1894
|
+
const rootUse = new RegExp(`(?<![a-zA-Z0-9_$])${root.replace(/\$/g, "\\$")}(?![a-zA-Z0-9_$])`, "g");
|
|
1895
|
+
let useMatch;
|
|
1896
|
+
while ((useMatch = rootUse.exec(source)) !== null) {
|
|
1897
|
+
const afterIdentifier = useMatch.index + useMatch[0].length;
|
|
1898
|
+
if (consumeChainLinks(afterIdentifier) === afterIdentifier) continue;
|
|
1899
|
+
const bound = boundIdentifierBefore(useMatch.index);
|
|
1900
|
+
if (bound) chainRootIdentifiers.add(bound);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
if (identifiers.size === 0) return [];
|
|
1905
|
+
const importMap = /* @__PURE__ */ new Map();
|
|
1906
|
+
const importRegex = /^\s*import\s+(?:([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*)?(?:\{([^}]*)\}|([a-zA-Z_$][a-zA-Z0-9_$]*))\s+from\s+['"]([^'"]+)['"]/gm;
|
|
1907
|
+
let importMatch;
|
|
1908
|
+
while ((importMatch = importRegex.exec(source)) !== null) {
|
|
1909
|
+
const [, comboDefault, namedImports, defaultImport, specifier] = importMatch;
|
|
1910
|
+
if (comboDefault) importMap.set(comboDefault, specifier);
|
|
1911
|
+
if (defaultImport) importMap.set(defaultImport, specifier);
|
|
1912
|
+
if (namedImports) for (const binding of namedImports.split(",")) {
|
|
1913
|
+
const parts = binding.trim().split(/\s+as\s+/);
|
|
1914
|
+
const localName = (parts[1] || parts[0]).trim();
|
|
1915
|
+
if (localName) importMap.set(localName, specifier);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
const systemFileDir = dirname$1(systemFilePath);
|
|
1919
|
+
const packages = /* @__PURE__ */ new Set();
|
|
1920
|
+
for (const id of identifiers) {
|
|
1921
|
+
const specifier = importMap.get(id);
|
|
1922
|
+
if (!specifier) continue;
|
|
1923
|
+
if (specifier.startsWith(".")) packages.add(resolve(systemFileDir, specifier));
|
|
1924
|
+
else packages.add(specifier);
|
|
1925
|
+
}
|
|
1926
|
+
return Array.from(packages);
|
|
1927
|
+
}
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region pipeline/source-corpus.ts
|
|
1930
|
+
function assembleRawEntries({ fileCache, externalFileOwners }) {
|
|
1931
|
+
const project = [];
|
|
1932
|
+
const external = [];
|
|
1933
|
+
for (const [path, { hash, source }] of fileCache) (externalFileOwners[path] === void 0 ? project : external).push({
|
|
1934
|
+
path,
|
|
1935
|
+
source,
|
|
1936
|
+
hash
|
|
1937
|
+
});
|
|
1938
|
+
project.sort((a, b) => compareDiscoveryOrder(a.path, b.path));
|
|
1939
|
+
return [...project, ...external];
|
|
1940
|
+
}
|
|
1941
|
+
function createSourceCorpus(host, ingestor = createSourceIngestor(host)) {
|
|
1942
|
+
let published = {
|
|
1943
|
+
analysisEntries: /* @__PURE__ */ new Map(),
|
|
1944
|
+
ownership: {}
|
|
1945
|
+
};
|
|
1946
|
+
return {
|
|
1947
|
+
async prepare(input) {
|
|
1948
|
+
const rawEntries = "fileCache" in input ? assembleRawEntries(input) : input;
|
|
1949
|
+
const ingested = await ingestor.ingest(rawEntries);
|
|
1950
|
+
return withoutInvalidOriginals(ingested, ingestor.surfaceDiagnostics(ingested.diagnostics));
|
|
1951
|
+
},
|
|
1952
|
+
publish(accepted) {
|
|
1953
|
+
ingestor.markPublished(accepted);
|
|
1954
|
+
published = {
|
|
1955
|
+
analysisEntries: new Map(accepted.analysisEntries.map((entry) => [entry.path, {
|
|
1956
|
+
hash: entry.hash,
|
|
1957
|
+
source: entry.source
|
|
1958
|
+
}])),
|
|
1959
|
+
ownership: accepted.ownership
|
|
1960
|
+
};
|
|
1961
|
+
},
|
|
1962
|
+
get published() {
|
|
1963
|
+
return published;
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
//#endregion
|
|
1968
|
+
//#region pipeline/asset-placeholders.ts
|
|
1969
|
+
const ASSET_PLACEHOLDER_PREFIX = "animus-asset:";
|
|
1970
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1971
|
+
const PREFIX_RE = escapeRegExp(ASSET_PLACEHOLDER_PREFIX);
|
|
1972
|
+
const QUOTED_PLACEHOLDER_RE = new RegExp(`(['"])${PREFIX_RE}([^'"]*?)\\1`, "g");
|
|
1973
|
+
const BARE_PLACEHOLDER_RE = new RegExp(`${PREFIX_RE}([^'")\\s]+)`, "g");
|
|
1974
|
+
function findAssetSpecifiers(css) {
|
|
1975
|
+
if (!css.includes("animus-asset:")) return [];
|
|
1976
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1977
|
+
const remainder = css.replace(QUOTED_PLACEHOLDER_RE, (_match, _quote, specifier) => {
|
|
1978
|
+
seen.add(specifier);
|
|
1979
|
+
return "";
|
|
1980
|
+
});
|
|
1981
|
+
for (const match of remainder.matchAll(BARE_PLACEHOLDER_RE)) seen.add(match[1]);
|
|
1982
|
+
return [...seen];
|
|
1983
|
+
}
|
|
1984
|
+
function substituteAssetPlaceholders(css, urlBySpecifier) {
|
|
1985
|
+
if (urlBySpecifier.size === 0) return css;
|
|
1986
|
+
if (!css.includes("animus-asset:")) return css;
|
|
1987
|
+
const specifiers = [...urlBySpecifier.keys()].sort((a, b) => b.length - a.length);
|
|
1988
|
+
let out = css;
|
|
1989
|
+
for (const specifier of specifiers) {
|
|
1990
|
+
const placeholder = new RegExp(escapeRegExp(ASSET_PLACEHOLDER_PREFIX + specifier) + String.raw`(?=['")\s]|$)`, "g");
|
|
1991
|
+
out = out.replace(placeholder, () => urlBySpecifier.get(specifier));
|
|
1992
|
+
}
|
|
1993
|
+
return out;
|
|
1994
|
+
}
|
|
1995
|
+
//#endregion
|
|
1996
|
+
//#region pipeline/resolve-asset.ts
|
|
1997
|
+
/**
|
|
1998
|
+
* Asset resolution for hosts with no bundler hook. Host path aliases apply
|
|
1999
|
+
* FIRST, so `asset('@fonts/x')` resolves as the alias does in app modules.
|
|
2000
|
+
*/
|
|
2001
|
+
const requireByRoot = /* @__PURE__ */ new Map();
|
|
2002
|
+
function requireAnchoredAt(rootDir) {
|
|
2003
|
+
let req = requireByRoot.get(rootDir);
|
|
2004
|
+
if (!req) {
|
|
2005
|
+
req = createRequire$1(join$1(rootDir, "package.json"));
|
|
2006
|
+
requireByRoot.set(rootDir, req);
|
|
2007
|
+
}
|
|
2008
|
+
return req;
|
|
2009
|
+
}
|
|
2010
|
+
function packageRootFromEntry(entry) {
|
|
2011
|
+
let current = dirname$1(entry);
|
|
2012
|
+
while (true) {
|
|
2013
|
+
if (existsSync(join$1(current, "package.json"))) return current;
|
|
2014
|
+
const parent = dirname$1(current);
|
|
2015
|
+
if (parent === current) return null;
|
|
2016
|
+
current = parent;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
const aliasTableCache = /* @__PURE__ */ new Map();
|
|
2020
|
+
/**
|
|
2021
|
+
* A parse failure throws: an empty table would disable every alias-based
|
|
2022
|
+
* `asset()` and ship dangling `url()`s as a successful build.
|
|
2023
|
+
*/
|
|
2024
|
+
function parseAliasTable(pathAliasesJson) {
|
|
2025
|
+
const cached = aliasTableCache.get(pathAliasesJson);
|
|
2026
|
+
if (cached) return cached;
|
|
2027
|
+
const aliases = parseInternalWire(pathAliasesJson, "pathAliasesJson (the host alias table from buildPathAliasesJson)").aliases ?? [];
|
|
2028
|
+
aliasTableCache.set(pathAliasesJson, aliases);
|
|
2029
|
+
return aliases;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Map a specifier through the host alias table, whose entries arrive sorted
|
|
2033
|
+
* longest-pattern-first. Returns an absolute path to an EXISTING file.
|
|
2034
|
+
*/
|
|
2035
|
+
function resolveThroughPathAliases(specifier, rootDir, pathAliasesJson) {
|
|
2036
|
+
if (!pathAliasesJson) return null;
|
|
2037
|
+
const aliases = parseAliasTable(pathAliasesJson);
|
|
2038
|
+
for (const alias of aliases) {
|
|
2039
|
+
let mapped = null;
|
|
2040
|
+
if (alias.type === "exact") {
|
|
2041
|
+
if (alias.pattern === specifier) mapped = alias.replacement;
|
|
2042
|
+
} else if (specifier.startsWith(alias.pattern)) mapped = alias.replacement + specifier.slice(alias.pattern.length);
|
|
2043
|
+
if (mapped === null) continue;
|
|
2044
|
+
const absolute = isAbsolute(mapped) ? mapped : join$1(rootDir, mapped);
|
|
2045
|
+
if (existsSync(absolute)) return absolute;
|
|
2046
|
+
}
|
|
2047
|
+
return null;
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Resolve an asset specifier: host aliases, then Node resolution anchored at
|
|
2051
|
+
* `rootDir`, then the package root. Strict gating stays at the caller.
|
|
2052
|
+
*/
|
|
2053
|
+
function resolveAssetFile(specifier, rootDir, pathAliasesJson) {
|
|
2054
|
+
const aliased = resolveThroughPathAliases(specifier, rootDir, pathAliasesJson);
|
|
2055
|
+
if (aliased) return aliased;
|
|
2056
|
+
const requireFromRoot = requireAnchoredAt(rootDir);
|
|
2057
|
+
try {
|
|
2058
|
+
return requireFromRoot.resolve(specifier);
|
|
2059
|
+
} catch {}
|
|
2060
|
+
const segments = specifier.split("/");
|
|
2061
|
+
const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
|
2062
|
+
const subpath = specifier.slice(packageName.length + 1);
|
|
2063
|
+
if (!subpath) return null;
|
|
2064
|
+
for (const modulesDir of requireFromRoot.resolve.paths(packageName) ?? []) {
|
|
2065
|
+
const packageRoot = join$1(modulesDir, packageName);
|
|
2066
|
+
if (!existsSync(join$1(packageRoot, "package.json"))) continue;
|
|
2067
|
+
const candidate = join$1(packageRoot, subpath);
|
|
2068
|
+
if (existsSync(candidate)) return candidate;
|
|
2069
|
+
}
|
|
2070
|
+
try {
|
|
2071
|
+
const packageRoot = packageRootFromEntry(requireFromRoot.resolve(packageName));
|
|
2072
|
+
if (!packageRoot) return null;
|
|
2073
|
+
const candidate = join$1(packageRoot, subpath);
|
|
2074
|
+
return existsSync(candidate) ? candidate : null;
|
|
2075
|
+
} catch {
|
|
2076
|
+
return null;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
//#endregion
|
|
2080
|
+
//#region pipeline/correlate-external-tokens.ts
|
|
2081
|
+
/**
|
|
2082
|
+
* Index the loader-captured theme manifests by owning specifier. An
|
|
2083
|
+
* unparseable capture throws: an empty index reads as "no source defines it".
|
|
2084
|
+
*/
|
|
2085
|
+
function buildSourceTokenIndex(opts) {
|
|
2086
|
+
const index = /* @__PURE__ */ new Map();
|
|
2087
|
+
if (!opts.sourceThemeManifestsJson) return index;
|
|
2088
|
+
const manifests = parseInternalWire(opts.sourceThemeManifestsJson, "sourceThemeManifestsJson (the system loader's built-theme capture)");
|
|
2089
|
+
const realDirOwners = [];
|
|
2090
|
+
const addOwnerDir = (dir, specifier) => {
|
|
2091
|
+
if (!realDirOwners.some((entry) => entry.dir === dir)) realDirOwners.push({
|
|
2092
|
+
dir,
|
|
2093
|
+
specifier
|
|
2094
|
+
});
|
|
2095
|
+
};
|
|
2096
|
+
for (const [dir, specifier] of Object.entries(opts.dirOwners)) {
|
|
2097
|
+
let real = dir;
|
|
2098
|
+
try {
|
|
2099
|
+
real = realpathSync(dir);
|
|
2100
|
+
} catch {}
|
|
2101
|
+
addOwnerDir(real, specifier);
|
|
2102
|
+
let packageRoot = real;
|
|
2103
|
+
while (packageRoot !== dirname$1(packageRoot) && !existsSync(join$1(packageRoot, "package.json"))) packageRoot = dirname$1(packageRoot);
|
|
2104
|
+
if (existsSync(join$1(packageRoot, "package.json"))) addOwnerDir(packageRoot, specifier);
|
|
2105
|
+
}
|
|
2106
|
+
for (const [modulePath, exports] of Object.entries(manifests)) {
|
|
2107
|
+
const owner = realDirOwners.find(({ dir }) => isPathWithinRoot(dir, modulePath));
|
|
2108
|
+
if (!owner) continue;
|
|
2109
|
+
let tokens = index.get(owner.specifier);
|
|
2110
|
+
if (!tokens) {
|
|
2111
|
+
tokens = /* @__PURE__ */ new Set();
|
|
2112
|
+
index.set(owner.specifier, tokens);
|
|
2113
|
+
}
|
|
2114
|
+
for (const paths of Object.values(exports)) for (const token of paths) tokens.add(token);
|
|
2115
|
+
}
|
|
2116
|
+
return index;
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* One message per distinct (component, token, specifier) whose source
|
|
2120
|
+
* defines the token. Severity routing stays at the caller.
|
|
2121
|
+
*/
|
|
2122
|
+
function correlateExternalTokenDiagnostics(opts) {
|
|
2123
|
+
const messages = [];
|
|
2124
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2125
|
+
for (const diagnostic of opts.diagnostics ?? []) {
|
|
2126
|
+
if (diagnostic.kind !== "external-token-candidate" || !diagnostic.token) continue;
|
|
2127
|
+
const specifier = opts.fileOwners[diagnostic.file];
|
|
2128
|
+
if (!specifier) continue;
|
|
2129
|
+
if (!opts.sourceTokens.get(specifier)?.has(diagnostic.token)) continue;
|
|
2130
|
+
const key = `${diagnostic.component}\u0000${diagnostic.token}\u0000${specifier}`;
|
|
2131
|
+
if (seen.has(key)) continue;
|
|
2132
|
+
seen.add(key);
|
|
2133
|
+
messages.push(`${diagnostic.component} (from '${specifier}') references token '${diagnostic.token}', which the consumer theme does not define — inherit the source's tokens with createTheme().extend(...) using the tokens (or bundle) export of '${specifier}'`);
|
|
2134
|
+
}
|
|
2135
|
+
return messages;
|
|
2136
|
+
}
|
|
2137
|
+
const indexCache = /* @__PURE__ */ new WeakMap();
|
|
2138
|
+
/**
|
|
2139
|
+
* The one cross-source token-contract gate both plugins share, so the hosts
|
|
2140
|
+
* cannot drift on wiring or severity.
|
|
2141
|
+
*/
|
|
2142
|
+
function enforceExternalTokenContracts(opts) {
|
|
2143
|
+
const manifestsJson = opts.sourceThemeManifestsJson ?? "";
|
|
2144
|
+
const cached = indexCache.get(opts.dirOwners);
|
|
2145
|
+
let sourceTokens;
|
|
2146
|
+
if (cached && cached.manifestsJson === manifestsJson) sourceTokens = cached.index;
|
|
2147
|
+
else {
|
|
2148
|
+
sourceTokens = buildSourceTokenIndex({
|
|
2149
|
+
sourceThemeManifestsJson: opts.sourceThemeManifestsJson,
|
|
2150
|
+
dirOwners: opts.dirOwners
|
|
2151
|
+
});
|
|
2152
|
+
indexCache.set(opts.dirOwners, {
|
|
2153
|
+
manifestsJson,
|
|
2154
|
+
index: sourceTokens
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
const messages = correlateExternalTokenDiagnostics({
|
|
2158
|
+
diagnostics: opts.diagnostics,
|
|
2159
|
+
fileOwners: opts.fileOwners,
|
|
2160
|
+
sourceTokens
|
|
2161
|
+
});
|
|
2162
|
+
if (messages.length === 0) return;
|
|
2163
|
+
if (opts.strict) throw new Error(`${opts.prefix} ${messages.join("\n")}`);
|
|
2164
|
+
for (const message of messages) opts.warn(message);
|
|
2165
|
+
}
|
|
2166
|
+
//#endregion
|
|
2167
|
+
//#region pipeline/path-aliases.ts
|
|
2168
|
+
function buildPathAliasesJson(pairs, rootDir) {
|
|
2169
|
+
const entries = [];
|
|
2170
|
+
for (const { pattern, target, kind } of pairs) {
|
|
2171
|
+
const replacement = target.startsWith(rootDir) ? target.slice(rootDir.length + 1) : target;
|
|
2172
|
+
if (kind ? kind === "exact" : /\.\w+$/.test(replacement)) entries.push({
|
|
2173
|
+
pattern,
|
|
2174
|
+
replacement,
|
|
2175
|
+
type: "exact"
|
|
2176
|
+
});
|
|
2177
|
+
else entries.push({
|
|
2178
|
+
pattern: pattern.endsWith("/") ? pattern : pattern + "/",
|
|
2179
|
+
replacement: replacement.endsWith("/") ? replacement : replacement + "/",
|
|
2180
|
+
type: "prefix"
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
entries.sort((a, b) => b.pattern.length - a.pattern.length);
|
|
2184
|
+
if (entries.length === 0) return null;
|
|
2185
|
+
return {
|
|
2186
|
+
json: JSON.stringify({ aliases: entries }),
|
|
2187
|
+
count: entries.length
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
//#endregion
|
|
2191
|
+
//#region pipeline/post-process-css.ts
|
|
2192
|
+
function resolveLightningTargets(explicitTargets, rootDir) {
|
|
2193
|
+
let queries;
|
|
2194
|
+
if (explicitTargets) queries = Array.isArray(explicitTargets) ? explicitTargets : [explicitTargets];
|
|
2195
|
+
else {
|
|
2196
|
+
const detected = browserslist(void 0, { path: rootDir });
|
|
2197
|
+
queries = detected.length > 0 ? detected : browserslist("defaults");
|
|
2198
|
+
}
|
|
2199
|
+
const [firstQuery] = queries;
|
|
2200
|
+
return browserslistToTargets(firstQuery !== void 0 && firstQuery.includes(" ") ? browserslist(queries) : queries);
|
|
2201
|
+
}
|
|
2202
|
+
function postProcessCss(css, opts) {
|
|
2203
|
+
if (!css) return css;
|
|
2204
|
+
try {
|
|
2205
|
+
return transform({
|
|
2206
|
+
filename: "animus-extracted.css",
|
|
2207
|
+
code: Buffer.from(css),
|
|
2208
|
+
minify: opts.minify,
|
|
2209
|
+
targets: opts.targets
|
|
2210
|
+
}).code.toString();
|
|
2211
|
+
} catch (e) {
|
|
2212
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2213
|
+
(opts.warnFn ?? console.warn)(`[animus] Lightning CSS post-processing failed: ${msg}`);
|
|
2214
|
+
return css;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
//#endregion
|
|
2218
|
+
//#region pipeline/manifest-diagnostics.ts
|
|
2219
|
+
/** Stable code for selector forms with no substitutable subject; the
|
|
2220
|
+
* extraction engine mints the same string. */
|
|
2221
|
+
const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
|
|
2222
|
+
/** The one matcher for the engine's unresolved-parent chain-drop message;
|
|
2223
|
+
* consumers match through the exported helpers, never their own copy. */
|
|
2224
|
+
const UNRESOLVED_PARENT_RE = /chain dropped: could not resolve parent component '([^']+)'/;
|
|
2225
|
+
function isUnresolvedParentDrop(diagnostic) {
|
|
2226
|
+
return UNRESOLVED_PARENT_RE.test(diagnostic.message);
|
|
2227
|
+
}
|
|
2228
|
+
function unresolvedParentName(diagnostic) {
|
|
2229
|
+
return UNRESOLVED_PARENT_RE.exec(diagnostic.message)?.[1] ?? null;
|
|
2230
|
+
}
|
|
2231
|
+
/** True when the selector carries at least one substitutable `&` subject
|
|
2232
|
+
* outside quoted text. */
|
|
2233
|
+
function hasSelectorSubject(value) {
|
|
2234
|
+
let quote = null;
|
|
2235
|
+
let escaped = false;
|
|
2236
|
+
for (const c of value) {
|
|
2237
|
+
if (escaped) {
|
|
2238
|
+
escaped = false;
|
|
2239
|
+
continue;
|
|
2240
|
+
}
|
|
2241
|
+
if (c === "\\") {
|
|
2242
|
+
escaped = true;
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
if (quote !== null) {
|
|
2246
|
+
if (c === quote) quote = null;
|
|
2247
|
+
} else if (c === "\"" || c === "'") quote = c;
|
|
2248
|
+
else if (c === "&") return true;
|
|
2249
|
+
}
|
|
2250
|
+
return false;
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Coded diagnostics for selector-shaped alias values with no substitutable
|
|
2254
|
+
* subject; alias values never reach the evaluator's own key guard.
|
|
2255
|
+
*/
|
|
2256
|
+
function collectSelectorAliasDiagnostics(selectorAliasesJson) {
|
|
2257
|
+
if (!selectorAliasesJson) return [];
|
|
2258
|
+
const aliases = parseInternalWire(selectorAliasesJson, "selectorAliasesJson (the system loader's selector-alias registry)");
|
|
2259
|
+
const diagnostics = [];
|
|
2260
|
+
for (const [name, value] of Object.entries(aliases)) if (value.includes("&") && !hasSelectorSubject(value)) diagnostics.push({
|
|
2261
|
+
file: "system",
|
|
2262
|
+
component: name,
|
|
2263
|
+
kind: "warn",
|
|
2264
|
+
message: `selector alias '${name}' value '${value}' has no substitutable '&' subject outside quoted text (${SELECTOR_UNSUPPORTED_SUBJECT})`,
|
|
2265
|
+
code: SELECTOR_UNSUPPORTED_SUBJECT,
|
|
2266
|
+
severity: severityFor(SELECTOR_UNSUPPORTED_SUBJECT)
|
|
2267
|
+
});
|
|
2268
|
+
return diagnostics;
|
|
2269
|
+
}
|
|
2270
|
+
const UNREADABLE_SOURCE_FILE = "animus.ingestion.unreadable-source-file";
|
|
2271
|
+
/**
|
|
2272
|
+
* A configured source file that could not be read is a LOST INPUT: the
|
|
2273
|
+
* emitted artifacts silently lack whatever that file declared.
|
|
2274
|
+
*/
|
|
2275
|
+
function unreadableSourceDiagnostic(relPath, error) {
|
|
2276
|
+
return {
|
|
2277
|
+
file: relPath,
|
|
2278
|
+
component: "source",
|
|
2279
|
+
kind: "warn",
|
|
2280
|
+
message: `configured source file ${relPath} could not be read and was skipped: ${String(error)}`,
|
|
2281
|
+
code: UNREADABLE_SOURCE_FILE,
|
|
2282
|
+
severity: severityFor(UNREADABLE_SOURCE_FILE)
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
/** The collision entry code minted by the system package's merge. */
|
|
2286
|
+
const VOCABULARY_COLLISION = "animus.vocabulary.collision";
|
|
2287
|
+
/** Minted when a sealed kit with registered vocabulary is consumed through
|
|
2288
|
+
* the deprecated `from()`/`includes:` verbs. */
|
|
2289
|
+
const VOCABULARY_LEGACY_VERB = "animus.vocabulary.legacy-verb";
|
|
2290
|
+
const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
|
|
2291
|
+
[SELECTOR_UNSUPPORTED_SUBJECT, "error"],
|
|
2292
|
+
[UNREADABLE_SOURCE_FILE, "error"],
|
|
2293
|
+
[VOCABULARY_COLLISION, "warn"],
|
|
2294
|
+
[VOCABULARY_LEGACY_VERB, "warn"]
|
|
2295
|
+
]);
|
|
2296
|
+
/** An unlisted code is `warn`: a witness kind from a newer system package
|
|
2297
|
+
* must not fail the strict build of a host that predates its writer. */
|
|
2298
|
+
function severityFor(code) {
|
|
2299
|
+
return (code === void 0 ? void 0 : DIAGNOSTIC_SEVERITY.get(code)) ?? "warn";
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* The one mapper of a sealed system's witness entries for every host: the
|
|
2303
|
+
* loader's evaluation host shims `console`, so the record is the channel.
|
|
2304
|
+
*/
|
|
2305
|
+
function vocabularyWitnessDiagnostics(vocabularyWitnessesJson) {
|
|
2306
|
+
if (!vocabularyWitnessesJson) return [];
|
|
2307
|
+
const entries = parseInternalWire(vocabularyWitnessesJson, "vocabularyWitnessesJson (the sealed system's vocabulary witness record)");
|
|
2308
|
+
const diagnostics = [];
|
|
2309
|
+
for (const entry of entries) if (entry.code === "animus.vocabulary.collision") diagnostics.push({
|
|
2310
|
+
file: "system",
|
|
2311
|
+
component: entry.name ?? "keyframes",
|
|
2312
|
+
kind: "warn",
|
|
2313
|
+
message: `keyframes vocabulary "${entry.name}" is registered by both ${entry.loser} and ${entry.winner} — ${entry.winner} wins; rename one collection (${entry.code})`,
|
|
2314
|
+
code: entry.code,
|
|
2315
|
+
severity: severityFor(entry.code)
|
|
2316
|
+
});
|
|
2317
|
+
else if (entry.code === "animus.vocabulary.legacy-verb") diagnostics.push({
|
|
2318
|
+
file: "system",
|
|
2319
|
+
component: "keyframes",
|
|
2320
|
+
kind: "warn",
|
|
2321
|
+
message: `a sealed system (${entry.source ?? `'${entry.verb}' source`}) with registered vocabulary [${(entry.names ?? []).join(", ")}] was consumed through the deprecated '${entry.verb}' verb, which cannot carry it — those collections do NOT reach this consumer; use createSystem().extend(source) (${entry.code})`,
|
|
2322
|
+
code: entry.code,
|
|
2323
|
+
severity: severityFor(entry.code)
|
|
2324
|
+
});
|
|
2325
|
+
else diagnostics.push({
|
|
2326
|
+
file: "system",
|
|
2327
|
+
component: "vocabulary",
|
|
2328
|
+
kind: "warn",
|
|
2329
|
+
message: `unrecognized vocabulary witness entry ${JSON.stringify(entry)} — a newer @animus-ui/system may have recorded a witness kind this host predates${entry.code ? ` (${entry.code})` : ""}`,
|
|
2330
|
+
code: entry.code,
|
|
2331
|
+
severity: severityFor(entry.code)
|
|
2332
|
+
});
|
|
2333
|
+
return diagnostics;
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* The single strict-escalation policy point for both extraction plugins:
|
|
2337
|
+
* error-severity diagnostics throw together under `strict`, else warn.
|
|
2338
|
+
*/
|
|
2339
|
+
function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
|
|
2340
|
+
const errors = [];
|
|
2341
|
+
const diagnostics = policy.prepend?.length ? [...policy.prepend, ...manifest.diagnostics ?? []] : manifest.diagnostics ?? [];
|
|
2342
|
+
for (const diagnostic of diagnostics) {
|
|
2343
|
+
let line = null;
|
|
2344
|
+
if (diagnostic.kind === "bail") line = `⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`;
|
|
2345
|
+
else if (diagnostic.kind === "skip") line = `⚠ ${diagnostic.component}: skipped ${diagnostic.message}`;
|
|
2346
|
+
else if (diagnostic.kind === "warn") line = `⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
|
|
2347
|
+
if (line === null) continue;
|
|
2348
|
+
if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
|
|
2349
|
+
if (policy.strict && diagnostic.severity === "error") {
|
|
2350
|
+
errors.push(`${diagnostic.code ?? "error"} — ${diagnostic.component}: ${diagnostic.message}`);
|
|
2351
|
+
continue;
|
|
2352
|
+
}
|
|
2353
|
+
warn(line);
|
|
2354
|
+
}
|
|
2355
|
+
if (errors.length > 0) throw new Error(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
|
|
2356
|
+
}
|
|
2357
|
+
//#endregion
|
|
2358
|
+
//#region pipeline/unit-fallback.ts
|
|
2359
|
+
function applyUnitFallback(css) {
|
|
2360
|
+
return css.replace(/([a-z-]+)\s*:\s*([^;{}]+);/g, (match, prop, value) => {
|
|
2361
|
+
if (isUnitlessProperty(prop)) return match;
|
|
2362
|
+
let depth = 0;
|
|
2363
|
+
let fixed = "";
|
|
2364
|
+
let i = 0;
|
|
2365
|
+
while (i < value.length) if (value[i] === "(") {
|
|
2366
|
+
depth++;
|
|
2367
|
+
fixed += value[i];
|
|
2368
|
+
i++;
|
|
2369
|
+
} else if (value[i] === ")") {
|
|
2370
|
+
depth--;
|
|
2371
|
+
fixed += value[i];
|
|
2372
|
+
i++;
|
|
2373
|
+
} else if (depth > 0) {
|
|
2374
|
+
fixed += value[i];
|
|
2375
|
+
i++;
|
|
2376
|
+
} else {
|
|
2377
|
+
const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
|
|
2378
|
+
if (numMatch) {
|
|
2379
|
+
const num = numMatch[1];
|
|
2380
|
+
const after = value[i + num.length];
|
|
2381
|
+
if (after && /[a-z%]/i.test(after)) fixed += num;
|
|
2382
|
+
else fixed += num + "px";
|
|
2383
|
+
i += num.length;
|
|
2384
|
+
} else {
|
|
2385
|
+
fixed += value[i];
|
|
2386
|
+
i++;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
return fixed !== value ? `${prop}:${fixed};` : match;
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
//#endregion
|
|
2393
|
+
//#region pipeline/run-analysis.ts
|
|
2394
|
+
/** The named `analyzeProject` input set, and the persistence shape an
|
|
2395
|
+
* isolated process replays the analysis from. */
|
|
2396
|
+
function buildAnalysisInputs(opts) {
|
|
2397
|
+
const emitterConfig = {
|
|
2398
|
+
runtime_import: opts.emitter.runtimeImport,
|
|
2399
|
+
css_module_id: opts.emitter.cssModuleId
|
|
2400
|
+
};
|
|
2401
|
+
if (opts.emitter.systemPropsModuleId) emitterConfig.system_props_module_id = opts.emitter.systemPropsModuleId;
|
|
2402
|
+
return {
|
|
2403
|
+
filesJson: JSON.stringify(opts.fileEntries),
|
|
2404
|
+
scalesJson: opts.system.scalesJson,
|
|
2405
|
+
variableMapJson: opts.system.variableMapJson,
|
|
2406
|
+
contextualVarsJson: opts.system.contextualVarsJson,
|
|
2407
|
+
propConfigJson: opts.system.propConfigJson,
|
|
2408
|
+
groupRegistryJson: opts.system.groupRegistryJson,
|
|
2409
|
+
packageResolutionJson: JSON.stringify(opts.packageMap),
|
|
2410
|
+
devMode: opts.devMode,
|
|
2411
|
+
emitterConfigJson: JSON.stringify(emitterConfig),
|
|
2412
|
+
selectorAliasesJson: opts.system.selectorAliasesJson,
|
|
2413
|
+
globalStyleBlocksJson: opts.system.globalStyleBlocksJson,
|
|
2414
|
+
pathAliasesJson: opts.pathAliasesJson,
|
|
2415
|
+
keyframesJson: opts.system.keyframesJson,
|
|
2416
|
+
staticCssJson: opts.staticCssJson ?? null,
|
|
2417
|
+
conditionAliasesJson: opts.system.conditionAliasesJson ?? null,
|
|
2418
|
+
transformSourcesJson: opts.system.transformSourcesJson ?? null,
|
|
2419
|
+
externalDirsJson: opts.externalDirs?.length && hasSourceThemeManifests(opts.system) ? JSON.stringify(opts.externalDirs) : null
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
function hasSourceThemeManifests(system) {
|
|
2423
|
+
const json = system.sourceThemeManifestsJson ?? "";
|
|
2424
|
+
return json.length > 0 && json !== "{}";
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* The one analysis invocation both plugins share. Error handling stays at
|
|
2428
|
+
* the call site (strict-mode throw vs warn).
|
|
2429
|
+
*/
|
|
2430
|
+
function runProjectAnalysis(engineApi, opts) {
|
|
2431
|
+
const { analyzeProject } = engineApi();
|
|
2432
|
+
let t = performance.now();
|
|
2433
|
+
const inputs = buildAnalysisInputs(opts);
|
|
2434
|
+
const serializeMs = Math.round(performance.now() - t);
|
|
2435
|
+
t = performance.now();
|
|
2436
|
+
const manifestJson = analyzeProject(...buildAnalyzeProjectArgs(inputs));
|
|
2437
|
+
const extractMs = Math.round(performance.now() - t);
|
|
2438
|
+
t = performance.now();
|
|
2439
|
+
const manifest = JSON.parse(manifestJson);
|
|
2440
|
+
surfaceManifestDiagnostics(manifest, opts.warn, {
|
|
2441
|
+
strict: opts.strict,
|
|
2442
|
+
prepend: [...collectSelectorAliasDiagnostics(opts.system.selectorAliasesJson), ...opts.extraDiagnostics ?? []]
|
|
2443
|
+
});
|
|
2444
|
+
const parseMs = Math.round(performance.now() - t);
|
|
2445
|
+
return {
|
|
2446
|
+
manifest,
|
|
2447
|
+
manifestJson,
|
|
2448
|
+
globalCss: manifest.sheets.global,
|
|
2449
|
+
componentCss: applyUnitFallback(manifest.css),
|
|
2450
|
+
inputs,
|
|
2451
|
+
timings: {
|
|
2452
|
+
serializeMs,
|
|
2453
|
+
extractMs,
|
|
2454
|
+
parseMs
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Clear the engine's per-file analysis cache so stale results never bleed
|
|
2460
|
+
* into a fresh run. Engines without the capability are tolerated.
|
|
2461
|
+
*/
|
|
2462
|
+
function clearEngineCache(engineApi) {
|
|
2463
|
+
try {
|
|
2464
|
+
const { clearAnalysisCache } = engineApi();
|
|
2465
|
+
clearAnalysisCache();
|
|
2466
|
+
} catch {}
|
|
2467
|
+
}
|
|
2468
|
+
//#endregion
|
|
2469
|
+
//#region pipeline/utils.ts
|
|
2470
|
+
function camelToKebab(str) {
|
|
2471
|
+
return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
2472
|
+
}
|
|
2473
|
+
function stableStringify(value) {
|
|
2474
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
2475
|
+
if (isKeyedReference(value)) return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
2476
|
+
return JSON.stringify(value);
|
|
2477
|
+
}
|
|
2478
|
+
/** Decided by an intrinsic only functions accept, so a spoofed
|
|
2479
|
+
* `Symbol.toStringTag` cannot pass a plain object off as a function. */
|
|
2480
|
+
function isCallable(value) {
|
|
2481
|
+
try {
|
|
2482
|
+
Function.prototype.toString.call(value);
|
|
2483
|
+
return true;
|
|
2484
|
+
} catch {
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
function isKeyedReference(value) {
|
|
2489
|
+
return Object(value) === value && !isCallable(value);
|
|
2490
|
+
}
|
|
2491
|
+
//#endregion
|
|
2492
|
+
//#region pipeline/static-css.ts
|
|
2493
|
+
/** The `staticCss` option declares emission the JSX scanner cannot observe
|
|
2494
|
+
* (config-driven variants, spread props); the engine applies it as usage. */
|
|
2495
|
+
function serializeStaticCss(config) {
|
|
2496
|
+
if (!config) return null;
|
|
2497
|
+
const json = stableStringify(config);
|
|
2498
|
+
return json === "{}" ? null : json;
|
|
2499
|
+
}
|
|
2500
|
+
//#endregion
|
|
2501
|
+
//#region pipeline/prefix.ts
|
|
2502
|
+
function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson) {
|
|
2503
|
+
if (!prefix) return {
|
|
2504
|
+
variableMapJson,
|
|
2505
|
+
variableCss,
|
|
2506
|
+
themeJson,
|
|
2507
|
+
contextualVarsJson
|
|
2508
|
+
};
|
|
2509
|
+
const varRefRe = /var\(--([a-zA-Z][\w-]*)\)/g;
|
|
2510
|
+
const map = JSON.parse(variableMapJson);
|
|
2511
|
+
const prefixed = {};
|
|
2512
|
+
for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
|
|
2513
|
+
let css = variableCss;
|
|
2514
|
+
css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
|
|
2515
|
+
css = css.replace(varRefRe, `var(--${prefix}-$1)`);
|
|
2516
|
+
const result = {
|
|
2517
|
+
variableMapJson: JSON.stringify(prefixed),
|
|
2518
|
+
variableCss: css
|
|
2519
|
+
};
|
|
2520
|
+
if (themeJson) result.themeJson = themeJson.replace(varRefRe, `var(--${prefix}-$1)`);
|
|
2521
|
+
if (contextualVarsJson) {
|
|
2522
|
+
const ctxVars = JSON.parse(contextualVarsJson);
|
|
2523
|
+
const prefixedCtx = {};
|
|
2524
|
+
for (const [scale, names] of Object.entries(ctxVars)) prefixedCtx[scale] = names.map((name) => `${prefix}-${name}`);
|
|
2525
|
+
result.contextualVarsJson = JSON.stringify(prefixedCtx);
|
|
2526
|
+
}
|
|
2527
|
+
return result;
|
|
2528
|
+
}
|
|
2529
|
+
//#endregion
|
|
2530
|
+
//#region pipeline/system-config.ts
|
|
2531
|
+
/**
|
|
2532
|
+
* Load and normalize a SystemInstance; `prefix` namespaces every CSS
|
|
2533
|
+
* variable name. Error handling stays at the call site.
|
|
2534
|
+
*/
|
|
2535
|
+
function loadSystemConfig(engineApi, opts) {
|
|
2536
|
+
const { loadSystemModule } = engineApi();
|
|
2537
|
+
const config = loadSystemModule(opts.systemPath, opts.rootDir);
|
|
2538
|
+
let scalesJson = config.scalesJson;
|
|
2539
|
+
let variableMapJson = config.variableMapJson;
|
|
2540
|
+
let variableCss = config.variableCss;
|
|
2541
|
+
let contextualVarsJson = config.contextualVarsJson || null;
|
|
2542
|
+
if (opts.prefix) {
|
|
2543
|
+
const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0);
|
|
2544
|
+
variableMapJson = prefixed.variableMapJson;
|
|
2545
|
+
variableCss = prefixed.variableCss;
|
|
2546
|
+
if (prefixed.themeJson) scalesJson = prefixed.themeJson;
|
|
2547
|
+
if (prefixed.contextualVarsJson) contextualVarsJson = prefixed.contextualVarsJson;
|
|
2548
|
+
}
|
|
2549
|
+
return {
|
|
2550
|
+
propConfigJson: config.propConfig,
|
|
2551
|
+
groupRegistryJson: config.groupRegistry,
|
|
2552
|
+
scalesJson,
|
|
2553
|
+
variableMapJson,
|
|
2554
|
+
variableCss,
|
|
2555
|
+
contextualVarsJson,
|
|
2556
|
+
selectorAliasesJson: config.selectorAliases || null,
|
|
2557
|
+
conditionAliasesJson: config.conditionAliases || null,
|
|
2558
|
+
transformSourcesJson: config.transformSources || null,
|
|
2559
|
+
globalStyleBlocksJson: config.globalStyleBlocks || null,
|
|
2560
|
+
keyframesJson: config.keyframesBlocks || null,
|
|
2561
|
+
vocabularyWitnessesJson: config.vocabularyWitnesses || null,
|
|
2562
|
+
dependencies: config.dependencies ?? [],
|
|
2563
|
+
sourceThemeManifestsJson: config.sourceThemeManifests || null
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
//#endregion
|
|
2567
|
+
//#region pipeline/structural-self-check.ts
|
|
2568
|
+
function runStructuralSelfCheck(input) {
|
|
2569
|
+
const failures = [];
|
|
2570
|
+
if (input.componentCount === 0) failures.push("No component CSS produced — check the system file and its includes list");
|
|
2571
|
+
else if (input.assembledCss !== void 0 ? !/@layer\s+anm-base\s*\{/.test(input.assembledCss) : input.componentCss.trim().length === 0) failures.push(`${input.componentCount} component(s) discovered but the emitted component CSS is empty`);
|
|
2572
|
+
for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "empty") failures.push(`include '${specifier}' resolved but discovered no component sources`);
|
|
2573
|
+
else if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
|
|
2574
|
+
if (!input.variableCss.includes(":root")) failures.push("No :root variable block found in variable CSS");
|
|
2575
|
+
if ((input.assembledCss ?? `${input.variableCss}\n${input.globalCss}\n${input.componentCss}`).includes("__TRANSFORM__")) failures.push("Unresolved __TRANSFORM__ placeholders found in CSS output");
|
|
2576
|
+
if (input.componentCount > 0 && (input.componentCss.length > 0 || input.assembledCss !== void 0)) {
|
|
2577
|
+
const assembled = input.assembledCss ?? assembleStylesheet({
|
|
2578
|
+
layers: input.layers,
|
|
2579
|
+
variableCss: input.variableCss,
|
|
2580
|
+
globalCss: input.globalCss,
|
|
2581
|
+
componentCss: input.componentCss
|
|
2582
|
+
});
|
|
2583
|
+
const baseIdx = assembled.search(/@layer\s+anm-base\s*\{/);
|
|
2584
|
+
const variantsIdx = assembled.search(/@layer\s+anm-variants\s*\{/);
|
|
2585
|
+
if (baseIdx !== -1 && variantsIdx !== -1 && baseIdx >= variantsIdx) failures.push(`CSS layer ordering violated — @layer anm-base (offset ${baseIdx}) must precede @layer anm-variants (offset ${variantsIdx})`);
|
|
2586
|
+
}
|
|
2587
|
+
return failures;
|
|
2588
|
+
}
|
|
2589
|
+
//#endregion
|
|
2590
|
+
//#region pipeline/dynamic-prop-config.ts
|
|
2591
|
+
function buildDynamicPropConfig(dynamicProps) {
|
|
2592
|
+
const configEntries = {};
|
|
2593
|
+
for (const [propName, meta] of Object.entries(dynamicProps)) {
|
|
2594
|
+
if (!meta.varName || !meta.slotClass) throw new Error(`buildDynamicPropConfig: dynamic prop '${propName}' carries no slot metadata — expected varName and slotClass, got keys [${Object.keys(meta).join(", ")}].`);
|
|
2595
|
+
const entry = {
|
|
2596
|
+
varName: meta.varName,
|
|
2597
|
+
slotClass: meta.slotClass
|
|
2598
|
+
};
|
|
2599
|
+
if (meta.property) entry.property = meta.property;
|
|
2600
|
+
if (meta.properties && meta.properties.length > 0) entry.properties = meta.properties;
|
|
2601
|
+
if (meta.transformName) entry.transformName = meta.transformName;
|
|
2602
|
+
if (meta.scaleValues && Object.keys(meta.scaleValues).length > 0) entry.scaleValues = meta.scaleValues;
|
|
2603
|
+
configEntries[propName] = entry;
|
|
2604
|
+
}
|
|
2605
|
+
return configEntries;
|
|
2606
|
+
}
|
|
2607
|
+
//#endregion
|
|
2608
|
+
//#region pipeline/system-props-module.ts
|
|
2609
|
+
function buildSystemPropsModule(opts) {
|
|
2610
|
+
const dynamicPropConfig = buildDynamicPropConfig(opts.dynamicProps);
|
|
2611
|
+
return `export const systemPropMap = ${opts.systemPropMapJson};\nexport const systemPropGroups = ${opts.groupRegistryJson};\nexport const dynamicPropConfig = ${JSON.stringify(dynamicPropConfig)};\nexport const transforms = ${opts.transformsSource ?? "{}"};\n`;
|
|
2612
|
+
}
|
|
2613
|
+
//#endregion
|
|
2614
|
+
//#region pipeline/timing-waterfall.ts
|
|
2615
|
+
const RUST_TIMING_PHASES = [
|
|
2616
|
+
["parseAndWalk", "parse+walk"],
|
|
2617
|
+
["importResolution", "imports"],
|
|
2618
|
+
["extensionProvenance", "provenance"],
|
|
2619
|
+
["topologicalSort", "topo-sort"],
|
|
2620
|
+
["chainEvaluation", "chains"],
|
|
2621
|
+
["jsxScanning", "jsx-scan"],
|
|
2622
|
+
["systemPropAggregation", "sys-props"],
|
|
2623
|
+
["usageLedger", "usage"],
|
|
2624
|
+
["reconciliation", "reconcile"],
|
|
2625
|
+
["cssGeneration", "css-gen"],
|
|
2626
|
+
["manifestSerialization", "serialize"]
|
|
2627
|
+
];
|
|
2628
|
+
function formatRustTimingWaterfall(timing, opts) {
|
|
2629
|
+
const lines = [];
|
|
2630
|
+
for (const [key, label] of RUST_TIMING_PHASES) {
|
|
2631
|
+
const ms = timing[key] ?? 0;
|
|
2632
|
+
const pad = " ".repeat(Math.max(0, opts.labelWidth - label.length));
|
|
2633
|
+
const extra = key === "parseAndWalk" ? ` (${timing.fileCount ?? 0} files, ${timing.cacheHits ?? 0} cached)` : "";
|
|
2634
|
+
lines.push(`${opts.indent}${label}${pad}${String(ms).padStart(5)}ms${extra}`);
|
|
2635
|
+
}
|
|
2636
|
+
return lines;
|
|
2637
|
+
}
|
|
2638
|
+
//#endregion
|
|
2639
|
+
//#region pipeline/tsconfig-paths.ts
|
|
2640
|
+
/**
|
|
2641
|
+
* tsconfig `paths` → alias pairs, for drivers with no live bundler config.
|
|
2642
|
+
* The nearest config declaring `paths` wins wholesale — TS never merges.
|
|
2643
|
+
*/
|
|
2644
|
+
function stripJsonc(text) {
|
|
2645
|
+
let out = "";
|
|
2646
|
+
let inString = false;
|
|
2647
|
+
let inLine = false;
|
|
2648
|
+
let inBlock = false;
|
|
2649
|
+
for (let i = 0; i < text.length; i++) {
|
|
2650
|
+
const ch = text[i];
|
|
2651
|
+
const next = text[i + 1];
|
|
2652
|
+
if (inLine) {
|
|
2653
|
+
if (ch === "\n") {
|
|
2654
|
+
inLine = false;
|
|
2655
|
+
out += ch;
|
|
2656
|
+
}
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
if (inBlock) {
|
|
2660
|
+
if (ch === "*" && next === "/") {
|
|
2661
|
+
inBlock = false;
|
|
2662
|
+
i++;
|
|
2663
|
+
}
|
|
2664
|
+
continue;
|
|
2665
|
+
}
|
|
2666
|
+
if (inString) {
|
|
2667
|
+
out += ch;
|
|
2668
|
+
if (ch === "\\") {
|
|
2669
|
+
out += next ?? "";
|
|
2670
|
+
i++;
|
|
2671
|
+
} else if (ch === "\"") inString = false;
|
|
2672
|
+
continue;
|
|
2673
|
+
}
|
|
2674
|
+
if (ch === "\"") {
|
|
2675
|
+
inString = true;
|
|
2676
|
+
out += ch;
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
if (ch === "/" && next === "/") {
|
|
2680
|
+
inLine = true;
|
|
2681
|
+
i++;
|
|
2682
|
+
continue;
|
|
2683
|
+
}
|
|
2684
|
+
if (ch === "/" && next === "*") {
|
|
2685
|
+
inBlock = true;
|
|
2686
|
+
i++;
|
|
2687
|
+
continue;
|
|
2688
|
+
}
|
|
2689
|
+
out += ch;
|
|
2690
|
+
}
|
|
2691
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
2692
|
+
}
|
|
2693
|
+
/** `Object(value) === value` holds for exactly the blocks and lists
|
|
2694
|
+
* `JSON.parse` produces; the `[object Object]` tag separates the two. */
|
|
2695
|
+
function isJsonBlock(value) {
|
|
2696
|
+
return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
|
|
2697
|
+
}
|
|
2698
|
+
/** A block or a list. A declared `paths` OWNS the setting and blocks its
|
|
2699
|
+
* parents' even when it contributes nothing usable. */
|
|
2700
|
+
function isJsonKeyed(value) {
|
|
2701
|
+
return Object(value) === value;
|
|
2702
|
+
}
|
|
2703
|
+
/** A JSON string, excluding the boxed `String` object, which no path join
|
|
2704
|
+
* accepts. */
|
|
2705
|
+
function isJsonString(value) {
|
|
2706
|
+
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
2707
|
+
}
|
|
2708
|
+
function decodePaths(value) {
|
|
2709
|
+
if (!isJsonKeyed(value)) return null;
|
|
2710
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
2711
|
+
for (const [pattern, targets] of Object.entries(value)) {
|
|
2712
|
+
const [first] = Array.isArray(targets) ? targets : [];
|
|
2713
|
+
if (isJsonString(first)) decoded.set(pattern, first);
|
|
2714
|
+
}
|
|
2715
|
+
return decoded;
|
|
2716
|
+
}
|
|
2717
|
+
/** TypeScript accepts one specifier or an array; a non-string member names
|
|
2718
|
+
* no config and is dropped here. */
|
|
2719
|
+
function decodeExtends(value) {
|
|
2720
|
+
return (Array.isArray(value) ? value : [value]).filter(isJsonString);
|
|
2721
|
+
}
|
|
2722
|
+
function readConfig(path) {
|
|
2723
|
+
let raw;
|
|
2724
|
+
try {
|
|
2725
|
+
raw = readFileSync(path, "utf-8");
|
|
2726
|
+
} catch {
|
|
2727
|
+
return null;
|
|
2728
|
+
}
|
|
2729
|
+
let parsed;
|
|
2730
|
+
try {
|
|
2731
|
+
parsed = JSON.parse(stripJsonc(raw));
|
|
2732
|
+
} catch {
|
|
2733
|
+
return null;
|
|
2734
|
+
}
|
|
2735
|
+
const root = isJsonBlock(parsed) ? parsed : {};
|
|
2736
|
+
const compilerOptions = isJsonBlock(root.compilerOptions) ? root.compilerOptions : {};
|
|
2737
|
+
return {
|
|
2738
|
+
dir: dirname$1(path),
|
|
2739
|
+
baseUrl: isJsonString(compilerOptions.baseUrl) ? compilerOptions.baseUrl : null,
|
|
2740
|
+
paths: decodePaths(compilerOptions.paths),
|
|
2741
|
+
extends: decodeExtends(root.extends)
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
function resolveExtendsTarget(specifier, fromDir) {
|
|
2745
|
+
if (specifier.startsWith(".") || isAbsolute(specifier)) {
|
|
2746
|
+
const base = resolve(fromDir, specifier);
|
|
2747
|
+
for (const candidate of [
|
|
2748
|
+
base,
|
|
2749
|
+
`${base}.json`,
|
|
2750
|
+
join$1(base, "tsconfig.json")
|
|
2751
|
+
]) if (existsSync(candidate)) return candidate;
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
try {
|
|
2755
|
+
return __require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
|
|
2756
|
+
} catch {
|
|
2757
|
+
try {
|
|
2758
|
+
return __require.resolve(specifier, { paths: [fromDir] });
|
|
2759
|
+
} catch {
|
|
2760
|
+
return null;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
/** Child-first flattened extends chain starting at `entryPath`. */
|
|
2765
|
+
function loadChain(entryPath) {
|
|
2766
|
+
const chain = [];
|
|
2767
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2768
|
+
const queue = [entryPath];
|
|
2769
|
+
while (queue.length > 0) {
|
|
2770
|
+
const path = queue.shift();
|
|
2771
|
+
const key = resolve(path);
|
|
2772
|
+
if (visited.has(key)) continue;
|
|
2773
|
+
visited.add(key);
|
|
2774
|
+
const node = readConfig(key);
|
|
2775
|
+
if (!node) continue;
|
|
2776
|
+
chain.push(node);
|
|
2777
|
+
for (const parent of node.extends) {
|
|
2778
|
+
const resolved = resolveExtendsTarget(parent, node.dir);
|
|
2779
|
+
if (resolved) queue.push(resolved);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
return chain;
|
|
2783
|
+
}
|
|
2784
|
+
/**
|
|
2785
|
+
* The project's tsconfig path aliases as `buildPathAliasesJson` pairs; empty
|
|
2786
|
+
* when no readable tsconfig, or no usable `paths`, exists.
|
|
2787
|
+
*/
|
|
2788
|
+
function readTsconfigAliasPairs(rootDir) {
|
|
2789
|
+
const chain = loadChain(join$1(rootDir, "tsconfig.json"));
|
|
2790
|
+
if (chain.length === 0) return [];
|
|
2791
|
+
const pathsOwner = chain.find((node) => node.paths !== null);
|
|
2792
|
+
const paths = pathsOwner?.paths ?? null;
|
|
2793
|
+
if (pathsOwner === void 0 || paths === null) return [];
|
|
2794
|
+
const baseOwner = chain.find((node) => node.baseUrl !== null);
|
|
2795
|
+
const baseUrl = baseOwner?.baseUrl ?? null;
|
|
2796
|
+
const base = baseOwner !== void 0 && baseUrl !== null ? resolve(baseOwner.dir, baseUrl) : pathsOwner.dir;
|
|
2797
|
+
const pairs = [];
|
|
2798
|
+
for (const [pattern, target] of paths) {
|
|
2799
|
+
const patternStars = pattern.split("*").length - 1;
|
|
2800
|
+
const targetStars = target.split("*").length - 1;
|
|
2801
|
+
if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
|
|
2802
|
+
if (pattern.endsWith("/*")) {
|
|
2803
|
+
if (!target.endsWith("/*")) continue;
|
|
2804
|
+
pairs.push({
|
|
2805
|
+
pattern: pattern.slice(0, -2),
|
|
2806
|
+
target: resolve(base, target.slice(0, -2)),
|
|
2807
|
+
kind: "prefix"
|
|
2808
|
+
});
|
|
2809
|
+
continue;
|
|
2810
|
+
}
|
|
2811
|
+
if (patternStars > 0 || targetStars > 0) continue;
|
|
2812
|
+
pairs.push({
|
|
2813
|
+
pattern,
|
|
2814
|
+
target: resolve(base, target),
|
|
2815
|
+
kind: "exact"
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
return pairs;
|
|
2819
|
+
}
|
|
2820
|
+
//#endregion
|
|
2821
|
+
//#region pipeline/error-diagnostics.ts
|
|
2822
|
+
function assertNoErrorDiagnostics(diagnostics) {
|
|
2823
|
+
const errors = (diagnostics ?? []).filter((diagnostic) => diagnostic.kind === "error");
|
|
2824
|
+
if (errors.length === 0) return;
|
|
2825
|
+
const lines = [...new Set(errors.map((diagnostic) => `[animus] ${diagnostic.component || "<unknown component>"} (${diagnostic.file || "<unknown file>"}): ${diagnostic.message || "<no message>"}`))];
|
|
2826
|
+
throw new Error(lines.join("\n"));
|
|
2827
|
+
}
|
|
2828
|
+
//#endregion
|
|
2829
|
+
//#region pipeline/replacement-plans.ts
|
|
2830
|
+
const snapshotByManifest = /* @__PURE__ */ new WeakMap();
|
|
2831
|
+
/** `null` (a context before its first analysis) snapshots to the empty plan
|
|
2832
|
+
* set — the same state as an empty universe. */
|
|
2833
|
+
function snapshotFilePlans(manifest) {
|
|
2834
|
+
if (manifest === null) return /* @__PURE__ */ new Map();
|
|
2835
|
+
const cached = snapshotByManifest.get(manifest);
|
|
2836
|
+
if (cached) return cached;
|
|
2837
|
+
const entriesByFile = /* @__PURE__ */ new Map();
|
|
2838
|
+
for (const [id, desc] of Object.entries(manifest.components)) {
|
|
2839
|
+
const list = entriesByFile.get(desc.file) ?? [];
|
|
2840
|
+
list.push([id, desc.replacement]);
|
|
2841
|
+
entriesByFile.set(desc.file, list);
|
|
2842
|
+
}
|
|
2843
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
2844
|
+
for (const [file, entries] of entriesByFile) {
|
|
2845
|
+
entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
2846
|
+
snapshot.set(file, JSON.stringify(entries));
|
|
2847
|
+
}
|
|
2848
|
+
snapshotByManifest.set(manifest, snapshot);
|
|
2849
|
+
return snapshot;
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Paths whose plan changed: absent↔present, membership, or content.
|
|
2853
|
+
* `exclude` drops one path whose own modules are already being updated.
|
|
2854
|
+
*/
|
|
2855
|
+
function diffFilePlans(prev, next, opts) {
|
|
2856
|
+
const changed = /* @__PURE__ */ new Set();
|
|
2857
|
+
for (const [file, plan] of next) if (prev.get(file) !== plan) changed.add(file);
|
|
2858
|
+
for (const file of prev.keys()) if (!next.has(file)) changed.add(file);
|
|
2859
|
+
if (opts?.exclude) changed.delete(opts.exclude);
|
|
2860
|
+
return [...changed].sort();
|
|
2861
|
+
}
|
|
2862
|
+
/** Domain prefix keeping this hash disjoint from every other `contentHash`
|
|
2863
|
+
* use in the pipeline. */
|
|
2864
|
+
const REPLACEMENT_PLANS_DOMAIN = "animus-replacement-plans-v1\0";
|
|
2865
|
+
/** Leads with NUL: entries end with `\0` and contain none, so `\0\0` is
|
|
2866
|
+
* unreachable from entries alone and the section cannot be forged. */
|
|
2867
|
+
const SERVED_DEPENDENCY_DOMAIN = "\0animus-served-dependency-v1\0";
|
|
2868
|
+
/**
|
|
2869
|
+
* THE replacement epoch: integrations consume this hash, never their own.
|
|
2870
|
+
* `servedDependencyWitness` folds in content the plans cannot see.
|
|
2871
|
+
*/
|
|
2872
|
+
function hashReplacementPlans(snapshot, servedDependencyWitness) {
|
|
2873
|
+
const entries = [...snapshot.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
2874
|
+
let serialized = REPLACEMENT_PLANS_DOMAIN;
|
|
2875
|
+
for (const [file, plan] of entries) serialized += file + "\0" + plan + "\0";
|
|
2876
|
+
if (servedDependencyWitness !== void 0) serialized += SERVED_DEPENDENCY_DOMAIN + servedDependencyWitness;
|
|
2877
|
+
return contentHash(serialized);
|
|
2878
|
+
}
|
|
2879
|
+
//#endregion
|
|
2880
|
+
//#region pipeline/watch-keys.ts
|
|
2881
|
+
function toWatchKeys(input) {
|
|
2882
|
+
const lexical = normalize(input);
|
|
2883
|
+
const canonical = realpathThroughNearestExistingAncestor(lexical);
|
|
2884
|
+
return canonical === lexical ? [lexical] : [lexical, canonical];
|
|
2885
|
+
}
|
|
2886
|
+
/** Canonicalizes a path that may not exist, through its nearest existing
|
|
2887
|
+
* ancestor. A symlink retargeted after load fails open: no match. */
|
|
2888
|
+
function realpathThroughNearestExistingAncestor(path) {
|
|
2889
|
+
try {
|
|
2890
|
+
return realpathSync$1(path);
|
|
2891
|
+
} catch {}
|
|
2892
|
+
const suffix = [];
|
|
2893
|
+
let current = path;
|
|
2894
|
+
for (;;) {
|
|
2895
|
+
const parent = dirname(current);
|
|
2896
|
+
if (parent === current) return path;
|
|
2897
|
+
suffix.unshift(basename(current));
|
|
2898
|
+
current = parent;
|
|
2899
|
+
try {
|
|
2900
|
+
return join(realpathSync$1(current), ...suffix);
|
|
2901
|
+
} catch {}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
//#endregion
|
|
2905
|
+
export { DEFAULT_EXCLUDE as $, enforceExternalTokenContracts as A, firstOwners as B, surfaceManifestDiagnostics as C, validateLayerOrder as Ct, postProcessCss as D, vocabularyWitnessDiagnostics as E, createSourceCorpus as F, walkPackageSources as G, resolveAbsolutePathSpecifier as H, collectExternalPackageSources as I, sharesVolumeRoot as J, createSourceIdentity as K, excludeCollectedPackages as L, resolveThroughPathAliases as M, findAssetSpecifiers as N, resolveLightningTargets as O, substituteAssetPlaceholders as P, CORE_OPTION_KEYS as Q, extractSystemFilePackages as R, isUnresolvedParentDrop as S, stripLeadingLayerDeclaration as St, unresolvedParentName as T, staleDistIncludesMessage as U, isExcludedPackageRelativePath as V, unresolvableIncludesMessage as W, discoverFiles as X, compareDiscoveryOrder as Y, AnimusConfigError as Z, clearEngineCache as _, preprocessMdx as _t, assertNoErrorDiagnostics as a, resolveMode as at, VOCABULARY_COLLISION as b, ANIMUS_LAYERS as bt, buildSystemPropsModule as c, createV2EngineApi as ct, loadSystemConfig as d, parseFilesJson as dt, DRIVER_NAMESPACES as et, applyPrefix as f, projectExternalFileOwners as ft, buildAnalysisInputs as g, isEngineTransformExtension as gt, stableStringify as h, ENGINE_TRANSFORM_EXTENSIONS as ht, snapshotFilePlans as i, createExcludeMatcher as it, resolveAssetFile as j, buildPathAliasesJson as k, buildDynamicPropConfig as l, ingestSourceEntries as lt, camelToKebab as m, DEFAULT_EXTENSIONS as mt, diffFilePlans as n, STRUCTURAL_EXCLUDE as nt, readTsconfigAliasPairs as o, RETIRED_ENGINE_MESSAGE as ot, serializeStaticCss as p, adaptSvelteSource as pt, isPathWithinRoot as q, hashReplacementPlans as r, assertKnownOptionKeys as rt, formatRustTimingWaterfall as s, assertNoRetiredEngineSelection as st, toWatchKeys as t, REPLACEABLE_DEFAULT_EXCLUDE as tt, runStructuralSelfCheck as u, isAdvisorySourceDiagnostic as ut, runProjectAnalysis as v, contentHash as vt, unreadableSourceDiagnostic as w, __require as wt, VOCABULARY_LEGACY_VERB as x, assembleStylesheet as xt, applyUnitFallback as y, buildAnalyzeProjectArgs as yt, findPackageRoot as z };
|