@animus-ui/extract 0.1.22 → 0.1.24

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.
Files changed (46) hide show
  1. package/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
  2. package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
  3. package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
  4. package/crates/extract-v2/index.d.ts +16 -3
  5. package/dist/index.cjs +14 -2
  6. package/dist/index.mjs +2 -2
  7. package/dist/pipeline/analyze-project-args.d.ts +5 -1
  8. package/dist/pipeline/analyze-project-args.d.ts.map +1 -1
  9. package/dist/pipeline/asset-placeholders.d.ts +4 -1
  10. package/dist/pipeline/asset-placeholders.d.ts.map +1 -1
  11. package/dist/pipeline/core-options.d.ts +15 -3
  12. package/dist/pipeline/core-options.d.ts.map +1 -1
  13. package/dist/pipeline/correlate-external-tokens.d.ts +3 -0
  14. package/dist/pipeline/correlate-external-tokens.d.ts.map +1 -1
  15. package/dist/pipeline/discover-packages.d.ts +105 -7
  16. package/dist/pipeline/discover-packages.d.ts.map +1 -1
  17. package/dist/pipeline/dynamic-prop-config.d.ts +13 -2
  18. package/dist/pipeline/dynamic-prop-config.d.ts.map +1 -1
  19. package/dist/pipeline/engine-adapter.d.ts +5 -1
  20. package/dist/pipeline/engine-adapter.d.ts.map +1 -1
  21. package/dist/pipeline/index.d.ts +7 -4
  22. package/dist/pipeline/index.d.ts.map +1 -1
  23. package/dist/pipeline/kit-descriptor.d.ts +44 -0
  24. package/dist/pipeline/kit-descriptor.d.ts.map +1 -0
  25. package/dist/pipeline/kit-publication.d.ts +15 -0
  26. package/dist/pipeline/kit-publication.d.ts.map +1 -0
  27. package/dist/pipeline/manifest-diagnostics.d.ts +64 -5
  28. package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -1
  29. package/dist/pipeline/manifest-schema.d.ts +4 -0
  30. package/dist/pipeline/manifest-schema.d.ts.map +1 -1
  31. package/dist/pipeline/prefix.d.ts +14 -3
  32. package/dist/pipeline/prefix.d.ts.map +1 -1
  33. package/dist/pipeline/run-analysis.d.ts +16 -2
  34. package/dist/pipeline/run-analysis.d.ts.map +1 -1
  35. package/dist/pipeline/source-ingestion.d.ts +13 -2
  36. package/dist/pipeline/source-ingestion.d.ts.map +1 -1
  37. package/dist/pipeline/structural-self-check.d.ts.map +1 -1
  38. package/dist/pipeline/system-config.d.ts +6 -3
  39. package/dist/pipeline/system-config.d.ts.map +1 -1
  40. package/dist/{pipeline-BXY7Pf_D.mjs → pipeline-Bl20tbu_.mjs} +1484 -546
  41. package/dist/{pipeline-D7B1z0-1.cjs → pipeline-CAH9mxnK.cjs} +1602 -591
  42. package/dist/session/extraction-session.d.ts +17 -1
  43. package/dist/session/extraction-session.d.ts.map +1 -1
  44. package/dist/session.cjs +60 -17
  45. package/dist/session.mjs +60 -17
  46. package/package.json +4 -4
@@ -1,13 +1,14 @@
1
- import { createRequire } from "node:module";
2
- import path, { basename, dirname, join, normalize, posix } from "node:path";
1
+ import { createRequire, isBuiltin } from "node:module";
2
+ import path, { basename, dirname, extname, isAbsolute, join, normalize, posix, relative, resolve, sep } from "node:path";
3
3
  import { createHash } from "crypto";
4
4
  import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "fs";
5
- import { dirname as dirname$1, extname, isAbsolute, join as join$1, relative, resolve, sep } from "path";
6
- import { realpathSync as realpathSync$1 } from "node:fs";
5
+ import { dirname as dirname$1, extname as extname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
6
+ import { existsSync as existsSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, statSync as statSync$1 } from "node:fs";
7
7
  import { createRequire as createRequire$1 } from "module";
8
8
  import browserslist from "browserslist";
9
9
  import { browserslistToTargets, transform } from "lightningcss";
10
10
  import { componentValues, dependsOnContext, foldInitialValue, identifierAt, isSpace, isUnitlessProperty, someValue, substitutesValue, tokenize, variableReads } from "@animus-ui/properties";
11
+ import { spawnSync } from "node:child_process";
11
12
  //#region \0rolldown/runtime.js
12
13
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
13
14
  //#endregion
@@ -90,8 +91,13 @@ function buildAnalyzeProjectArgs(inputs) {
90
91
  inputs.transformSourcesJson,
91
92
  inputs.transformProvenanceJson ?? null
92
93
  ];
93
- if (inputs.declarationScalesJson || inputs.analysisContextJson) args.push(inputs.declarationScalesJson);
94
- if (inputs.analysisContextJson) args.push(inputs.analysisContextJson);
94
+ const tail = [
95
+ inputs.declarationScalesJson,
96
+ inputs.analysisContextJson,
97
+ inputs.generatedNamesJson
98
+ ];
99
+ const last = tail.findLastIndex((slot) => slot !== void 0 && slot !== "");
100
+ for (const slot of tail.slice(0, last + 1)) args.push(slot || void 0);
95
101
  return args;
96
102
  }
97
103
  //#endregion
@@ -111,7 +117,180 @@ function parseInternalWire(json, wire) {
111
117
  return parsed;
112
118
  }
113
119
  //#endregion
120
+ //#region pipeline/asset-placeholders.ts
121
+ const ASSET_PLACEHOLDER_PREFIX = "animus-asset:";
122
+ const URL_OPEN_RE = /url\(\s*(['"]?)/gi;
123
+ const IMAGE_SET_OPEN_RE = /(?:-webkit-)?image-set\(/gi;
124
+ const BARE_SPECIFIER_RE = /^[^'")\s]+/;
125
+ /** The index of the `)` that closes the `(` at `open`, outside strings. */
126
+ function closingParen(css, open) {
127
+ let depth = 0;
128
+ let quote = null;
129
+ for (let i = open; i < css.length; i += 1) {
130
+ const c = css[i];
131
+ if (quote !== null) {
132
+ if (c === quote) quote = null;
133
+ } else if (c === "\"" || c === "'") quote = c;
134
+ else if (c === "(") depth += 1;
135
+ else if (c === ")") {
136
+ depth -= 1;
137
+ if (depth === 0) return i;
138
+ }
139
+ }
140
+ return css.length;
141
+ }
142
+ function quotedReference(css, quoteAt) {
143
+ const start = quoteAt + 1;
144
+ if (!css.startsWith("animus-asset:", start)) return null;
145
+ const end = css.indexOf(css[quoteAt], start);
146
+ if (end === -1) return null;
147
+ return {
148
+ start,
149
+ end,
150
+ specifier: css.slice(start + 13, end)
151
+ };
152
+ }
153
+ function assetReferences(css) {
154
+ if (!css.includes("animus-asset:")) return [];
155
+ const references = [];
156
+ for (const open of css.matchAll(URL_OPEN_RE)) {
157
+ const at = (open.index ?? 0) + open[0].length;
158
+ if (open[1]) {
159
+ const reference = quotedReference(css, at - 1);
160
+ if (reference) references.push(reference);
161
+ continue;
162
+ }
163
+ if (!css.startsWith("animus-asset:", at)) continue;
164
+ const specifier = BARE_SPECIFIER_RE.exec(css.slice(at + 13))?.[0];
165
+ if (specifier === void 0) continue;
166
+ const end = at + 13 + specifier.length;
167
+ references.push({
168
+ start: at,
169
+ end,
170
+ specifier
171
+ });
172
+ }
173
+ for (const open of css.matchAll(IMAGE_SET_OPEN_RE)) {
174
+ const paren = (open.index ?? 0) + open[0].length - 1;
175
+ const close = closingParen(css, paren);
176
+ let depth = 0;
177
+ let candidateStart = true;
178
+ for (let i = paren + 1; i < close; i += 1) {
179
+ const c = css[i];
180
+ if (/\s/.test(c)) continue;
181
+ if (c === "\"" || c === "'") {
182
+ if (depth === 0 && candidateStart) {
183
+ const reference = quotedReference(css, i);
184
+ if (reference) references.push(reference);
185
+ }
186
+ const end = css.indexOf(c, i + 1);
187
+ i = end === -1 ? close : end;
188
+ } else if (c === "(") depth += 1;
189
+ else if (c === ")") depth -= 1;
190
+ else if (c === "," && depth === 0) {
191
+ candidateStart = true;
192
+ continue;
193
+ }
194
+ candidateStart = false;
195
+ }
196
+ }
197
+ return references.sort((a, b) => a.start - b.start);
198
+ }
199
+ function findAssetSpecifiers(css) {
200
+ return [...new Set(assetReferences(css).map((reference) => reference.specifier))];
201
+ }
202
+ function substituteAssetPlaceholders(css, urlBySpecifier) {
203
+ if (urlBySpecifier.size === 0) return css;
204
+ let out = css;
205
+ for (const reference of assetReferences(css).reverse()) {
206
+ const url = urlBySpecifier.get(reference.specifier);
207
+ if (url === void 0) continue;
208
+ out = out.slice(0, reference.start) + url + out.slice(reference.end);
209
+ }
210
+ return out;
211
+ }
212
+ /**
213
+ * Specifiers of placeholder text anywhere inside a `url()` or `image-set()`
214
+ * call, a reference or not: after substitution any of it is a broken load.
215
+ */
216
+ function placeholdersInAssetCalls(css) {
217
+ if (!css.includes("animus-asset:")) return [];
218
+ const calls = [...css.matchAll(URL_OPEN_RE), ...css.matchAll(IMAGE_SET_OPEN_RE)].map((open) => {
219
+ const paren = css.indexOf("(", open.index ?? 0);
220
+ return [paren, closingParen(css, paren)];
221
+ });
222
+ const found = /* @__PURE__ */ new Set();
223
+ for (let at = css.indexOf(ASSET_PLACEHOLDER_PREFIX); at !== -1; at = css.indexOf(ASSET_PLACEHOLDER_PREFIX, at + 1)) {
224
+ if (!calls.some(([open, close]) => open < at && at < close)) continue;
225
+ found.add(/^[^'")\s,]*/.exec(css.slice(at + 13))[0]);
226
+ }
227
+ return [...found];
228
+ }
229
+ function findSheetAssetSpecifiers(sheets) {
230
+ return [.../* @__PURE__ */ new Set([
231
+ ...findAssetSpecifiers(sheets.variableCss),
232
+ ...findAssetSpecifiers(sheets.globalCss),
233
+ ...findAssetSpecifiers(sheets.componentCss)
234
+ ])];
235
+ }
236
+ /** The one substitution step every emitted sheet passes through. */
237
+ function substituteSheetAssets(sheets, urlBySpecifier) {
238
+ return {
239
+ variableCss: substituteAssetPlaceholders(sheets.variableCss, urlBySpecifier),
240
+ globalCss: substituteAssetPlaceholders(sheets.globalCss, urlBySpecifier),
241
+ componentCss: substituteAssetPlaceholders(sheets.componentCss, urlBySpecifier)
242
+ };
243
+ }
244
+ const UNSUBSTITUTED_ASSET_CODE = "animus.asset.unsubstituted-placeholder";
245
+ /** A placeholder in emitted CSS is a broken URL in the browser: it fails a
246
+ * strict build, strict development reports it, and it warns otherwise. */
247
+ function reportSurvivingAssetPlaceholders(css, report) {
248
+ const specifiers = placeholdersInAssetCalls(css);
249
+ if (specifiers.length === 0) return;
250
+ const message = `${report.prefix} asset() placeholders reached ${report.surface ?? "emitted CSS"} unsubstituted: ${specifiers.join(", ")} (${UNSUBSTITUTED_ASSET_CODE})`;
251
+ if (!report.strict) {
252
+ report.warn(message);
253
+ return;
254
+ }
255
+ if (!report.reportErrors) throw new Error(message);
256
+ report.reportErrors(message);
257
+ }
258
+ /**
259
+ * The runtime code an analysis generates: each dynamic prop config, which the
260
+ * shared prop map module carries, and each component's replacement. Extraction
261
+ * lifts asset() out of them into root variables, so none should hold one.
262
+ * Both embed values as JSON strings, so their quote and backslash escapes are
263
+ * undone for scanning: the text is for `findAssetSpecifiers`, not to run.
264
+ */
265
+ function generatedModuleCode(manifest) {
266
+ return [JSON.stringify(manifest.dynamic_props), ...Object.values(manifest.components).map((component) => component.replacement)].join("\n").replace(/\\(["\\])/g, "$1");
267
+ }
268
+ //#endregion
114
269
  //#region pipeline/manifest-diagnostics.ts
270
+ /** `file`, then its line and column where the record has them. */
271
+ function locationOf(diagnostic) {
272
+ if (diagnostic.line === void 0) return diagnostic.file;
273
+ if (diagnostic.column === void 0) return `${diagnostic.file}:${diagnostic.line}`;
274
+ return `${diagnostic.file}:${diagnostic.line}:${diagnostic.column}`;
275
+ }
276
+ /** A located record's `file:line:column: `; nothing for one without a line,
277
+ * whose message names its file. */
278
+ function locatedPrefix(diagnostic) {
279
+ return diagnostic.line === void 0 ? "" : `${locationOf(diagnostic)}: `;
280
+ }
281
+ /** What the record dropped, when its message does not already quote it. */
282
+ function droppedSuffix(diagnostic) {
283
+ return diagnostic.dropped && !diagnostic.message.includes(diagnostic.dropped) ? ` — dropped: ${diagnostic.dropped}` : "";
284
+ }
285
+ /** The record's code, when its message does not already quote it. */
286
+ function codeSuffix(diagnostic) {
287
+ return diagnostic.code && !diagnostic.message.includes(diagnostic.code) ? ` [${diagnostic.code}]` : "";
288
+ }
289
+ /** A warn-kind record's line after its mark:
290
+ * `file:line:column: component: message [code]`. */
291
+ function warnLine(diagnostic) {
292
+ return `${locationOf(diagnostic)}: ${diagnostic.component}: ${diagnostic.message}${droppedSuffix(diagnostic)}${codeSuffix(diagnostic)}`;
293
+ }
115
294
  /** Stable code for selector forms with no substitutable subject; the
116
295
  * extraction engine mints the same string. */
117
296
  const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
@@ -124,6 +303,81 @@ function isUnresolvedParentDrop(diagnostic) {
124
303
  function unresolvedParentName(diagnostic) {
125
304
  return UNRESOLVED_PARENT_RE.exec(diagnostic.message)?.[1] ?? null;
126
305
  }
306
+ const DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set([
307
+ "off",
308
+ "info",
309
+ "warn",
310
+ "error"
311
+ ]);
312
+ /** A key that selects every record of a kind: `kind:bail`, `kind:skip`,
313
+ * `kind:warn` or `kind:error`. */
314
+ const KIND_KEY = "kind:";
315
+ const DIAGNOSTIC_KINDS = /* @__PURE__ */ new Set([
316
+ "bail",
317
+ "skip",
318
+ "warn",
319
+ "error"
320
+ ]);
321
+ /** The level `levels` sets for `code`: its exact entry, else the entry of
322
+ * the longest prefix it starts with. */
323
+ function codeLevel(code, levels) {
324
+ if (code === void 0) return void 0;
325
+ if (Object.hasOwn(levels, code)) return levels[code];
326
+ let longest;
327
+ for (const [key, level] of Object.entries(levels)) {
328
+ if (!key.endsWith(".*")) continue;
329
+ const prefix = key.slice(0, -1);
330
+ if (code.startsWith(prefix) && prefix.length > (longest?.length ?? -1)) longest = {
331
+ length: prefix.length,
332
+ level
333
+ };
334
+ }
335
+ return longest?.level;
336
+ }
337
+ /** The level `levels` sets for a record: its code's level, else its kind's. */
338
+ function levelFor({ code, kind }, levels) {
339
+ if (levels === void 0) return void 0;
340
+ const kindKey = `${KIND_KEY}${kind}`;
341
+ return codeLevel(code, levels) ?? (Object.hasOwn(levels, kindKey) ? levels[kindKey] : void 0);
342
+ }
343
+ /** The level a record prints at: the option's entry for its code, else
344
+ * `error` for a hard error (an `error`-kind record) or an error-severity
345
+ * record under `strict`, else its own. */
346
+ function effectiveLevel(diagnostic, policy) {
347
+ const level = levelFor(diagnostic, policy.levels);
348
+ if (level !== void 0) return level;
349
+ if (diagnostic.kind === "error") return "error";
350
+ if (diagnostic.severity === "info") return "info";
351
+ return policy.strict && diagnostic.severity === "error" ? "error" : "warn";
352
+ }
353
+ /** Every code Animus is known to report: the engine's table, as the native
354
+ * module publishes it (`diagnosticCodes()`), the pipeline's, and the asset
355
+ * code. The system package mints vocabulary codes at run time, so a valid
356
+ * code can be missing. */
357
+ function knownDiagnosticCodes(engineCodesJson) {
358
+ const engineCodes = engineCodesJson ? Object.keys(parseInternalWire(engineCodesJson, "diagnosticCodes (the engine code table)")) : [];
359
+ return /* @__PURE__ */ new Set([
360
+ ...engineCodes,
361
+ ...DIAGNOSTIC_SEVERITY.keys(),
362
+ UNSUBSTITUTED_ASSET_CODE
363
+ ]);
364
+ }
365
+ /** The keys of `levels` that name no known code, exactly or as a prefix. */
366
+ function unknownDiagnosticKeys(levels, known) {
367
+ return Object.keys(levels).filter((key) => {
368
+ if (key.startsWith(KIND_KEY)) return !DIAGNOSTIC_KINDS.has(key.slice(5));
369
+ if (!key.endsWith(".*")) return !known.has(key);
370
+ const prefix = key.slice(0, -1);
371
+ return ![...known].some((code) => code.startsWith(prefix));
372
+ });
373
+ }
374
+ /** The failure of diagnostics at `error` level, from `strict` or the
375
+ * `diagnostics` option: a host fails the build on it whatever its own
376
+ * strictness. It prints as the plain `Error` strict always threw. */
377
+ var DiagnosticFailure = class extends Error {};
378
+ /** Level options already checked against the known codes: one warning per
379
+ * unknown key, however often a host analyzes. */
380
+ const checkedLevels = /* @__PURE__ */ new WeakSet();
127
381
  /** True when the selector carries at least one substitutable `&` subject
128
382
  * outside quoted text. */
129
383
  function hasSelectorSubject(value) {
@@ -166,6 +420,22 @@ function collectSelectorAliasDiagnostics(selectorAliasesJson) {
166
420
  const UNREADABLE_SOURCE_FILE = "animus.ingestion.unreadable-source-file";
167
421
  /** A kit the system extends resolved, yet discovery found none of its files. */
168
422
  const NO_KIT_FILES = "animus.discovery.no-kit-files";
423
+ /** A kit with no `animus` export condition: discovery guesses its source
424
+ * from `src/`. */
425
+ const KIT_WITHOUT_SOURCE_CONDITION = "animus.discovery.kit-without-source-condition";
426
+ /** A kit's `animus` export condition names a file that is missing or outside
427
+ * its package, so that entry is not read from source. */
428
+ const INVALID_KIT_SOURCE_CONDITION = "animus.discovery.invalid-source-condition";
429
+ /** A kit the application imports whose system the application's system
430
+ * does not include: its components are not extracted. */
431
+ const KIT_SYSTEM_NOT_INCLUDED = "animus.kit.system-not-included";
432
+ /** A system file's `createSystem` binding that discovery could not follow to
433
+ * Animus's factory, so it is no root. */
434
+ const UNPROVEN_ROOT_BINDING = "animus.discovery.unproven-root-binding";
435
+ /** A system file calls `createSystem` with no import or local binding. The
436
+ * system loader evaluates the file without auto-imports, where the name is
437
+ * undefined, so the call is no root. */
438
+ const UNIMPORTED_CREATE_SYSTEM = "animus.discovery.unimported-create-system";
169
439
  /** What a skipped source file costs: the analysis never sees what it
170
440
  * renders. */
171
441
  const SKIPPED_SOURCE_COST = "skipped: its renders are not seen, so nothing is pruned and every component, variant option and state is kept until it is analyzed";
@@ -201,6 +471,11 @@ function noKitFilesDiagnostics(outcomes) {
201
471
  severity: severityFor(NO_KIT_FILES)
202
472
  }));
203
473
  }
474
+ /** A kit's descriptor declares a format this Animus does not read. */
475
+ const KIT_UNSUPPORTED_FORMAT = "animus.kit.unsupported-format";
476
+ /** A kit's descriptor records a definition its shipped source no longer
477
+ * declares. */
478
+ const KIT_STALE_DESCRIPTOR = "animus.kit.stale-descriptor";
204
479
  /** The collision entry code minted by the system package's merge. */
205
480
  const VOCABULARY_COLLISION = "animus.vocabulary.collision";
206
481
  /** A theme `@property` registration browsers would ignore; it is not emitted. */
@@ -226,9 +501,6 @@ const PROPERTY_FALLBACK_SELF_REFERENCE = "animus.property.fallback-self-referenc
226
501
  * another directly, so every property in the cycle is invalid at
227
502
  * computed-value time. */
228
503
  const PROPERTY_CURRENT_VAR_CYCLE = "animus.property.current-var-cycle";
229
- /** A prefix renamed contextual variables without `prefixContextualVars`, so
230
- * their declared names no longer resolve. */
231
- const PREFIX_CONTEXTUAL_VARS_UNPREFIXED = "animus.prefix.contextual-vars-unprefixed";
232
504
  /** Under `prefixContextualVars`, a final name that collides with a runtime
233
505
  * transport variable or a theme variable of the same spelling. */
234
506
  const PREFIX_NAME_CONFLICT = "animus.prefix.name-conflict";
@@ -242,6 +514,13 @@ const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
242
514
  [SELECTOR_UNSUPPORTED_SUBJECT, "error"],
243
515
  [UNREADABLE_SOURCE_FILE, "error"],
244
516
  [NO_KIT_FILES, "warn"],
517
+ [KIT_WITHOUT_SOURCE_CONDITION, "warn"],
518
+ [INVALID_KIT_SOURCE_CONDITION, "error"],
519
+ [KIT_SYSTEM_NOT_INCLUDED, "error"],
520
+ [KIT_UNSUPPORTED_FORMAT, "error"],
521
+ [KIT_STALE_DESCRIPTOR, "warn"],
522
+ [UNPROVEN_ROOT_BINDING, "warn"],
523
+ [UNIMPORTED_CREATE_SYSTEM, "warn"],
245
524
  [VOCABULARY_COLLISION, "warn"],
246
525
  [VOCABULARY_LEGACY_VERB, "warn"],
247
526
  [INVALID_PROPERTY_REGISTRATION, "error"],
@@ -251,7 +530,6 @@ const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
251
530
  [PROPERTY_SELF_REFERENCE, "warn"],
252
531
  [PROPERTY_FALLBACK_SELF_REFERENCE, "warn"],
253
532
  [PROPERTY_CURRENT_VAR_CYCLE, "warn"],
254
- [PREFIX_CONTEXTUAL_VARS_UNPREFIXED, "warn"],
255
533
  [PREFIX_NAME_CONFLICT, "error"],
256
534
  [PROPERTY_LEGACY_TOKEN_COLLISION, "warn"],
257
535
  [PROPERTY_TOKEN_COLLISION, "error"]
@@ -306,15 +584,6 @@ function systemLoadDiagnostics(system) {
306
584
  code: INVALID_PROPERTY_REGISTRATION,
307
585
  severity: severityFor(INVALID_PROPERTY_REGISTRATION)
308
586
  }));
309
- const prefixed = system.legacyPrefixedContextualVars ?? [];
310
- const unprefixed = prefixed.length === 0 ? [] : [{
311
- file: "system",
312
- component: "prefix",
313
- kind: "warn",
314
- message: `the prefix renames the contextual variables ${prefixed.join(", ")}, but their declared names are still read and written as written, so scale reads of them fail and their writes miss. Set prefixContextualVars: true to resolve them under the prefix (${PREFIX_CONTEXTUAL_VARS_UNPREFIXED})`,
315
- code: PREFIX_CONTEXTUAL_VARS_UNPREFIXED,
316
- severity: severityFor(PREFIX_CONTEXTUAL_VARS_UNPREFIXED)
317
- }];
318
587
  const conflicts = (system.prefixNameConflicts ?? []).map(({ name, final, reason }) => ({
319
588
  file: "system",
320
589
  component: `--${name}`,
@@ -326,7 +595,6 @@ function systemLoadDiagnostics(system) {
326
595
  return [
327
596
  ...vocabularyWitnessDiagnostics(system.vocabularyWitnessesJson),
328
597
  ...registrations,
329
- ...unprefixed,
330
598
  ...conflicts,
331
599
  ...tokenCollisionDiagnostics(system)
332
600
  ];
@@ -362,29 +630,44 @@ function tokenCollisionDiagnostics(system) {
362
630
  }
363
631
  /**
364
632
  * The single strict-escalation policy point for both extraction plugins:
365
- * error-severity diagnostics throw together under `strict`, else warn.
633
+ * error-severity diagnostics throw together under `strict`, else warn. A
634
+ * development session reports them through `reportErrors` instead.
366
635
  */
367
636
  function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
368
- const errors = [];
637
+ const errors = /* @__PURE__ */ new Set();
638
+ if (policy.levels && policy.knownCodes && !checkedLevels.has(policy.levels)) {
639
+ checkedLevels.add(policy.levels);
640
+ for (const key of unknownDiagnosticKeys(policy.levels, policy.knownCodes)) warn(key.startsWith(KIND_KEY) ? `⚠ diagnostics option: '${key}' names no diagnostic kind — use kind:bail, kind:skip, kind:warn or kind:error` : `⚠ diagnostics option: '${key}' matches no Animus diagnostic code — check its spelling (a code the system package mints at run time is known only once it is reported)`);
641
+ }
369
642
  const diagnostics = policy.prepend?.length ? [...policy.prepend, ...manifest.diagnostics ?? []] : manifest.diagnostics ?? [];
370
643
  for (const diagnostic of diagnostics) {
644
+ const level = effectiveLevel(diagnostic, policy);
645
+ if (level === "off") continue;
371
646
  let line = null;
372
- if (diagnostic.kind === "bail") line = `⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`;
373
- else if (diagnostic.kind === "skip") line = `⚠ ${diagnostic.component}: skipped ${diagnostic.message}`;
374
- else if (diagnostic.kind === "warn") line = `${diagnostic.severity === "info" ? "ℹ" : "⚠"} ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
647
+ const mark = level === "info" ? "ℹ" : "⚠";
648
+ const message = `${diagnostic.message}${droppedSuffix(diagnostic)}`;
649
+ if (diagnostic.kind === "bail") line = `${mark} ${locatedPrefix(diagnostic)}${diagnostic.component} not extracted: ${message}${codeSuffix(diagnostic)}`;
650
+ else if (diagnostic.kind === "skip") line = `${mark} ${locatedPrefix(diagnostic)}${diagnostic.component}: skipped ${message}${codeSuffix(diagnostic)}`;
651
+ else if (diagnostic.kind === "warn" || diagnostic.kind === "error") line = `${mark} ${warnLine(diagnostic)}`;
375
652
  if (line === null) continue;
376
- if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
377
- if (diagnostic.severity === "info") {
653
+ if (level === "info") {
378
654
  policy.info?.(line);
379
655
  continue;
380
656
  }
381
- if (policy.strict && diagnostic.severity === "error") {
382
- errors.push(`${diagnostic.code ?? "error"} — ${diagnostic.component}: ${diagnostic.message}`);
657
+ if (level === "error") {
658
+ const subject = diagnostic.kind === "warn" || diagnostic.kind === "error" ? `${locationOf(diagnostic)}: ` : locatedPrefix(diagnostic);
659
+ errors.add(`${diagnostic.code ?? "error"} — ${subject}${diagnostic.component}: ${message}`);
383
660
  continue;
384
661
  }
385
662
  warn(line);
386
663
  }
387
- if (errors.length > 0) throw new Error(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
664
+ if (errors.size === 0) return;
665
+ const message = `[animus] strict: ${errors.size} error diagnostic(s):\n${[...errors].join("\n")}`;
666
+ if (policy.reportErrors) {
667
+ policy.reportErrors(message);
668
+ return;
669
+ }
670
+ throw new DiagnosticFailure(message);
388
671
  }
389
672
  //#endregion
390
673
  //#region pipeline/mdx-preprocessor.ts
@@ -1036,17 +1319,17 @@ const NODE_NEXT_EXTENSION_MAP = {
1036
1319
  ".mjs": [".mts"],
1037
1320
  ".cjs": [".cts"]
1038
1321
  };
1039
- function resolveRelativeSource(importerPath, specifier, files) {
1040
- if (!specifier.startsWith(".")) return null;
1322
+ /** The paths a relative specifier can name from `importerPath`, in probe
1323
+ * order: as written with each source suffix, then a NodeNext emitted
1324
+ * extension's sources. Empty for a non-relative specifier. */
1325
+ function relativeSourceCandidates(importerPath, specifier) {
1326
+ if (!specifier.startsWith(".")) return [];
1041
1327
  const base = posix.normalize(posix.join(posix.dirname(canonicalResolverPath(importerPath)), specifier));
1042
- for (const suffix of RELATIVE_PROBE_SUFFIXES) {
1043
- const candidate = `${base}${suffix}`;
1044
- const actualPath = files.get(candidate);
1045
- if (actualPath !== void 0) return actualPath;
1046
- }
1047
1328
  const explicitExtension = extension(base);
1048
- for (const sourceExtension of NODE_NEXT_EXTENSION_MAP[explicitExtension] ?? []) {
1049
- const candidate = `${base.slice(0, -explicitExtension.length)}${sourceExtension}`;
1329
+ return [...RELATIVE_PROBE_SUFFIXES.map((suffix) => `${base}${suffix}`), ...(NODE_NEXT_EXTENSION_MAP[explicitExtension] ?? []).map((sourceExtension) => `${base.slice(0, -explicitExtension.length)}${sourceExtension}`)];
1330
+ }
1331
+ function resolveRelativeSource(importerPath, specifier, files) {
1332
+ for (const candidate of relativeSourceCandidates(importerPath, specifier)) {
1050
1333
  const actualPath = files.get(candidate);
1051
1334
  if (actualPath !== void 0) return actualPath;
1052
1335
  }
@@ -1351,7 +1634,8 @@ function createSourceIngestor(host) {
1351
1634
  const fatal = diagnostics.filter((diagnostic) => !isAdvisorySourceDiagnostic(diagnostic));
1352
1635
  const invalidOriginals = new Set(fatal.map((diagnostic) => diagnostic.originalPath));
1353
1636
  if (diagnostics.length === 0) return invalidOriginals;
1354
- if (host.strict() && fatal.length > 0) {
1637
+ const reportError = host.strict() ? host.reportErrors?.() : void 0;
1638
+ if (host.strict() && fatal.length > 0 && !reportError) {
1355
1639
  const lines = fatal.map((diagnostic) => `${diagnostic.code} ${diagnostic.originalPath}: ${diagnostic.message} (${SKIPPED_SOURCE_COST})`);
1356
1640
  throw new Error(`${host.prefix} ${lines.join(`\n${host.prefix} `)}`);
1357
1641
  }
@@ -1365,7 +1649,8 @@ function createSourceIngestor(host) {
1365
1649
  }
1366
1650
  if (warned.has(line)) continue;
1367
1651
  warned.add(line);
1368
- host.warn(`${host.prefix} ${line}`);
1652
+ if (reportError && skipped) reportError(`${host.prefix} ${line}`);
1653
+ else host.warn(`${host.prefix} ${line}`);
1369
1654
  }
1370
1655
  return invalidOriginals;
1371
1656
  },
@@ -1392,7 +1677,9 @@ function createV2EngineApi(deps) {
1392
1677
  return {
1393
1678
  loadSystemModule: (...args) => native.loadSystemModule(...args),
1394
1679
  extractFacts: (filesJson) => native.extractFacts(filesJson),
1395
- analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson, transformSourcesJson, transformProvenanceJson, declarationScalesJson, analysisContextJson) => {
1680
+ moduleSpecifiers: (filesJson) => native.moduleSpecifiers(filesJson),
1681
+ diagnosticCodes: () => native.diagnosticCodes(),
1682
+ analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson, transformSourcesJson, transformProvenanceJson, declarationScalesJson, analysisContextJson, generatedNamesJson) => {
1396
1683
  const filesJson = deps.rehydrateFilesJson ? deps.rehydrateFilesJson(filesJsonRaw) : filesJsonRaw;
1397
1684
  const sent = /* @__PURE__ */ new Map();
1398
1685
  for (const entry of parseFilesJson(filesJson, label)) sent.set(entry.path, entry.source);
@@ -1420,6 +1707,7 @@ function createV2EngineApi(deps) {
1420
1707
  transformProvenanceJson: transformProvenanceJson ?? void 0,
1421
1708
  declarationScalesJson,
1422
1709
  analysisContextJson,
1710
+ generatedNamesJson,
1423
1711
  devMode
1424
1712
  };
1425
1713
  const engine = new native.ExtractEngine(config);
@@ -1457,6 +1745,10 @@ function assertNoRetiredEngineSelection(engineOption) {
1457
1745
  }
1458
1746
  //#endregion
1459
1747
  //#region pipeline/core-options.ts
1748
+ /**
1749
+ * One option schema shared by the Vite, Next, and CLI drivers: key semantics,
1750
+ * default exclusions, and mode authority cannot drift per driver.
1751
+ */
1460
1752
  const DRIVER_NAMESPACES = [
1461
1753
  "vite",
1462
1754
  "next",
@@ -1490,6 +1782,7 @@ const CORE_OPTION_KEYS = /* @__PURE__ */ new Set([
1490
1782
  "exclude",
1491
1783
  "extensions",
1492
1784
  "strict",
1785
+ "diagnostics",
1493
1786
  "verbose",
1494
1787
  "prefix",
1495
1788
  "prefixContextualVars",
@@ -1549,6 +1842,7 @@ const isString = (value) => Object(value) !== value && Object.prototype.toString
1549
1842
  const isBoolean = (value) => Object(value) !== value && Object.prototype.toString.call(value) === "[object Boolean]";
1550
1843
  const isVerbose = (value) => isBoolean(value) || value === "trace";
1551
1844
  const isStringArray = (value) => Array.isArray(value) && value.every(isString);
1845
+ const isDiagnosticLevels = (value) => Object.prototype.toString.call(value) === "[object Object]" && Object.values(Object(value)).every((level) => isString(level) && DIAGNOSTIC_LEVELS.has(level));
1552
1846
  const isStringOrStringArray = (value) => isString(value) || isStringArray(value);
1553
1847
  const CORE_VALUE_GATES = [
1554
1848
  {
@@ -1571,6 +1865,11 @@ const CORE_VALUE_GATES = [
1571
1865
  ok: isBoolean,
1572
1866
  expected: "a boolean"
1573
1867
  },
1868
+ {
1869
+ key: "diagnostics",
1870
+ ok: isDiagnosticLevels,
1871
+ expected: "an object mapping codes to 'off', 'info', 'warn' or 'error'"
1872
+ },
1574
1873
  {
1575
1874
  key: "verbose",
1576
1875
  ok: isVerbose,
@@ -1731,7 +2030,7 @@ function walkFiles(dir, rootDir, exclude, extensionsSet) {
1731
2030
  const isExcluded = exclude ? (full, rel) => exclude.matches(full, rel) : () => false;
1732
2031
  for (const entry of entries) {
1733
2032
  const fullPath = join$1(dir, entry);
1734
- if (isExcluded(fullPath, relative(rootDir, fullPath))) continue;
2033
+ if (isExcluded(fullPath, relative$1(rootDir, fullPath))) continue;
1735
2034
  let stat;
1736
2035
  try {
1737
2036
  stat = statSync(fullPath);
@@ -1739,182 +2038,74 @@ function walkFiles(dir, rootDir, exclude, extensionsSet) {
1739
2038
  continue;
1740
2039
  }
1741
2040
  if (stat.isDirectory()) results.push(...walkFiles(fullPath, rootDir, exclude, extensionsSet));
1742
- else if (extensionsSet.has(extname(entry))) results.push(fullPath);
2041
+ else if (extensionsSet.has(extname$1(entry))) results.push(fullPath);
1743
2042
  }
1744
2043
  return results;
1745
2044
  }
1746
2045
  //#endregion
1747
- //#region pipeline/source-identity.ts
1748
- /**
1749
- * One derivation of source identity for discovery, watch ingestion, ownership
1750
- * and deletion: event paths are lookup INPUTS only and never fork identity.
1751
- */
2046
+ //#region pipeline/tsconfig-paths.ts
1752
2047
  /**
1753
- * True when `target` is `root` or a descendant, decided by `relative()`
1754
- * shape: string prefixing would let `/ui` claim `/ui-old`.
2048
+ * tsconfig `paths` → alias pairs, for drivers with no live bundler config.
2049
+ * The nearest config declaring `paths` wins wholesale — TS never merges.
1755
2050
  */
1756
- function isPathWithinRoot(root, target, pathApi = path) {
1757
- const rel = pathApi.relative(root, target);
1758
- return rel === "" || !pathApi.isAbsolute(rel) && rel !== ".." && !rel.startsWith(".." + pathApi.sep);
2051
+ function stripJsonc(text) {
2052
+ let out = "";
2053
+ let inString = false;
2054
+ let inLine = false;
2055
+ let inBlock = false;
2056
+ for (let i = 0; i < text.length; i++) {
2057
+ const ch = text[i];
2058
+ const next = text[i + 1];
2059
+ if (inLine) {
2060
+ if (ch === "\n") {
2061
+ inLine = false;
2062
+ out += ch;
2063
+ }
2064
+ continue;
2065
+ }
2066
+ if (inBlock) {
2067
+ if (ch === "*" && next === "/") {
2068
+ inBlock = false;
2069
+ i++;
2070
+ }
2071
+ continue;
2072
+ }
2073
+ if (inString) {
2074
+ out += ch;
2075
+ if (ch === "\\") {
2076
+ out += next ?? "";
2077
+ i++;
2078
+ } else if (ch === "\"") inString = false;
2079
+ continue;
2080
+ }
2081
+ if (ch === "\"") {
2082
+ inString = true;
2083
+ out += ch;
2084
+ continue;
2085
+ }
2086
+ if (ch === "/" && next === "/") {
2087
+ inLine = true;
2088
+ i++;
2089
+ continue;
2090
+ }
2091
+ if (ch === "/" && next === "*") {
2092
+ inBlock = true;
2093
+ i++;
2094
+ continue;
2095
+ }
2096
+ out += ch;
2097
+ }
2098
+ return out.replace(/,(\s*[}\]])/g, "$1");
1759
2099
  }
1760
- /**
1761
- * True when two paths share a platform root (win32 drive letter or UNC
1762
- * share; always true on posix). Drive letters compare case-insensitively.
1763
- */
1764
- function sharesVolumeRoot(a, b, pathApi = path) {
1765
- const volumeOf = (p) => pathApi.parse(pathApi.resolve(p)).root.toLowerCase();
1766
- return volumeOf(a) === volumeOf(b);
2100
+ /** `Object(value) === value` holds for exactly the blocks and lists
2101
+ * `JSON.parse` produces; the `[object Object]` tag separates the two. */
2102
+ function isJsonBlock(value) {
2103
+ return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
1767
2104
  }
1768
- function canonicalize(p) {
1769
- try {
1770
- return realpathSync$1(p);
1771
- } catch {
1772
- return null;
1773
- }
1774
- }
1775
- function createSourceIdentity(rootDir) {
1776
- const normalizedRootDir = path.normalize(rootDir);
1777
- const canonicalRootDir = canonicalize(normalizedRootDir) ?? normalizedRootDir;
1778
- const roots = [];
1779
- const aliases = /* @__PURE__ */ new Map();
1780
- const owningRecord = (canonicalPath) => {
1781
- let owner = null;
1782
- for (const record of roots) {
1783
- if (!isPathWithinRoot(record.canonical, canonicalPath)) continue;
1784
- if (!owner || record.canonical.length > owner.canonical.length) owner = record;
1785
- }
1786
- return owner;
1787
- };
1788
- return {
1789
- rootDir: normalizedRootDir,
1790
- canonicalRootDir,
1791
- registerExternalRoot(root) {
1792
- const asRegistered = path.normalize(root);
1793
- const canonical = canonicalize(asRegistered) ?? asRegistered;
1794
- const existing = roots.find((record) => record.canonical === canonical);
1795
- if (existing) return existing.canonical;
1796
- roots.push({
1797
- asRegistered,
1798
- canonical
1799
- });
1800
- return canonical;
1801
- },
1802
- externalRoots() {
1803
- return roots.map((record) => record.canonical);
1804
- },
1805
- resolveSourceId(inputPath) {
1806
- const lexical = path.normalize(inputPath);
1807
- const canonical = canonicalize(lexical);
1808
- if (canonical === null) return null;
1809
- const owner = owningRecord(canonical);
1810
- const ownedBy = (record) => {
1811
- const pathInRoot = path.relative(record.canonical, canonical);
1812
- return {
1813
- sourceKey: path.relative(normalizedRootDir, path.join(record.asRegistered, pathInRoot)),
1814
- owningRoot: record.canonical,
1815
- pathInRoot
1816
- };
1817
- };
1818
- const ownerIsMoreSpecific = owner !== null && owner.canonical.length > canonicalRootDir.length;
1819
- let resolved;
1820
- if (!ownerIsMoreSpecific && isPathWithinRoot(canonicalRootDir, canonical)) {
1821
- const key = path.relative(canonicalRootDir, canonical);
1822
- resolved = {
1823
- sourceKey: key,
1824
- owningRoot: null,
1825
- pathInRoot: key
1826
- };
1827
- } else if (owner) resolved = ownedBy(owner);
1828
- else return null;
1829
- aliases.set(lexical, resolved);
1830
- aliases.set(canonical, resolved);
1831
- return resolved;
1832
- },
1833
- resolveDeletedSourceId(inputPath) {
1834
- return aliases.get(path.normalize(inputPath)) ?? null;
1835
- },
1836
- containingExternalRoot(inputPath) {
1837
- const lexical = path.normalize(inputPath);
1838
- let owner = null;
1839
- for (const record of roots) {
1840
- if (!(isPathWithinRoot(record.canonical, lexical) || isPathWithinRoot(record.asRegistered, lexical))) continue;
1841
- if (!owner || record.canonical.length > owner.canonical.length) owner = record;
1842
- }
1843
- if (owner) return owner.canonical;
1844
- const canonical = canonicalize(lexical);
1845
- if (canonical === null) return null;
1846
- for (const record of roots) {
1847
- if (!isPathWithinRoot(record.canonical, canonical)) continue;
1848
- if (!owner || record.canonical.length > owner.canonical.length) owner = record;
1849
- }
1850
- return owner?.canonical ?? null;
1851
- }
1852
- };
1853
- }
1854
- //#endregion
1855
- //#region pipeline/tsconfig-paths.ts
1856
- /**
1857
- * tsconfig `paths` → alias pairs, for drivers with no live bundler config.
1858
- * The nearest config declaring `paths` wins wholesale — TS never merges.
1859
- */
1860
- function stripJsonc(text) {
1861
- let out = "";
1862
- let inString = false;
1863
- let inLine = false;
1864
- let inBlock = false;
1865
- for (let i = 0; i < text.length; i++) {
1866
- const ch = text[i];
1867
- const next = text[i + 1];
1868
- if (inLine) {
1869
- if (ch === "\n") {
1870
- inLine = false;
1871
- out += ch;
1872
- }
1873
- continue;
1874
- }
1875
- if (inBlock) {
1876
- if (ch === "*" && next === "/") {
1877
- inBlock = false;
1878
- i++;
1879
- }
1880
- continue;
1881
- }
1882
- if (inString) {
1883
- out += ch;
1884
- if (ch === "\\") {
1885
- out += next ?? "";
1886
- i++;
1887
- } else if (ch === "\"") inString = false;
1888
- continue;
1889
- }
1890
- if (ch === "\"") {
1891
- inString = true;
1892
- out += ch;
1893
- continue;
1894
- }
1895
- if (ch === "/" && next === "/") {
1896
- inLine = true;
1897
- i++;
1898
- continue;
1899
- }
1900
- if (ch === "/" && next === "*") {
1901
- inBlock = true;
1902
- i++;
1903
- continue;
1904
- }
1905
- out += ch;
1906
- }
1907
- return out.replace(/,(\s*[}\]])/g, "$1");
1908
- }
1909
- /** `Object(value) === value` holds for exactly the blocks and lists
1910
- * `JSON.parse` produces; the `[object Object]` tag separates the two. */
1911
- function isJsonBlock(value) {
1912
- return Object(value) === value && Object.prototype.toString.call(value) === "[object Object]";
1913
- }
1914
- /** A block or a list. A declared `paths` OWNS the setting and blocks its
1915
- * parents' even when it contributes nothing usable. */
1916
- function isJsonKeyed(value) {
1917
- return Object(value) === value;
2105
+ /** A block or a list. A declared `paths` OWNS the setting and blocks its
2106
+ * parents' even when it contributes nothing usable. */
2107
+ function isJsonKeyed(value) {
2108
+ return Object(value) === value;
1918
2109
  }
1919
2110
  /** A JSON string, excluding the boxed `String` object, which no path join
1920
2111
  * accepts. */
@@ -1958,8 +2149,8 @@ function readConfig(path) {
1958
2149
  };
1959
2150
  }
1960
2151
  function resolveExtendsTarget(specifier, fromDir) {
1961
- if (specifier.startsWith(".") || isAbsolute(specifier)) {
1962
- const base = resolve(fromDir, specifier);
2152
+ if (specifier.startsWith(".") || isAbsolute$1(specifier)) {
2153
+ const base = resolve$1(fromDir, specifier);
1963
2154
  for (const candidate of [
1964
2155
  base,
1965
2156
  `${base}.json`,
@@ -1984,7 +2175,7 @@ function loadChain(entryPath) {
1984
2175
  const queue = [entryPath];
1985
2176
  while (queue.length > 0) {
1986
2177
  const path = queue.shift();
1987
- const key = resolve(path);
2178
+ const key = resolve$1(path);
1988
2179
  if (visited.has(key)) continue;
1989
2180
  visited.add(key);
1990
2181
  const node = readConfig(key);
@@ -2009,7 +2200,7 @@ function readTsconfigAliasPairs(rootDir) {
2009
2200
  if (pathsOwner === void 0 || paths === null) return [];
2010
2201
  const baseOwner = chain.find((node) => node.baseUrl !== null);
2011
2202
  const baseUrl = baseOwner?.baseUrl ?? null;
2012
- const base = baseOwner !== void 0 && baseUrl !== null ? resolve(baseOwner.dir, baseUrl) : pathsOwner.dir;
2203
+ const base = baseOwner !== void 0 && baseUrl !== null ? resolve$1(baseOwner.dir, baseUrl) : pathsOwner.dir;
2013
2204
  const pairs = [];
2014
2205
  for (const [pattern, target] of paths) {
2015
2206
  const patternStars = pattern.split("*").length - 1;
@@ -2019,7 +2210,7 @@ function readTsconfigAliasPairs(rootDir) {
2019
2210
  if (!target.endsWith("/*")) continue;
2020
2211
  pairs.push({
2021
2212
  pattern: pattern.slice(0, -2),
2022
- target: resolve(base, target.slice(0, -2)),
2213
+ target: resolve$1(base, target.slice(0, -2)),
2023
2214
  kind: "prefix"
2024
2215
  });
2025
2216
  continue;
@@ -2027,13 +2218,223 @@ function readTsconfigAliasPairs(rootDir) {
2027
2218
  if (patternStars > 0 || targetStars > 0) continue;
2028
2219
  pairs.push({
2029
2220
  pattern,
2030
- target: resolve(base, target),
2221
+ target: resolve$1(base, target),
2031
2222
  kind: "exact"
2032
2223
  });
2033
2224
  }
2034
2225
  return pairs;
2035
2226
  }
2036
2227
  //#endregion
2228
+ //#region pipeline/kit-descriptor.ts
2229
+ /** A kit's description, which `animus build --kit` writes at its package
2230
+ * root and the kit exports as `./animus.json`. */
2231
+ const KIT_DESCRIPTOR_FILE = "animus.json";
2232
+ /** A path with `/` separators, as descriptors key their components. */
2233
+ function posixPath$1(path) {
2234
+ return path.split(sep).join("/");
2235
+ }
2236
+ /** The descriptor of the kit a build analysed. */
2237
+ function buildKitDescriptor(manifest) {
2238
+ const components = {};
2239
+ for (const id of Object.keys(manifest.components).sort()) {
2240
+ const component = manifest.components[id];
2241
+ components[posixPath$1(id)] = {
2242
+ definition: component.definition_fingerprint,
2243
+ runtimeProps: [...new Set(component.system_prop_names)].sort()
2244
+ };
2245
+ }
2246
+ return {
2247
+ format: 1,
2248
+ system: manifest.system_fingerprint,
2249
+ components
2250
+ };
2251
+ }
2252
+ /** Reads the descriptor at a kit's package root; `null` when it has none. */
2253
+ function readKitDescriptor(pkgRoot, rootDir) {
2254
+ const path = join(pkgRoot, KIT_DESCRIPTOR_FILE);
2255
+ if (!existsSync$1(path)) return null;
2256
+ const packageRoot = posixPath$1(relative(rootDir, pkgRoot));
2257
+ let wire;
2258
+ try {
2259
+ wire = JSON.parse(readFileSync$1(path, "utf-8"));
2260
+ } catch {
2261
+ return {
2262
+ packageRoot,
2263
+ unreadable: "is not valid JSON: rebuild it with animus build --kit"
2264
+ };
2265
+ }
2266
+ const block = isJsonBlock(wire) ? wire : {};
2267
+ if (block.format !== 1) return {
2268
+ packageRoot,
2269
+ unreadable: `declares descriptor format ${JSON.stringify(block.format ?? null)}, which this Animus does not read (it reads format 1): upgrade Animus, or install a kit built for this version`
2270
+ };
2271
+ const definitions = {};
2272
+ const components = isJsonBlock(block.components) ? block.components : {};
2273
+ for (const [id, component] of Object.entries(components)) {
2274
+ const definition = isJsonBlock(component) ? component.definition : null;
2275
+ if (isJsonString(definition)) definitions[posixPath$1(id)] = definition;
2276
+ }
2277
+ return {
2278
+ packageRoot,
2279
+ definitions
2280
+ };
2281
+ }
2282
+ /**
2283
+ * A descriptor in a format this Animus cannot read fails the build; one
2284
+ * whose component definitions differ from the kit's shipped source, as the
2285
+ * analysis read it, or that describes a component its analysed file no
2286
+ * longer declares, was built from other source and is stale.
2287
+ */
2288
+ function kitDescriptorDiagnostics(records, manifest) {
2289
+ if (records.length === 0) return [];
2290
+ const diagnostics = [];
2291
+ const analysedById = new Map(Object.entries(manifest.components).map(([id, component]) => [posixPath$1(id), component]));
2292
+ const analysedFiles = new Set(Object.keys(manifest.fileFacts).map(posixPath$1));
2293
+ const declared = new Set(Object.entries(manifest.fileFacts).flatMap(([file, facts]) => facts.chains.map((chain) => `${posixPath$1(file)}::${chain.descriptor.binding}`)));
2294
+ for (const record of records) {
2295
+ const file = `${record.packageRoot}/${KIT_DESCRIPTOR_FILE}`;
2296
+ if ("unreadable" in record) {
2297
+ diagnostics.push({
2298
+ file,
2299
+ component: "kit",
2300
+ kind: "error",
2301
+ message: record.unreadable,
2302
+ code: KIT_UNSUPPORTED_FORMAT,
2303
+ severity: severityFor(KIT_UNSUPPORTED_FORMAT)
2304
+ });
2305
+ continue;
2306
+ }
2307
+ for (const [id, definition] of Object.entries(record.definitions)) {
2308
+ const key = `${record.packageRoot}/${id}`;
2309
+ const analysed = analysedById.get(key);
2310
+ const fileAnalysed = analysedFiles.has(key.slice(0, key.lastIndexOf("::")));
2311
+ let stale;
2312
+ if (analysed) {
2313
+ if (analysed.definition_fingerprint === definition) continue;
2314
+ stale = `describes ${id} with a definition other than the one its shipped source declares`;
2315
+ } else if (fileAnalysed && !declared.has(key)) stale = `describes ${id}, which its shipped source no longer declares`;
2316
+ else continue;
2317
+ diagnostics.push({
2318
+ file,
2319
+ component: analysed?.binding ?? id.slice(id.lastIndexOf("::") + 2),
2320
+ kind: "warn",
2321
+ message: `${stale}, so the descriptor is stale: rebuild it with animus build --kit`,
2322
+ code: KIT_STALE_DESCRIPTOR,
2323
+ severity: severityFor(KIT_STALE_DESCRIPTOR)
2324
+ });
2325
+ }
2326
+ }
2327
+ return diagnostics;
2328
+ }
2329
+ //#endregion
2330
+ //#region pipeline/source-identity.ts
2331
+ /**
2332
+ * One derivation of source identity for discovery, watch ingestion, ownership
2333
+ * and deletion: event paths are lookup INPUTS only and never fork identity.
2334
+ */
2335
+ /**
2336
+ * True when `target` is `root` or a descendant, decided by `relative()`
2337
+ * shape: string prefixing would let `/ui` claim `/ui-old`.
2338
+ */
2339
+ function isPathWithinRoot(root, target, pathApi = path) {
2340
+ const rel = pathApi.relative(root, target);
2341
+ return rel === "" || !pathApi.isAbsolute(rel) && rel !== ".." && !rel.startsWith(".." + pathApi.sep);
2342
+ }
2343
+ /**
2344
+ * True when two paths share a platform root (win32 drive letter or UNC
2345
+ * share; always true on posix). Drive letters compare case-insensitively.
2346
+ */
2347
+ function sharesVolumeRoot(a, b, pathApi = path) {
2348
+ const volumeOf = (p) => pathApi.parse(pathApi.resolve(p)).root.toLowerCase();
2349
+ return volumeOf(a) === volumeOf(b);
2350
+ }
2351
+ function canonicalize(p) {
2352
+ try {
2353
+ return realpathSync$1(p);
2354
+ } catch {
2355
+ return null;
2356
+ }
2357
+ }
2358
+ function createSourceIdentity(rootDir) {
2359
+ const normalizedRootDir = path.normalize(rootDir);
2360
+ const canonicalRootDir = canonicalize(normalizedRootDir) ?? normalizedRootDir;
2361
+ const roots = [];
2362
+ const aliases = /* @__PURE__ */ new Map();
2363
+ const owningRecord = (canonicalPath) => {
2364
+ let owner = null;
2365
+ for (const record of roots) {
2366
+ if (!isPathWithinRoot(record.canonical, canonicalPath)) continue;
2367
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2368
+ }
2369
+ return owner;
2370
+ };
2371
+ return {
2372
+ rootDir: normalizedRootDir,
2373
+ canonicalRootDir,
2374
+ registerExternalRoot(root) {
2375
+ const asRegistered = path.normalize(root);
2376
+ const canonical = canonicalize(asRegistered) ?? asRegistered;
2377
+ const existing = roots.find((record) => record.canonical === canonical);
2378
+ if (existing) return existing.canonical;
2379
+ roots.push({
2380
+ asRegistered,
2381
+ canonical
2382
+ });
2383
+ return canonical;
2384
+ },
2385
+ externalRoots() {
2386
+ return roots.map((record) => record.canonical);
2387
+ },
2388
+ resolveSourceId(inputPath) {
2389
+ const lexical = path.normalize(inputPath);
2390
+ const canonical = canonicalize(lexical);
2391
+ if (canonical === null) return null;
2392
+ const owner = owningRecord(canonical);
2393
+ const ownedBy = (record) => {
2394
+ const pathInRoot = path.relative(record.canonical, canonical);
2395
+ return {
2396
+ sourceKey: path.relative(normalizedRootDir, path.join(record.asRegistered, pathInRoot)),
2397
+ owningRoot: record.canonical,
2398
+ pathInRoot
2399
+ };
2400
+ };
2401
+ const ownerIsMoreSpecific = owner !== null && owner.canonical.length > canonicalRootDir.length;
2402
+ let resolved;
2403
+ if (!ownerIsMoreSpecific && isPathWithinRoot(canonicalRootDir, canonical)) {
2404
+ const key = path.relative(canonicalRootDir, canonical);
2405
+ resolved = {
2406
+ sourceKey: key,
2407
+ owningRoot: null,
2408
+ pathInRoot: key
2409
+ };
2410
+ } else if (owner) resolved = ownedBy(owner);
2411
+ else return null;
2412
+ aliases.set(lexical, resolved);
2413
+ aliases.set(canonical, resolved);
2414
+ return resolved;
2415
+ },
2416
+ resolveDeletedSourceId(inputPath) {
2417
+ return aliases.get(path.normalize(inputPath)) ?? null;
2418
+ },
2419
+ containingExternalRoot(inputPath) {
2420
+ const lexical = path.normalize(inputPath);
2421
+ let owner = null;
2422
+ for (const record of roots) {
2423
+ if (!(isPathWithinRoot(record.canonical, lexical) || isPathWithinRoot(record.asRegistered, lexical))) continue;
2424
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2425
+ }
2426
+ if (owner) return owner.canonical;
2427
+ const canonical = canonicalize(lexical);
2428
+ if (canonical === null) return null;
2429
+ for (const record of roots) {
2430
+ if (!isPathWithinRoot(record.canonical, canonical)) continue;
2431
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2432
+ }
2433
+ return owner?.canonical ?? null;
2434
+ }
2435
+ };
2436
+ }
2437
+ //#endregion
2037
2438
  //#region pipeline/discover-packages.ts
2038
2439
  function findPackageRoot(absEntryPath) {
2039
2440
  let pkgRoot = dirname$1(absEntryPath);
@@ -2050,7 +2451,7 @@ function isExcludedPackageRelativePath(rel) {
2050
2451
  return PACKAGE_SRC_EXCLUDES.some((pattern) => rel.includes(pattern));
2051
2452
  }
2052
2453
  function walkPackageSources(packageDir, extensionsSet) {
2053
- return discoverFiles(packageDir, packageDir, void 0, extensionsSet).filter((absPath) => !isExcludedPackageRelativePath(relative(packageDir, absPath)));
2454
+ return discoverFiles(packageDir, packageDir, void 0, extensionsSet).filter((absPath) => !isExcludedPackageRelativePath(relative$1(packageDir, absPath)));
2054
2455
  }
2055
2456
  const PACKAGE_OUTPUT_EXCLUDES = [
2056
2457
  "node_modules",
@@ -2059,7 +2460,7 @@ const PACKAGE_OUTPUT_EXCLUDES = [
2059
2460
  ".d.ts",
2060
2461
  ".map"
2061
2462
  ];
2062
- const isFile = (path) => {
2463
+ const isFile$1 = (path) => {
2063
2464
  try {
2064
2465
  return statSync(path).isFile();
2065
2466
  } catch {
@@ -2071,14 +2472,15 @@ function resolveAbsolutePathSpecifier(absSpecifier, extensionsSet) {
2071
2472
  absSpecifier,
2072
2473
  ...Array.from(extensionsSet, (ext) => absSpecifier + ext),
2073
2474
  ...Array.from(extensionsSet, (ext) => join$1(absSpecifier, `index${ext}`))
2074
- ].find(isFile) ?? null;
2475
+ ].find(isFile$1) ?? null;
2075
2476
  }
2477
+ /** The package a bare specifier names: `@scope/name` or `name`. */
2076
2478
  function bareSpecifierPackageName(specifier) {
2077
2479
  const segments = specifier.split("/");
2078
2480
  return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
2079
2481
  }
2080
2482
  function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
2081
- if (isAbsolute(specifier)) {
2483
+ if (isAbsolute$1(specifier)) {
2082
2484
  const resolved = resolveAbsolutePathSpecifier(specifier, extensionsSet);
2083
2485
  if (!resolved) return null;
2084
2486
  return isPathWithinRoot(srcDir, resolved) ? resolved : null;
@@ -2087,6 +2489,76 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
2087
2489
  const subpath = specifier.slice(packageName.length + 1);
2088
2490
  return resolveAbsolutePathSpecifier(join$1(srcDir, subpath || "index"), extensionsSet);
2089
2491
  }
2492
+ /** The package's parsed `package.json`, or null when it cannot be read. */
2493
+ function readPackageManifest(pkgRoot) {
2494
+ try {
2495
+ return JSON.parse(readFileSync(join$1(pkgRoot, "package.json"), "utf-8"));
2496
+ } catch {
2497
+ return null;
2498
+ }
2499
+ }
2500
+ /** Each `exports` subpath and its value; a bare target or condition object
2501
+ * is the `.` entry. */
2502
+ function exportsSubpaths(manifest) {
2503
+ const exports = isJsonBlock(manifest) ? manifest.exports : void 0;
2504
+ if (exports === void 0 || exports === null) return [];
2505
+ const subpaths = isJsonBlock(exports) && Object.keys(exports).some((k) => k.startsWith(".")) ? exports : { ".": exports };
2506
+ return Object.entries(subpaths).filter(([subpath]) => subpath.startsWith("."));
2507
+ }
2508
+ /**
2509
+ * The export condition a kit gives each public entry's original source:
2510
+ * `{ "types": …, "animus": "./src/system.ts", "import": …, "default": … }`.
2511
+ * Hosts never resolve through it; discovery reads it, redirects the entry to
2512
+ * its source in every host, and the system loader prefers it.
2513
+ */
2514
+ const KIT_SOURCE_CONDITION = "animus";
2515
+ /** The package's kit source condition, or null when no exact `exports`
2516
+ * entry declares it: the package is no source kit. */
2517
+ function readKitSourceCondition(pkgRoot) {
2518
+ const condition = {
2519
+ entries: [],
2520
+ invalid: [],
2521
+ root: null
2522
+ };
2523
+ for (const [subpath, value] of exportsSubpaths(readPackageManifest(pkgRoot))) {
2524
+ if (subpath.includes("*") || !isJsonBlock(value)) continue;
2525
+ const target = value[KIT_SOURCE_CONDITION];
2526
+ if (!isJsonString(target)) continue;
2527
+ const file = resolve$1(pkgRoot, target);
2528
+ if (isPathWithinRoot(pkgRoot, file) && isFile$1(file)) condition.entries.push([subpath, file]);
2529
+ else condition.invalid.push([subpath, target]);
2530
+ }
2531
+ if (condition.entries.length === 0 && condition.invalid.length === 0) return null;
2532
+ condition.root = condition.entries.reduce((root, [, file]) => root === null ? dirname$1(file) : commonDirectory(root, dirname$1(file)), null);
2533
+ return condition;
2534
+ }
2535
+ function commonDirectory(a, b) {
2536
+ let dir = a;
2537
+ while (!isPathWithinRoot(dir, b)) dir = dirname$1(dir);
2538
+ return dir;
2539
+ }
2540
+ /** Whether the manifest at `dir` names the package `name`. */
2541
+ function isPackageNamed(dir, name) {
2542
+ const manifest = readPackageManifest(dir);
2543
+ return isJsonBlock(manifest) && manifest.name === name;
2544
+ }
2545
+ function realPath(path) {
2546
+ try {
2547
+ return realpathSync(path);
2548
+ } catch {
2549
+ return path;
2550
+ }
2551
+ }
2552
+ /** The root of the installed package `name` as Node finds it from `fromDir`,
2553
+ * the first `node_modules/<name>` up the directory tree, at its real path,
2554
+ * as host resolvers report a linked package. */
2555
+ function locatePackageRoot(name, fromDir) {
2556
+ for (let dir = fromDir;; dir = dirname$1(dir)) {
2557
+ const candidate = join$1(dir, "node_modules", name);
2558
+ if (existsSync(join$1(candidate, "package.json"))) return realPath(candidate);
2559
+ if (dir === dirname$1(dir)) return null;
2560
+ }
2561
+ }
2090
2562
  /**
2091
2563
  * A package's `sideEffects` field, decoded the way Vite reads it: a boolean
2092
2564
  * as written, or the list's globs, one with no `/` matching the basename
@@ -2095,12 +2567,7 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
2095
2567
  * extglobs), which leaves the bundler's side-effectful default.
2096
2568
  */
2097
2569
  function readPackageSideEffects(pkgRoot) {
2098
- let manifest;
2099
- try {
2100
- manifest = JSON.parse(readFileSync(join$1(pkgRoot, "package.json"), "utf-8"));
2101
- } catch {
2102
- return;
2103
- }
2570
+ const manifest = readPackageManifest(pkgRoot);
2104
2571
  const field = isJsonBlock(manifest) ? manifest.sideEffects : void 0;
2105
2572
  if (field === true || field === false) return field;
2106
2573
  if (!Array.isArray(field)) return void 0;
@@ -2108,9 +2575,80 @@ function readPackageSideEffects(pkgRoot) {
2108
2575
  if (globs.some((glob) => /[{}[\]()!+@]/.test(glob))) return void 0;
2109
2576
  return globs.map((glob) => globToRegExp(glob.includes("/") ? glob.replace(/^\.?\//, "") : `**/${glob}`));
2110
2577
  }
2578
+ /** Whether a bundler that reads the nearest `package.json`'s `sideEffects`
2579
+ * against the file's own path keeps `absFile`; undefined without a field
2580
+ * it reads, which keeps the bundler's side-effectful default. */
2581
+ function fileSideEffects(absFile) {
2582
+ const pkgRoot = findPackageRoot(absFile);
2583
+ const field = readPackageSideEffects(pkgRoot);
2584
+ if (field === void 0 || field === true || field === false) return field;
2585
+ return matchesSideEffects(field, pkgRoot, [absFile]);
2586
+ }
2587
+ /** Whether a `sideEffects` glob can name a code file: a last segment with
2588
+ * no extension can name any file, and an extension with glob syntax or of
2589
+ * a script can name code; `*.css` cannot. Brace alternatives are each read,
2590
+ * and braces this reading cannot expand count as naming code. */
2591
+ function globNamesCode(glob) {
2592
+ const alternatives = expandBraces(glob);
2593
+ if (alternatives === null) return true;
2594
+ return alternatives.some((alternative) => {
2595
+ const last = alternative.split("/").at(-1) ?? "";
2596
+ const dot = last.lastIndexOf(".");
2597
+ if (dot === -1) return true;
2598
+ const extension = last.slice(dot + 1);
2599
+ return /[*?[]/.test(extension) || /^[cm]?[jt]sx?$/.test(extension);
2600
+ });
2601
+ }
2602
+ /** Every alternative a glob's `{a,b}` groups spell, nested groups included;
2603
+ * null when its braces do not balance. */
2604
+ function expandBraces(glob) {
2605
+ const open = glob.indexOf("{");
2606
+ if (open === -1) return glob.includes("}") ? null : [glob];
2607
+ let depth = 0;
2608
+ const commas = [];
2609
+ for (let at = open; at < glob.length; at++) if (glob[at] === "{") depth++;
2610
+ else if (glob[at] === "," && depth === 1) commas.push(at);
2611
+ else if (glob[at] === "}" && --depth === 0) {
2612
+ const bounds = [
2613
+ open,
2614
+ ...commas,
2615
+ at
2616
+ ];
2617
+ const rest = expandBraces(glob.slice(at + 1));
2618
+ if (rest === null) return null;
2619
+ const expanded = [];
2620
+ for (let i = 0; i < bounds.length - 1; i++) {
2621
+ const inner = expandBraces(glob.slice(bounds[i] + 1, bounds[i + 1]));
2622
+ if (inner === null) return null;
2623
+ for (const middle of inner) for (const tail of rest) expanded.push(glob.slice(0, open) + middle + tail);
2624
+ }
2625
+ return expanded;
2626
+ }
2627
+ return null;
2628
+ }
2629
+ /**
2630
+ * True when a source module of the package that owns `absFile` must stay
2631
+ * side-effectful: its `sideEffects` list names shipped code, and no proven
2632
+ * map takes a shipped file to its source module, so a bundler that reads
2633
+ * the list against the source path can drop a listed effect. Undefined
2634
+ * leaves the bundler's own reading, which no list naming code can mislead.
2635
+ */
2636
+ function kitSourceModuleSideEffects(absFile) {
2637
+ const manifest = readPackageManifest(findPackageRoot(absFile));
2638
+ const field = isJsonBlock(manifest) ? manifest.sideEffects : void 0;
2639
+ if (!Array.isArray(field)) return void 0;
2640
+ return field.some((glob) => isJsonString(glob) && globNamesCode(glob)) ? true : void 0;
2641
+ }
2642
+ /** Whether a host keeps the module at `file` side-effectful: it lies under
2643
+ * one of the discovered kit source directories, and its package's list
2644
+ * could misread it (see `kitSourceModuleSideEffects`). A host asks this of
2645
+ * the resolved module, never of its importer alone. */
2646
+ function keepsKitSourceEffects(file, kitDirs) {
2647
+ return kitDirs.some((dir) => isPathWithinRoot(dir, file)) && kitSourceModuleSideEffects(file) === true;
2648
+ }
2111
2649
  function matchesSideEffects(globs, pkgRoot, absFiles) {
2112
2650
  return absFiles.some((file) => {
2113
- const relPath = relative(pkgRoot, file).split(sep).join("/");
2651
+ const relPath = relative$1(pkgRoot, file).split(sep$1).join("/");
2114
2652
  return globs.some((glob) => glob.test(relPath));
2115
2653
  });
2116
2654
  }
@@ -2131,20 +2669,11 @@ function exportTarget(value) {
2131
2669
  /** Each exact `exports` entry of the package at `pkgRoot`, as its subpath
2132
2670
  * and absolute target; `*` patterns name no single file and are skipped. */
2133
2671
  function packageExportEntries(pkgRoot) {
2134
- let manifest;
2135
- try {
2136
- manifest = JSON.parse(readFileSync(join$1(pkgRoot, "package.json"), "utf-8"));
2137
- } catch {
2138
- return [];
2139
- }
2140
- const exports = isJsonBlock(manifest) ? manifest.exports : void 0;
2141
- if (exports === void 0 || exports === null) return [];
2142
- const subpaths = isJsonBlock(exports) && Object.keys(exports).some((k) => k.startsWith(".")) ? exports : { ".": exports };
2143
2672
  const entries = [];
2144
- for (const [subpath, value] of Object.entries(subpaths)) {
2145
- if (!subpath.startsWith(".") || subpath.includes("*")) continue;
2673
+ for (const [subpath, value] of exportsSubpaths(readPackageManifest(pkgRoot))) {
2674
+ if (subpath.includes("*")) continue;
2146
2675
  const target = exportTarget(value);
2147
- if (target !== null) entries.push([subpath, resolve(pkgRoot, target)]);
2676
+ if (target !== null) entries.push([subpath, resolve$1(pkgRoot, target)]);
2148
2677
  }
2149
2678
  return entries;
2150
2679
  }
@@ -2181,6 +2710,16 @@ async function collectExternalPackageSources(opts) {
2181
2710
  const sourceEntrySideEffects = /* @__PURE__ */ new Map();
2182
2711
  /** `replaced` names the entry the redirect stands in for; it is only
2183
2712
  * needed, and so only resolved, for a glob list. */
2713
+ /** The entry `subpath` of the package resolves to without the redirect.
2714
+ * A resolver with no `import` condition, as Node's `require` has none,
2715
+ * finds no ESM-only entry, so the manifest's own target answers then. */
2716
+ const shippedEntry = async (specifier, pkgRoot, subpath) => {
2717
+ try {
2718
+ const resolved = await resolveSpecifier(specifier);
2719
+ if (resolved) return resolved;
2720
+ } catch {}
2721
+ return packageExportEntries(pkgRoot).find(([entry]) => entry === subpath)?.[1] ?? null;
2722
+ };
2184
2723
  const redirect = async (specifier, srcEntry, pkgRoot, replaced) => {
2185
2724
  sourceEntries.set(specifier, srcEntry);
2186
2725
  const field = readPackageSideEffects(pkgRoot);
@@ -2193,6 +2732,8 @@ async function collectExternalPackageSources(opts) {
2193
2732
  sourceEntrySideEffects.set(specifier, replacedEntry === null || matchesSideEffects(field, pkgRoot, [srcEntry, replacedEntry]));
2194
2733
  };
2195
2734
  const packageDirs = [];
2735
+ const linkedDirs = [];
2736
+ const outputDirs = [];
2196
2737
  const dirOwnerSets = {};
2197
2738
  const dirExtensions = {};
2198
2739
  const fileOwners = {};
@@ -2201,6 +2742,31 @@ async function collectExternalPackageSources(opts) {
2201
2742
  (dirOwnerSets[dir] ??= []).push(specifier);
2202
2743
  };
2203
2744
  const alreadyIngested = (relPath) => hasEntry(relPath) || pushed.has(relPath);
2745
+ /** Adds `file` to the analysis set unless it is there; false when it
2746
+ * cannot be read. */
2747
+ const ingest = (file, specifier) => {
2748
+ const relPath = relative$1(rootDir, file);
2749
+ if (alreadyIngested(relPath)) return true;
2750
+ let source;
2751
+ try {
2752
+ source = readFileSync(file, "utf-8");
2753
+ } catch (err) {
2754
+ onUnreadable(relPath, err);
2755
+ return false;
2756
+ }
2757
+ onSourceRead?.(source, relPath, file);
2758
+ entries.push({
2759
+ path: relPath,
2760
+ source
2761
+ });
2762
+ pushed.add(relPath);
2763
+ fileOwners[relPath] ??= specifier;
2764
+ return true;
2765
+ };
2766
+ const diagnostics = [];
2767
+ const reportedPackages = /* @__PURE__ */ new Set();
2768
+ const kitDescriptors = [];
2769
+ const describedRoots = /* @__PURE__ */ new Set();
2204
2770
  for (const specifier of specifiers) {
2205
2771
  let absEntry;
2206
2772
  try {
@@ -2208,8 +2774,15 @@ async function collectExternalPackageSources(opts) {
2208
2774
  } catch {
2209
2775
  absEntry = null;
2210
2776
  }
2211
- if (!absEntry && isAbsolute(specifier)) absEntry = resolveAbsolutePathSpecifier(specifier, extensionsSet);
2212
- if (!absEntry) {
2777
+ if (!absEntry && isAbsolute$1(specifier)) absEntry = resolveAbsolutePathSpecifier(specifier, extensionsSet);
2778
+ const packageName = isAbsolute$1(specifier) ? null : bareSpecifierPackageName(specifier);
2779
+ const resolvedRoot = absEntry ? findPackageRoot(absEntry) : null;
2780
+ const locatedRoot = packageName && locatePackageRoot(packageName, rootDir);
2781
+ const pkgRoot = resolvedRoot && (!locatedRoot || isPackageNamed(resolvedRoot, packageName) || realPath(resolvedRoot) === locatedRoot) ? resolvedRoot : locatedRoot || resolvedRoot;
2782
+ const condition = packageName && pkgRoot ? readKitSourceCondition(pkgRoot) : null;
2783
+ const entryKey = packageName && `.${specifier.slice(packageName.length)}`;
2784
+ absEntry ??= condition?.entries.find(([entry]) => entry === entryKey)?.[1] ?? null;
2785
+ if (!absEntry || !pkgRoot) {
2213
2786
  outcomes.push({
2214
2787
  specifier,
2215
2788
  outcome: "unresolvable",
@@ -2217,106 +2790,82 @@ async function collectExternalPackageSources(opts) {
2217
2790
  });
2218
2791
  continue;
2219
2792
  }
2220
- const pkgRoot = findPackageRoot(absEntry);
2793
+ const realRoot = realPath(pkgRoot);
2794
+ if (!describedRoots.has(realRoot)) {
2795
+ describedRoots.add(realRoot);
2796
+ const described = readKitDescriptor(pkgRoot, rootDir);
2797
+ if (described) kitDescriptors.push(described);
2798
+ }
2799
+ const linked = !isInstalledPackage(pkgRoot);
2221
2800
  const srcDir = join$1(pkgRoot, "src");
2222
2801
  let fileCount = 0;
2223
2802
  let staleDist = false;
2224
- if (existsSync(srcDir)) {
2803
+ if (packageName && !reportedPackages.has(packageName)) {
2804
+ reportedPackages.add(packageName);
2805
+ diagnostics.push(...sourceConditionDiagnostics(packageName, condition));
2806
+ }
2807
+ if (condition && packageName) {
2808
+ for (const [entry, target] of condition.entries) {
2809
+ const entrySpecifier = packageName + entry.slice(1);
2810
+ packageMap[entrySpecifier] = relative$1(rootDir, target);
2811
+ await redirect(entrySpecifier, target, pkgRoot, () => shippedEntry(entrySpecifier, pkgRoot, entry));
2812
+ }
2813
+ packageMap[specifier] ??= relative$1(rootDir, absEntry);
2814
+ if (condition.root) {
2815
+ packageDirs.push(condition.root);
2816
+ if (linked) linkedDirs.push(condition.root);
2817
+ claimDir(condition.root, specifier);
2818
+ dirExtensions[condition.root] = [...extensionsSet];
2819
+ onPackageResolved?.(specifier, condition.root);
2820
+ for (const file of walkPackageSources(condition.root, extensionsSet)) if (ingest(file, specifier)) fileCount++;
2821
+ }
2822
+ } else if (existsSync(srcDir)) {
2225
2823
  packageDirs.push(srcDir);
2824
+ if (linked) linkedDirs.push(srcDir);
2226
2825
  claimDir(srcDir, specifier);
2227
2826
  dirExtensions[srcDir] = [...extensionsSet];
2228
2827
  onPackageResolved?.(specifier, srcDir);
2229
2828
  const srcEntry = sourceEntryForSpecifier(specifier, srcDir, extensionsSet);
2230
2829
  if (srcEntry) {
2231
- packageMap[specifier] = relative(rootDir, srcEntry);
2830
+ packageMap[specifier] = relative$1(rootDir, srcEntry);
2232
2831
  const replaced = absEntry;
2233
2832
  await redirect(specifier, srcEntry, pkgRoot, async () => replaced);
2234
- } else packageMap[specifier] = relative(rootDir, absEntry);
2235
- if (!isAbsolute(specifier)) {
2236
- const packageName = bareSpecifierPackageName(specifier);
2833
+ } else packageMap[specifier] = relative$1(rootDir, absEntry);
2834
+ if (packageName) {
2237
2835
  if (packageName !== specifier && !(packageName in packageMap)) {
2238
2836
  const rootEntry = sourceEntryForSpecifier(packageName, srcDir, extensionsSet);
2239
2837
  if (rootEntry) {
2240
- packageMap[packageName] = relative(rootDir, rootEntry);
2241
- await redirect(packageName, rootEntry, pkgRoot, async () => {
2242
- try {
2243
- return await resolveSpecifier(packageName);
2244
- } catch {
2245
- return null;
2246
- }
2247
- });
2838
+ packageMap[packageName] = relative$1(rootDir, rootEntry);
2839
+ await redirect(packageName, rootEntry, pkgRoot, () => shippedEntry(packageName, pkgRoot, "."));
2248
2840
  }
2249
2841
  }
2250
2842
  }
2251
2843
  const pkgFiles = walkPackageSources(srcDir, extensionsSet);
2252
- staleDist = !isInstalledPackage(pkgRoot) && distEntryIsStale(absEntry, srcDir, pkgFiles);
2253
- for (const pkgFile of pkgFiles) {
2254
- const relPath = relative(rootDir, pkgFile);
2255
- if (alreadyIngested(relPath)) {
2256
- fileCount++;
2257
- continue;
2258
- }
2259
- let source;
2260
- try {
2261
- source = readFileSync(pkgFile, "utf-8");
2262
- } catch (err) {
2263
- onUnreadable(relPath, err);
2264
- continue;
2265
- }
2266
- onSourceRead?.(source, relPath, pkgFile);
2267
- entries.push({
2268
- path: relPath,
2269
- source
2270
- });
2271
- pushed.add(relPath);
2272
- fileOwners[relPath] ??= specifier;
2273
- fileCount++;
2274
- }
2844
+ staleDist = linked && distEntryIsStale(absEntry, srcDir, pkgFiles);
2845
+ for (const pkgFile of pkgFiles) if (ingest(pkgFile, specifier)) fileCount++;
2275
2846
  } else {
2276
2847
  const outputDir = dirname$1(absEntry);
2277
2848
  packageDirs.push(outputDir);
2849
+ if (linked) linkedDirs.push(outputDir);
2850
+ outputDirs.push(outputDir);
2278
2851
  claimDir(outputDir, specifier);
2279
2852
  onPackageResolved?.(specifier, outputDir);
2280
- packageMap[specifier] = relative(rootDir, absEntry);
2853
+ packageMap[specifier] = relative$1(rootDir, absEntry);
2281
2854
  const outputExtensions = new Set(extensionsSet);
2282
- outputExtensions.add(extname(absEntry));
2855
+ outputExtensions.add(extname$1(absEntry));
2283
2856
  dirExtensions[outputDir] = [...outputExtensions];
2284
2857
  const outputFiles = discoverFiles(outputDir, outputDir, void 0, outputExtensions).filter((file) => {
2285
- const relToOutput = relative(outputDir, file);
2858
+ const relToOutput = relative$1(outputDir, file);
2286
2859
  return !PACKAGE_OUTPUT_EXCLUDES.some((pattern) => relToOutput.includes(pattern));
2287
2860
  });
2288
2861
  if (!outputFiles.includes(absEntry)) outputFiles.unshift(absEntry);
2289
- for (const outputFile of outputFiles) {
2290
- const outputRelPath = relative(rootDir, outputFile);
2291
- if (alreadyIngested(outputRelPath)) {
2292
- fileCount++;
2293
- continue;
2294
- }
2295
- let source;
2296
- try {
2297
- source = readFileSync(outputFile, "utf-8");
2298
- } catch (err) {
2299
- onUnreadable(outputRelPath, err);
2300
- continue;
2301
- }
2302
- onSourceRead?.(source, outputRelPath, outputFile);
2303
- entries.push({
2304
- path: outputRelPath,
2305
- source
2306
- });
2307
- pushed.add(outputRelPath);
2308
- fileOwners[outputRelPath] ??= specifier;
2309
- fileCount++;
2310
- }
2862
+ for (const outputFile of outputFiles) if (ingest(outputFile, specifier)) fileCount++;
2311
2863
  }
2312
- if (!isAbsolute(specifier)) {
2313
- const packageName = bareSpecifierPackageName(specifier);
2314
- for (const [subpath, target] of packageExportEntries(pkgRoot)) {
2315
- const entrySpecifier = packageName + subpath.slice(1);
2316
- const targetRelPath = relative(rootDir, target);
2317
- if (entrySpecifier in packageMap || !alreadyIngested(targetRelPath)) continue;
2318
- packageMap[entrySpecifier] = targetRelPath;
2319
- }
2864
+ if (packageName) for (const [subpath, target] of packageExportEntries(pkgRoot)) {
2865
+ const entrySpecifier = packageName + subpath.slice(1);
2866
+ const targetRelPath = relative$1(rootDir, target);
2867
+ if (entrySpecifier in packageMap || !alreadyIngested(targetRelPath)) continue;
2868
+ packageMap[entrySpecifier] = targetRelPath;
2320
2869
  }
2321
2870
  outcomes.push({
2322
2871
  specifier,
@@ -2330,12 +2879,112 @@ async function collectExternalPackageSources(opts) {
2330
2879
  sourceEntries,
2331
2880
  sourceEntrySideEffects,
2332
2881
  packageDirs,
2882
+ linkedDirs,
2883
+ outputDirs,
2333
2884
  dirOwnerSets,
2334
2885
  dirExtensions,
2335
2886
  fileOwners,
2336
- outcomes
2887
+ outcomes,
2888
+ diagnostics: [...diagnostics, ...noKitFilesDiagnostics(outcomes)],
2889
+ kitDescriptors
2337
2890
  };
2338
2891
  }
2892
+ /** The dependency names a manifest lists in `fields`. */
2893
+ function dependencyNames(manifest, fields) {
2894
+ if (!isJsonBlock(manifest)) return [];
2895
+ const names = /* @__PURE__ */ new Set();
2896
+ for (const field of fields) {
2897
+ const deps = manifest[field];
2898
+ if (isJsonBlock(deps)) for (const name of Object.keys(deps)) names.add(name);
2899
+ }
2900
+ return [...names];
2901
+ }
2902
+ /** The source kits the application at `rootDir` declares as dependencies.
2903
+ * A bundler serves an installed one as compiled code unless told
2904
+ * otherwise: Next keeps it external on the server unless it is in
2905
+ * `transpilePackages`, and Vite's optimizer prebundles it. */
2906
+ function sourceKitDependencies(rootDir) {
2907
+ return dependencyNames(readPackageManifest(rootDir), ["dependencies", "devDependencies"]).flatMap((name) => {
2908
+ const pkgRoot = locatePackageRoot(name, rootDir);
2909
+ if (pkgRoot === null || readKitSourceCondition(pkgRoot) === null) return [];
2910
+ return [{
2911
+ name,
2912
+ installed: isInstalledPackage(pkgRoot),
2913
+ dependencies: dependencyNames(readPackageManifest(pkgRoot), ["dependencies"])
2914
+ }];
2915
+ });
2916
+ }
2917
+ /** A specifier that names a package: not relative or absolute, and no URL,
2918
+ * scheme (`node:`, `virtual:`), subpath import or virtual id. */
2919
+ function isPackageSpecifier(specifier) {
2920
+ return /^(?:@[^/\\:]+\/)?[^./\\#\0:][^:]*$/.test(specifier);
2921
+ }
2922
+ /**
2923
+ * An error for each kit the application's own `files` import or re-export
2924
+ * whose system the application's system does not include: a package that
2925
+ * declares the kit source condition, with no kit the system extends
2926
+ * (`systemKits`) in that package. One app build has exactly one system, and
2927
+ * every kit's system is part of it, so such a kit is not extracted against
2928
+ * a system it was not built for. One batch parse reads the imports of every
2929
+ * file the engine parses as it is; an adapted source (`.svelte`, `.mdx`)
2930
+ * reaches the engine only as its generated children.
2931
+ */
2932
+ function importedKitDiagnostics(files, engine, rootDir, systemKits) {
2933
+ const { extractFacts } = engine;
2934
+ const parsed = files.filter(({ path }) => isEngineTransformExtension(path));
2935
+ if (!extractFacts || parsed.length === 0) return [];
2936
+ let facts;
2937
+ try {
2938
+ facts = parseInternalWire(extractFacts(JSON.stringify(parsed.map(({ path, source }) => ({
2939
+ path,
2940
+ source
2941
+ })))), "extractFacts");
2942
+ } catch {
2943
+ return [];
2944
+ }
2945
+ const seen = new Set(systemKits.filter((specifier) => !isAbsolute$1(specifier)).map(bareSpecifierPackageName));
2946
+ const diagnostics = [];
2947
+ for (const [path, file] of Object.entries(facts.files)) {
2948
+ if (file.parsePanicked) continue;
2949
+ const specifiers = [...file.imports.map((fact) => fact.source), ...file.exports.flatMap((fact) => fact.source === null ? [] : [fact.source])];
2950
+ for (const specifier of specifiers) {
2951
+ if (!isPackageSpecifier(specifier)) continue;
2952
+ const name = bareSpecifierPackageName(specifier);
2953
+ if (seen.has(name)) continue;
2954
+ seen.add(name);
2955
+ const pkgRoot = locatePackageRoot(name, dirname$1(resolve$1(rootDir, path)));
2956
+ if (pkgRoot && readKitSourceCondition(pkgRoot)) diagnostics.push({
2957
+ file: path,
2958
+ component: name,
2959
+ kind: "warn",
2960
+ message: `the application imports this kit, but its system is not included in the application's system, so its components are not extracted — include the kit's system in the application's system: createSystem().extend(<the kit's system>)`,
2961
+ code: KIT_SYSTEM_NOT_INCLUDED,
2962
+ severity: severityFor(KIT_SYSTEM_NOT_INCLUDED)
2963
+ });
2964
+ }
2965
+ }
2966
+ return diagnostics;
2967
+ }
2968
+ /** A kit without the source condition, warned once, or each condition entry
2969
+ * whose target is missing or outside the package. */
2970
+ function sourceConditionDiagnostics(packageName, condition) {
2971
+ if (condition === null) return [{
2972
+ file: packageName,
2973
+ component: "kit",
2974
+ kind: "warn",
2975
+ message: `declares no "${KIT_SOURCE_CONDITION}" export condition, so discovery guesses its source from src/ — give each public exports entry an "${KIT_SOURCE_CONDITION}" condition naming its original source`,
2976
+ code: KIT_WITHOUT_SOURCE_CONDITION,
2977
+ severity: severityFor(KIT_WITHOUT_SOURCE_CONDITION)
2978
+ }];
2979
+ return condition.invalid.map(([subpath, target]) => ({
2980
+ file: packageName,
2981
+ component: "kit",
2982
+ kind: "warn",
2983
+ message: `exports["${subpath}"].${KIT_SOURCE_CONDITION} names ${target}, which is missing or outside the package, so that entry is not read from source`,
2984
+ code: INVALID_KIT_SOURCE_CONDITION,
2985
+ severity: severityFor(INVALID_KIT_SOURCE_CONDITION)
2986
+ }));
2987
+ }
2339
2988
  function firstOwners(dirOwnerSets) {
2340
2989
  const owners = {};
2341
2990
  for (const [dir, specifiers] of Object.entries(dirOwnerSets)) if (specifiers.length > 0) owners[dir] = specifiers[0];
@@ -2348,7 +2997,7 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
2348
2997
  const targetRejected = (specifier, absTarget) => rejectedSpecifiers.has(specifier) || underRejectedDir(absTarget);
2349
2998
  const packageMap = {};
2350
2999
  for (const [specifier, relTarget] of Object.entries(collected.packageMap)) {
2351
- if (targetRejected(specifier, resolve(rootDir, relTarget))) continue;
3000
+ if (targetRejected(specifier, resolve$1(rootDir, relTarget))) continue;
2352
3001
  packageMap[specifier] = relTarget;
2353
3002
  }
2354
3003
  const sourceEntries = /* @__PURE__ */ new Map();
@@ -2381,10 +3030,14 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
2381
3030
  sourceEntries,
2382
3031
  sourceEntrySideEffects,
2383
3032
  packageDirs: collected.packageDirs.filter((dir) => !rejectedDirs.includes(dir)),
3033
+ linkedDirs: collected.linkedDirs.filter((dir) => !rejectedDirs.includes(dir)),
3034
+ outputDirs: collected.outputDirs.filter((dir) => !rejectedDirs.includes(dir)),
2384
3035
  dirOwnerSets,
2385
3036
  dirExtensions,
2386
3037
  fileOwners,
2387
- outcomes: collected.outcomes
3038
+ outcomes: collected.outcomes,
3039
+ diagnostics: collected.diagnostics,
3040
+ kitDescriptors: collected.kitDescriptors.filter((record) => !rejectedDirs.some((dir) => isPathWithinRoot(resolve$1(rootDir, record.packageRoot), dir)))
2388
3041
  };
2389
3042
  }
2390
3043
  function unresolvableIncludesMessage(outcomes) {
@@ -2392,29 +3045,224 @@ function unresolvableIncludesMessage(outcomes) {
2392
3045
  if (unresolvable.length === 0) return null;
2393
3046
  return `[animus-extract] unresolvable include specifier(s): ${unresolvable.join(", ")}`;
2394
3047
  }
2395
- /** A stale dist silently skews merged registry content while discovery
2396
- * compiles the fresh sources; null when no specifier is stale. */
2397
- function staleDistIncludesMessage(outcomes) {
2398
- const stale = outcomes.filter((record) => record.outcome === "stale-dist").map((record) => record.specifier);
2399
- if (stale.length === 0) return null;
2400
- 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`;
3048
+ /** A stale dist silently skews merged registry content while discovery
3049
+ * compiles the fresh sources; null when no specifier is stale. */
3050
+ function staleDistIncludesMessage(outcomes) {
3051
+ const stale = outcomes.filter((record) => record.outcome === "stale-dist").map((record) => record.specifier);
3052
+ if (stale.length === 0) return null;
3053
+ 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`;
3054
+ }
3055
+ /** The engine's own parse of a module's bindings, through its
3056
+ * `extractFacts`; undefined for an engine without one. A parse that
3057
+ * panicked counts as no parse. */
3058
+ function engineModuleParser(engine) {
3059
+ const { extractFacts } = engine;
3060
+ if (!extractFacts) return void 0;
3061
+ return (source, path) => {
3062
+ try {
3063
+ const facts = parseInternalWire(extractFacts(JSON.stringify([{
3064
+ path,
3065
+ source
3066
+ }])), "extractFacts").files[path];
3067
+ return facts && !facts.parsePanicked ? {
3068
+ imports: facts.imports,
3069
+ exports: facts.exports,
3070
+ unboundCreateSystemCalls: facts.unboundCreateSystemCalls ?? []
3071
+ } : null;
3072
+ } catch {
3073
+ return null;
3074
+ }
3075
+ };
3076
+ }
3077
+ const ANIMUS_SYSTEM_SPECIFIER = /^@animus-ui\/system(?:\/.*)?$/;
3078
+ /** The file a specifier names from `importer`: a relative one by the
3079
+ * ingestion's probe order, a bare one by the host's package resolution. */
3080
+ async function resolveModuleFile(importer, specifier, resolvePackage) {
3081
+ if (specifier.startsWith(".")) return relativeSourceCandidates(importer, specifier).find(isFile$1) ?? null;
3082
+ try {
3083
+ return await resolvePackage?.(specifier) ?? null;
3084
+ } catch {
3085
+ return null;
3086
+ }
3087
+ }
3088
+ const escapeRegExp$1 = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3089
+ /**
3090
+ * Follows `name`, exported by the module `specifier` names from `importer`,
3091
+ * through re-exports to its declaration. It is Animus's factory only when
3092
+ * that is `createSystem` of `@animus-ui/system`, and another identity only
3093
+ * when a module is shown to declare it; anything discovery cannot follow is
3094
+ * unknown.
3095
+ */
3096
+ async function exportIdentity(importer, specifier, name, following, seen) {
3097
+ if (ANIMUS_SYSTEM_SPECIFIER.test(specifier)) return name === "createSystem" ? { kind: "animus" } : {
3098
+ kind: "other",
3099
+ what: `'${name}' of ${specifier}`
3100
+ };
3101
+ const file = await resolveModuleFile(importer, specifier, following.resolvePackage);
3102
+ if (file === null) return {
3103
+ kind: "unknown",
3104
+ why: `'${specifier}' could not be resolved`
3105
+ };
3106
+ const key = `${file}#${name}`;
3107
+ if (seen.has(key)) return {
3108
+ kind: "unknown",
3109
+ why: `'${specifier}' re-exports it in a cycle`
3110
+ };
3111
+ seen.add(key);
3112
+ let source;
3113
+ try {
3114
+ source = readFileSync(file, "utf-8");
3115
+ } catch {
3116
+ return {
3117
+ kind: "unknown",
3118
+ why: `'${specifier}' could not be read`
3119
+ };
3120
+ }
3121
+ const record = following.parseModule(source, file);
3122
+ if (record === null) return {
3123
+ kind: "unknown",
3124
+ why: `'${specifier}' could not be parsed`
3125
+ };
3126
+ const exported = record.exports.find((entry) => entry.exported === name);
3127
+ if (exported?.source) return exportIdentity(file, exported.source, exported.original ?? name, following, seen);
3128
+ const local = exported?.local ?? name;
3129
+ const imported = record.imports.find((entry) => entry.local === local);
3130
+ if (exported && imported) return exportIdentity(file, imported.source, imported.imported, following, seen);
3131
+ if (exported || new RegExp(`\\bexport\\s+(?:async\\s+)?(?:function\\*?|class|const|let|var)\\s+${escapeRegExp$1(name)}\\b`).test(source)) return {
3132
+ kind: "other",
3133
+ what: `the '${name}' that '${specifier}' declares`
3134
+ };
3135
+ const stars = [];
3136
+ for (const match of source.matchAll(/\bexport\s*\*\s*from\s*['"]([^'"]+)['"]/g)) stars.push(await exportIdentity(file, match[1], name, following, seen));
3137
+ const definite = stars.filter((identity) => identity.kind !== "unknown");
3138
+ if (definite.length === 1) return definite[0];
3139
+ if (definite.length > 1) return {
3140
+ kind: "unknown",
3141
+ why: `more than one \`export *\` of '${specifier}' provides '${name}'`
3142
+ };
3143
+ return stars.find((identity) => identity.kind === "unknown") ?? {
3144
+ kind: "unknown",
3145
+ why: `'${specifier}' has no export '${name}' discovery can read`
3146
+ };
3147
+ }
3148
+ /** 1-based line and column of `offset` in `source`. */
3149
+ function locationIn(source, offset) {
3150
+ const before = source.slice(0, offset);
3151
+ const lineStart = before.lastIndexOf("\n") + 1;
3152
+ return {
3153
+ line: before.split("\n").length,
3154
+ column: offset - lineStart + 1
3155
+ };
2401
3156
  }
2402
- /** The engine's own parse of a module's imports, through its
2403
- * `extractFacts`; undefined for an engine without one. A parse that
2404
- * panicked reports no imports, so it counts as no parse. */
2405
- function engineImportParser(engine) {
2406
- const { extractFacts } = engine;
2407
- if (!extractFacts) return void 0;
2408
- return (source, path) => {
2409
- try {
2410
- const facts = parseInternalWire(extractFacts(JSON.stringify([{
2411
- path,
2412
- source
2413
- }])), "extractFacts").files[path];
2414
- return facts && !facts.parsePanicked ? facts.imports : null;
2415
- } catch {
2416
- return null;
3157
+ const NAMESPACE_IMPORT = /\bimport\s+(?:[a-zA-Z_$][a-zA-Z0-9_$]*\s*,\s*)?\*\s*as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s+from\s+['"]([^'"]+)['"]/g;
3158
+ /**
3159
+ * The callees that are Animus's `createSystem` in a parsed system file. A
3160
+ * binding counts once its identity is followed to the factory: a named
3161
+ * import, through local and package re-exports; a namespace import's
3162
+ * member; or a property destructured from a namespace import, `require` or
3163
+ * `import()` of a module. A binding named `createSystem` shown to be
3164
+ * something else is no root. One whose identity discovery cannot follow
3165
+ * keeps the admission spelling gave it, so a resolution limit never drops a
3166
+ * kit. Both are reported. A call of the bare name, which the file neither
3167
+ * imports nor declares, is no root: the system loader evaluates the file
3168
+ * without auto-imports, where the name is undefined. It is reported once.
3169
+ */
3170
+ async function systemRoots(systemFilePath, source, record, following) {
3171
+ const { imports } = record;
3172
+ const roots = /* @__PURE__ */ new Set();
3173
+ const identityOf = (specifier, name) => exportIdentity(systemFilePath, specifier, name, following, /* @__PURE__ */ new Set());
3174
+ const called = (callee) => callOf(callee).test(source);
3175
+ /** Admits a binding named `createSystem` by its identity: an unknown one
3176
+ * keeps `spellingAdmits`, and every unproven one is reported. */
3177
+ const admit = (local, offset, identity, spellingAdmits) => {
3178
+ if (identity.kind === "animus") {
3179
+ roots.add(local);
3180
+ return;
3181
+ }
3182
+ if (identity.kind === "unknown" && spellingAdmits) roots.add(local);
3183
+ const shown = identity.kind === "other" ? `it is ${identity.what}, so it is not read as a system root` : `discovery cannot follow what it is bound to (${identity.why}), so it is ${spellingAdmits ? "still read" : "not read"} as a system root by its name alone`;
3184
+ following.report?.({
3185
+ file: systemFilePath,
3186
+ component: local,
3187
+ kind: "warn",
3188
+ message: `'${local}' is not proven to be Animus's createSystem: ${shown} — import createSystem from '@animus-ui/system', directly or through a re-export discovery can follow`,
3189
+ code: UNPROVEN_ROOT_BINDING,
3190
+ severity: severityFor(UNPROVEN_ROOT_BINDING),
3191
+ ...locationIn(source, offset)
3192
+ });
3193
+ };
3194
+ for (const binding of imports) {
3195
+ if (binding.imported === "createSystem" || binding.local === "createSystem") {
3196
+ const statement = new RegExp(`\\bimport\\b[^;]*?\\bfrom\\s*['"]${escapeRegExp$1(binding.source)}['"]`, "g");
3197
+ let offset = 0;
3198
+ for (const match of source.matchAll(statement)) {
3199
+ const at = match[0].search(new RegExp(`(?<![a-zA-Z0-9_$])${escapeRegExp$1(binding.local)}\\b`));
3200
+ if (at !== -1) {
3201
+ offset = match.index + at;
3202
+ break;
3203
+ }
3204
+ }
3205
+ admit(binding.local, offset, await identityOf(binding.source, binding.imported), binding.imported === "createSystem");
3206
+ continue;
3207
+ }
3208
+ if (binding.source.startsWith(".") && called(binding.local) && (await identityOf(binding.source, binding.imported)).kind === "animus") roots.add(binding.local);
3209
+ }
3210
+ const namespaces = /* @__PURE__ */ new Map();
3211
+ for (const [, namespace, specifier] of source.matchAll(NAMESPACE_IMPORT)) {
3212
+ namespaces.set(namespace, specifier);
3213
+ const member = `${namespace}.createSystem`;
3214
+ if (ANIMUS_SYSTEM_SPECIFIER.test(specifier) || specifier.startsWith(".") && called(member) && (await identityOf(specifier, "createSystem")).kind === "animus") roots.add(member);
3215
+ }
3216
+ const destructuredFrom = (initializer) => {
3217
+ const loaded = /^\(?\s*(?:(?:await\s+)?import|require)\(\s*['"]([^'"]+)['"]\s*\)\s*\)?$/.exec(initializer);
3218
+ if (loaded) return identityOf(loaded[1], "createSystem");
3219
+ const namespace = namespaces.get(initializer);
3220
+ if (namespace !== void 0) return identityOf(namespace, "createSystem");
3221
+ return Promise.resolve({
3222
+ kind: "unknown",
3223
+ why: `it is destructured from '${initializer}', which is not a module namespace`
3224
+ });
3225
+ };
3226
+ for (const { local, offset, initializer } of destructuredCreateSystems(source)) admit(local, offset, await destructuredFrom(initializer), local === "createSystem");
3227
+ const unimported = unimportedCreateSystemCall(systemFilePath, record);
3228
+ if (unimported) following.report?.(unimported);
3229
+ return roots;
3230
+ }
3231
+ /** A call of `callee` as a whole name, not a member. */
3232
+ function callOf(callee) {
3233
+ return new RegExp(`(?<![a-zA-Z0-9_$.])${escapeRegExp$1(callee)}\\s*\\(`);
3234
+ }
3235
+ /** Each `createSystem` property a declaration destructures: its local name,
3236
+ * its offset in `source`, and the destructured initializer. */
3237
+ function* destructuredCreateSystems(source) {
3238
+ for (const match of source.matchAll(/\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*([^;\n]+)/g)) {
3239
+ const [whole, pattern, initializer] = match;
3240
+ for (const property of pattern.split(",")) {
3241
+ const binding = /^\s*createSystem\s*(?::\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*(?:=[\s\S]*)?$/.exec(property);
3242
+ if (!binding) continue;
3243
+ yield {
3244
+ local: binding[1] ?? "createSystem",
3245
+ offset: match.index + whole.indexOf("createSystem"),
3246
+ initializer: initializer.trim()
3247
+ };
2417
3248
  }
3249
+ }
3250
+ }
3251
+ /** The warning for a `createSystem` call that the parser's scopes leave
3252
+ * unbound: no import, declaration or parameter names it. Null when there is
3253
+ * none, or when the parser reports no scopes. */
3254
+ function unimportedCreateSystemCall(systemFilePath, record) {
3255
+ const call = record.unboundCreateSystemCalls?.[0];
3256
+ if (!call) return null;
3257
+ return {
3258
+ file: systemFilePath,
3259
+ component: "createSystem",
3260
+ kind: "warn",
3261
+ message: `'createSystem' is called with no import or local binding, so it is not read as a system root: the system loader evaluates this file without auto-imports, where the name is undefined — import createSystem from '@animus-ui/system'`,
3262
+ code: UNIMPORTED_CREATE_SYSTEM,
3263
+ severity: severityFor(UNIMPORTED_CREATE_SYSTEM),
3264
+ line: call[0],
3265
+ column: call[1]
2418
3266
  };
2419
3267
  }
2420
3268
  /** A pattern matching a call of any of `callees` as a whole name, or null
@@ -2423,24 +3271,20 @@ function rootCallPattern(callees) {
2423
3271
  if (callees.length === 0) return null;
2424
3272
  return `(?<![a-zA-Z0-9_$.])(?:${callees.map((callee) => callee.replace(/[.$]/g, "\\$&")).join("|")})`;
2425
3273
  }
2426
- function extractSystemFilePackages(systemFilePath, parseImports) {
3274
+ async function extractSystemFilePackages(systemFilePath, parseModule, report, resolvePackage) {
2427
3275
  let source;
2428
3276
  try {
2429
3277
  source = readFileSync(systemFilePath, "utf-8");
2430
3278
  } catch {
2431
3279
  return [];
2432
3280
  }
2433
- const parsed = parseImports?.(source, systemFilePath) ?? null;
2434
- const parsedImports = parsed && parsed.length > 0 ? parsed : null;
3281
+ const parsed = parseModule?.(source, systemFilePath) ?? null;
2435
3282
  let rootCall = "createSystem";
2436
- if (parsedImports) {
2437
- const shadowed = parsedImports.some((binding) => binding.local === "createSystem" && binding.imported !== "createSystem") || /\b(?:function\*?|class)\s+createSystem\b|\b(?:const|let|var)\s+createSystem\b/.test(source);
2438
- rootCall = rootCallPattern([.../* @__PURE__ */ new Set([
2439
- ...parsedImports.filter((binding) => binding.imported === "createSystem").map((binding) => binding.local),
2440
- ...Array.from(source.matchAll(/\bimport\s+(?:[a-zA-Z_$][a-zA-Z0-9_$]*\s*,\s*)?\*\s*as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s+from\s+['"]@animus-ui\/system(?:\/[^'"]*)?['"]/g), (match) => `${match[1]}.createSystem`),
2441
- ...shadowed ? [] : ["createSystem"]
2442
- ])]);
2443
- }
3283
+ if (parseModule && parsed) rootCall = rootCallPattern([...await systemRoots(systemFilePath, source, parsed, {
3284
+ parseModule,
3285
+ resolvePackage,
3286
+ report
3287
+ })]);
2444
3288
  const identifiers = /* @__PURE__ */ new Set();
2445
3289
  const constructorRegex = rootCall === null ? null : new RegExp(`${rootCall}\\s*\\(\\s*\\{[^}]*?\\bincludes\\s*:\\s*\\[([^\\]]*)\\]`, "gs");
2446
3290
  const chainRegex = /\.includes\s*\(\s*\[([^\]]*)\]\s*\)/gs;
@@ -2608,9 +3452,9 @@ function extractSystemFilePackages(systemFilePath, parseImports) {
2608
3452
  }
2609
3453
  if (identifiers.size === 0) return [];
2610
3454
  const importMap = /* @__PURE__ */ new Map();
2611
- for (const binding of parsedImports ?? []) importMap.set(binding.local, binding.source);
3455
+ for (const binding of parsed?.imports ?? []) importMap.set(binding.local, binding.source);
2612
3456
  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;
2613
- if (!parsedImports) {
3457
+ if (!parsed) {
2614
3458
  let importMatch;
2615
3459
  while ((importMatch = importRegex.exec(source)) !== null) {
2616
3460
  const [, comboDefault, namedImports, defaultImport, specifier] = importMatch;
@@ -2640,7 +3484,7 @@ function extractSystemFilePackages(systemFilePath, parseImports) {
2640
3484
  for (const id of identifiers) {
2641
3485
  const specifier = importMap.get(throughAliases(id));
2642
3486
  if (!specifier) continue;
2643
- if (specifier.startsWith(".")) packages.add(resolve(systemFileDir, specifier));
3487
+ if (specifier.startsWith(".")) packages.add(resolve$1(systemFileDir, specifier));
2644
3488
  else packages.add(specifier);
2645
3489
  }
2646
3490
  return Array.from(packages);
@@ -2695,151 +3539,6 @@ function createSourceCorpus(host, ingestor = createSourceIngestor(host)) {
2695
3539
  };
2696
3540
  }
2697
3541
  //#endregion
2698
- //#region pipeline/asset-placeholders.ts
2699
- const ASSET_PLACEHOLDER_PREFIX = "animus-asset:";
2700
- const URL_OPEN_RE = /url\(\s*(['"]?)/gi;
2701
- const IMAGE_SET_OPEN_RE = /(?:-webkit-)?image-set\(/gi;
2702
- const BARE_SPECIFIER_RE = /^[^'")\s]+/;
2703
- /** The index of the `)` that closes the `(` at `open`, outside strings. */
2704
- function closingParen(css, open) {
2705
- let depth = 0;
2706
- let quote = null;
2707
- for (let i = open; i < css.length; i += 1) {
2708
- const c = css[i];
2709
- if (quote !== null) {
2710
- if (c === quote) quote = null;
2711
- } else if (c === "\"" || c === "'") quote = c;
2712
- else if (c === "(") depth += 1;
2713
- else if (c === ")") {
2714
- depth -= 1;
2715
- if (depth === 0) return i;
2716
- }
2717
- }
2718
- return css.length;
2719
- }
2720
- function quotedReference(css, quoteAt) {
2721
- const start = quoteAt + 1;
2722
- if (!css.startsWith("animus-asset:", start)) return null;
2723
- const end = css.indexOf(css[quoteAt], start);
2724
- if (end === -1) return null;
2725
- return {
2726
- start,
2727
- end,
2728
- specifier: css.slice(start + 13, end)
2729
- };
2730
- }
2731
- function assetReferences(css) {
2732
- if (!css.includes("animus-asset:")) return [];
2733
- const references = [];
2734
- for (const open of css.matchAll(URL_OPEN_RE)) {
2735
- const at = (open.index ?? 0) + open[0].length;
2736
- if (open[1]) {
2737
- const reference = quotedReference(css, at - 1);
2738
- if (reference) references.push(reference);
2739
- continue;
2740
- }
2741
- if (!css.startsWith("animus-asset:", at)) continue;
2742
- const specifier = BARE_SPECIFIER_RE.exec(css.slice(at + 13))?.[0];
2743
- if (specifier === void 0) continue;
2744
- const end = at + 13 + specifier.length;
2745
- references.push({
2746
- start: at,
2747
- end,
2748
- specifier
2749
- });
2750
- }
2751
- for (const open of css.matchAll(IMAGE_SET_OPEN_RE)) {
2752
- const paren = (open.index ?? 0) + open[0].length - 1;
2753
- const close = closingParen(css, paren);
2754
- let depth = 0;
2755
- let candidateStart = true;
2756
- for (let i = paren + 1; i < close; i += 1) {
2757
- const c = css[i];
2758
- if (/\s/.test(c)) continue;
2759
- if (c === "\"" || c === "'") {
2760
- if (depth === 0 && candidateStart) {
2761
- const reference = quotedReference(css, i);
2762
- if (reference) references.push(reference);
2763
- }
2764
- const end = css.indexOf(c, i + 1);
2765
- i = end === -1 ? close : end;
2766
- } else if (c === "(") depth += 1;
2767
- else if (c === ")") depth -= 1;
2768
- else if (c === "," && depth === 0) {
2769
- candidateStart = true;
2770
- continue;
2771
- }
2772
- candidateStart = false;
2773
- }
2774
- }
2775
- return references.sort((a, b) => a.start - b.start);
2776
- }
2777
- function findAssetSpecifiers(css) {
2778
- return [...new Set(assetReferences(css).map((reference) => reference.specifier))];
2779
- }
2780
- function substituteAssetPlaceholders(css, urlBySpecifier) {
2781
- if (urlBySpecifier.size === 0) return css;
2782
- let out = css;
2783
- for (const reference of assetReferences(css).reverse()) {
2784
- const url = urlBySpecifier.get(reference.specifier);
2785
- if (url === void 0) continue;
2786
- out = out.slice(0, reference.start) + url + out.slice(reference.end);
2787
- }
2788
- return out;
2789
- }
2790
- /**
2791
- * Specifiers of placeholder text anywhere inside a `url()` or `image-set()`
2792
- * call, a reference or not: after substitution any of it is a broken load.
2793
- */
2794
- function placeholdersInAssetCalls(css) {
2795
- if (!css.includes("animus-asset:")) return [];
2796
- const calls = [...css.matchAll(URL_OPEN_RE), ...css.matchAll(IMAGE_SET_OPEN_RE)].map((open) => {
2797
- const paren = css.indexOf("(", open.index ?? 0);
2798
- return [paren, closingParen(css, paren)];
2799
- });
2800
- const found = /* @__PURE__ */ new Set();
2801
- for (let at = css.indexOf(ASSET_PLACEHOLDER_PREFIX); at !== -1; at = css.indexOf(ASSET_PLACEHOLDER_PREFIX, at + 1)) {
2802
- if (!calls.some(([open, close]) => open < at && at < close)) continue;
2803
- found.add(/^[^'")\s,]*/.exec(css.slice(at + 13))[0]);
2804
- }
2805
- return [...found];
2806
- }
2807
- function findSheetAssetSpecifiers(sheets) {
2808
- return [.../* @__PURE__ */ new Set([
2809
- ...findAssetSpecifiers(sheets.variableCss),
2810
- ...findAssetSpecifiers(sheets.globalCss),
2811
- ...findAssetSpecifiers(sheets.componentCss)
2812
- ])];
2813
- }
2814
- /** The one substitution step every emitted sheet passes through. */
2815
- function substituteSheetAssets(sheets, urlBySpecifier) {
2816
- return {
2817
- variableCss: substituteAssetPlaceholders(sheets.variableCss, urlBySpecifier),
2818
- globalCss: substituteAssetPlaceholders(sheets.globalCss, urlBySpecifier),
2819
- componentCss: substituteAssetPlaceholders(sheets.componentCss, urlBySpecifier)
2820
- };
2821
- }
2822
- const UNSUBSTITUTED_ASSET_CODE = "animus.asset.unsubstituted-placeholder";
2823
- /** A placeholder in emitted CSS is a broken URL in the browser: it fails a
2824
- * strict build and warns otherwise. */
2825
- function reportSurvivingAssetPlaceholders(css, report) {
2826
- const specifiers = placeholdersInAssetCalls(css);
2827
- if (specifiers.length === 0) return;
2828
- const message = `${report.prefix} asset() placeholders reached ${report.surface ?? "emitted CSS"} unsubstituted: ${specifiers.join(", ")} (${UNSUBSTITUTED_ASSET_CODE})`;
2829
- if (report.strict) throw new Error(message);
2830
- report.warn(message);
2831
- }
2832
- /**
2833
- * The runtime code an analysis generates: each dynamic prop config, which the
2834
- * shared prop map module carries, and each component's replacement. Extraction
2835
- * lifts asset() out of them into root variables, so none should hold one.
2836
- * Both embed values as JSON strings, so their quote and backslash escapes are
2837
- * undone for scanning: the text is for `findAssetSpecifiers`, not to run.
2838
- */
2839
- function generatedModuleCode(manifest) {
2840
- return [JSON.stringify(manifest.dynamic_props), ...Object.values(manifest.components).map((component) => component.replacement)].join("\n").replace(/\\(["\\])/g, "$1");
2841
- }
2842
- //#endregion
2843
3542
  //#region pipeline/resolve-asset.ts
2844
3543
  /**
2845
3544
  * Asset resolution for hosts with no bundler hook. Host path aliases apply
@@ -2888,7 +3587,7 @@ function resolveThroughPathAliases(specifier, rootDir, pathAliasesJson) {
2888
3587
  if (alias.pattern === specifier) mapped = alias.replacement;
2889
3588
  } else if (specifier.startsWith(alias.pattern)) mapped = alias.replacement + specifier.slice(alias.pattern.length);
2890
3589
  if (mapped === null) continue;
2891
- const absolute = isAbsolute(mapped) ? mapped : join$1(rootDir, mapped);
3590
+ const absolute = isAbsolute$1(mapped) ? mapped : join$1(rootDir, mapped);
2892
3591
  if (existsSync(absolute)) return absolute;
2893
3592
  }
2894
3593
  return null;
@@ -3007,8 +3706,13 @@ function enforceExternalTokenContracts(opts) {
3007
3706
  sourceTokens
3008
3707
  });
3009
3708
  if (messages.length === 0) return;
3010
- if (opts.strict) throw new Error(`${opts.prefix} ${messages.join("\n")}`);
3011
- for (const message of messages) opts.warn(message);
3709
+ if (!opts.strict) {
3710
+ for (const message of messages) opts.warn(message);
3711
+ return;
3712
+ }
3713
+ const message = `${opts.prefix} ${messages.join("\n")}`;
3714
+ if (!opts.reportErrors) throw new Error(message);
3715
+ opts.reportErrors(message);
3012
3716
  }
3013
3717
  //#endregion
3014
3718
  //#region pipeline/path-aliases.ts
@@ -3620,11 +4324,14 @@ function buildAnalysisInputs(opts) {
3620
4324
  externalDirsJson: opts.externalDirs?.length && hasSourceThemeManifests(opts.system) ? JSON.stringify(opts.externalDirs) : null
3621
4325
  };
3622
4326
  if (opts.system.declarationScalesJson) inputs.declarationScalesJson = opts.system.declarationScalesJson;
4327
+ if (opts.system.generatedNamesJson) inputs.generatedNamesJson = opts.system.generatedNamesJson;
3623
4328
  const context = opts.analysisContext;
3624
4329
  if (context?.skippedSources?.length || context?.unbundledComputedImports || context?.packageDirs?.length) inputs.analysisContextJson = JSON.stringify({
3625
4330
  skippedSources: context.skippedSources ?? [],
3626
4331
  unbundledComputedImports: context.unbundledComputedImports ?? false,
3627
- packageDirs: context.packageDirs ?? []
4332
+ packageDirs: context.packageDirs ?? [],
4333
+ linkedDirs: context.linkedDirs ?? [],
4334
+ outputDirs: context.outputDirs ?? []
3628
4335
  });
3629
4336
  return inputs;
3630
4337
  }
@@ -3640,9 +4347,12 @@ function hasSourceThemeManifests(system) {
3640
4347
  const surfacedSystems = /* @__PURE__ */ new WeakSet();
3641
4348
  /** The loaded system's own diagnostics, the first time it is analyzed. A
3642
4349
  * strict error keeps failing every analysis until the system changes. */
3643
- function systemDiagnostics(system, strict) {
4350
+ function systemDiagnostics(system, strict, levels) {
3644
4351
  const diagnostics = [...collectSelectorAliasDiagnostics(system.selectorAliasesJson), ...systemLoadDiagnostics(system)];
3645
- const blocking = strict === true && diagnostics.some((d) => d.severity === "error");
4352
+ const blocking = diagnostics.some((d) => effectiveLevel(d, {
4353
+ strict,
4354
+ levels
4355
+ }) === "error");
3646
4356
  if (surfacedSystems.has(system) && !blocking) return [];
3647
4357
  surfacedSystems.add(system);
3648
4358
  return diagnostics;
@@ -3652,7 +4362,7 @@ function systemDiagnostics(system, strict) {
3652
4362
  * the call site (strict-mode throw vs warn).
3653
4363
  */
3654
4364
  function runProjectAnalysis(engineApi, opts) {
3655
- const { analyzeProject } = engineApi();
4365
+ const { analyzeProject, diagnosticCodes } = engineApi();
3656
4366
  let t = performance.now();
3657
4367
  const inputs = buildAnalysisInputs(opts);
3658
4368
  const serializeMs = Math.round(performance.now() - t);
@@ -3662,7 +4372,12 @@ function runProjectAnalysis(engineApi, opts) {
3662
4372
  t = performance.now();
3663
4373
  const manifest = JSON.parse(manifestJson);
3664
4374
  const systemErrors = systemLoadDiagnostics(opts.system).filter((diagnostic) => diagnostic.kind === "error");
3665
- if (systemErrors.length > 0) manifest.diagnostics = [...systemErrors, ...manifest.diagnostics ?? []];
4375
+ const kitDiagnostics = kitDescriptorDiagnostics(opts.kitDescriptors ?? [], manifest);
4376
+ if (systemErrors.length > 0 || kitDiagnostics.length > 0) manifest.diagnostics = [
4377
+ ...systemErrors,
4378
+ ...kitDiagnostics,
4379
+ ...manifest.diagnostics ?? []
4380
+ ];
3666
4381
  const componentCss = applyUnitFallback(manifest.css);
3667
4382
  const propertyDiagnostics = checkCustomProperties({
3668
4383
  system: opts.system,
@@ -3673,8 +4388,11 @@ function runProjectAnalysis(engineApi, opts) {
3673
4388
  surfaceManifestDiagnostics(manifest, opts.warn, {
3674
4389
  strict: opts.strict,
3675
4390
  info: opts.info,
4391
+ reportErrors: opts.reportErrors,
4392
+ levels: opts.diagnostics,
4393
+ knownCodes: opts.diagnostics ? knownDiagnosticCodes(diagnosticCodes?.()) : void 0,
3676
4394
  prepend: [
3677
- ...systemDiagnostics(opts.system, opts.strict),
4395
+ ...systemDiagnostics(opts.system, opts.strict, opts.diagnostics),
3678
4396
  ...propertyDiagnostics,
3679
4397
  ...opts.extraDiagnostics ?? []
3680
4398
  ]
@@ -3778,36 +4496,34 @@ function renameCustomProperties(css, names) {
3778
4496
  }
3779
4497
  //#endregion
3780
4498
  //#region pipeline/prefix.ts
3781
- /** Prefix variable references without changing their matching rules. */
3782
- function prefixVariableReferences(prefix, value) {
3783
- if (!prefix) return value;
3784
- return value.replace(/var\(--([a-zA-Z][\w-]*)\)/g, `var(--${prefix}-$1)`);
3785
- }
3786
- function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson) {
4499
+ /**
4500
+ * The prefix without `prefixContextualVars`: every name Animus generates (a
4501
+ * variable the theme's map or variable CSS defines) takes `--${prefix}-`
4502
+ * wherever it is defined, registered or read, `var()` fallbacks at any depth
4503
+ * included. Every other name stays as written: each declared contextual
4504
+ * variable, and any custom property an author writes.
4505
+ */
4506
+ function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson, declarationScalesJson) {
3787
4507
  if (!prefix) return {
3788
4508
  variableMapJson,
3789
4509
  variableCss,
3790
4510
  themeJson,
3791
- contextualVarsJson
4511
+ contextualVarsJson,
4512
+ declarationScalesJson
3792
4513
  };
3793
- const map = JSON.parse(variableMapJson);
3794
- const prefixed = {};
3795
- for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
3796
- let css = variableCss;
3797
- css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
3798
- css = prefixVariableReferences(prefix, css);
3799
- css = css.replace(/@property(\s+)--([a-zA-Z][\w-]*)/g, `@property$1--${prefix}-$2`);
4514
+ const contextual = new Set(contextualVarsJson ? Object.values(parseInternalWire(contextualVarsJson, "contextualVarsJson (the theme's contextual variable names)")).flat() : []);
4515
+ const variableMap = parseInternalWire(variableMapJson, "variableMapJson (the theme's token variables)");
4516
+ const generated = [...Object.values(variableMap).map((name) => name.replace(/^--/, "")), ...themeDefinitions(variableCss)].filter((name) => !contextual.has(name));
4517
+ const names = createPropertyNames([], prefix, generated);
4518
+ const rename = (value) => renameCustomProperties(value, names);
3800
4519
  const result = {
3801
- variableMapJson: JSON.stringify(prefixed),
3802
- variableCss: css
4520
+ variableMapJson: JSON.stringify(Object.fromEntries(Object.entries(variableMap).map(([key, name]) => [key, rename(name)]))),
4521
+ variableCss: rename(variableCss)
3803
4522
  };
3804
- if (themeJson) result.themeJson = prefixVariableReferences(prefix, themeJson);
3805
- if (contextualVarsJson) {
3806
- const ctxVars = JSON.parse(contextualVarsJson);
3807
- const prefixedCtx = {};
3808
- for (const [scale, names] of Object.entries(ctxVars)) prefixedCtx[scale] = names.map((name) => `${prefix}-${name}`);
3809
- result.contextualVarsJson = JSON.stringify(prefixedCtx);
3810
- }
4523
+ if (themeJson) result.themeJson = renameTokenValues(themeJson, rename);
4524
+ if (contextualVarsJson) result.contextualVarsJson = contextualVarsJson;
4525
+ if (generated.length > 0) result.generatedNamesJson = JSON.stringify(Object.fromEntries(generated.map((name) => [name, `${prefix}-${name}`])));
4526
+ if (declarationScalesJson) result.declarationScalesJson = renameDeclarationRecords(declarationScalesJson, rename);
3811
4527
  return result;
3812
4528
  }
3813
4529
  /** The runtime's transport variables are `--animus-<prop>`. */
@@ -3823,7 +4539,8 @@ function applyPropertyNames(prefix, artifacts) {
3823
4539
  const byScale = artifacts.contextualVarsJson ? parseInternalWire(artifacts.contextualVarsJson, "contextualVarsJson (the theme's contextual variable names)") : null;
3824
4540
  const declared = Object.values(byScale ?? {}).flat();
3825
4541
  const variableMap = parseInternalWire(artifacts.variableMapJson, "variableMapJson (the theme's token variables)");
3826
- const managed = createPropertyNames(declared, prefix, [...Object.values(variableMap).map((name) => name.replace(/^--/, "")), ...[...artifacts.variableCss.matchAll(THEME_DEFINITION)].map((m) => m[1])]);
4542
+ const themeDefined = [...Object.values(variableMap).map((name) => name.replace(/^--/, "")), ...themeDefinitions(artifacts.variableCss)];
4543
+ const managed = createPropertyNames(declared, prefix, themeDefined);
3827
4544
  const nameConflicts = [...[...new Set(declared)].flatMap((name) => {
3828
4545
  const final = managed.finalName(name) ?? name;
3829
4546
  return final.startsWith(TRANSPORT_NAMESPACE) ? [{
@@ -3847,11 +4564,30 @@ function applyPropertyNames(prefix, artifacts) {
3847
4564
  }))]))),
3848
4565
  declarationScalesJson: artifacts.declarationScalesJson === null ? null : renameDeclarationRecords(artifacts.declarationScalesJson, rename),
3849
4566
  nameConflicts,
3850
- contextualProperties: [...new Set(declared)].map((name) => `--${managed.finalName(name) ?? name}`)
4567
+ contextualProperties: [...new Set(declared)].map((name) => `--${managed.finalName(name) ?? name}`),
4568
+ generatedNamesJson: generatedNamesJson(themeDefined.filter((name) => !declared.includes(name)), managed)
3851
4569
  };
3852
4570
  }
3853
- /** A custom property the theme's CSS defines or registers. */
3854
- const THEME_DEFINITION = /(?:@property\s+|(?:^|[\s;{]))--([\w-]+)(?=\s*[:{])/g;
4571
+ /** The generated names' final names, for authored component styles. */
4572
+ function generatedNamesJson(names, managed) {
4573
+ const entries = [...new Set(names)].flatMap((name) => {
4574
+ const final = managed.finalName(name);
4575
+ return final === void 0 ? [] : [[name, final]];
4576
+ });
4577
+ return entries.length === 0 ? null : JSON.stringify(Object.fromEntries(entries));
4578
+ }
4579
+ /** The custom properties the theme's CSS declares or `@property` registers,
4580
+ * read as CSS tokenizes it: a name in a string or comment defines nothing. */
4581
+ function themeDefinitions(variableCss) {
4582
+ const tokens = tokenize(variableCss).filter((token) => token.type !== "whitespace");
4583
+ return tokens.flatMap((token, index) => {
4584
+ if (token.type !== "ident" || !token.value.startsWith("--")) return [];
4585
+ const previous = tokens[index - 1];
4586
+ const declared = (previous === void 0 || previous.type === "{" || previous.type === ";") && tokens[index + 1]?.type === ":";
4587
+ const registered = previous?.type === "at-keyword" && previous.value.toLowerCase() === "property";
4588
+ return declared || registered ? [token.value.slice(2)] : [];
4589
+ });
4590
+ }
3855
4591
  /** Renames inside the token map's values. */
3856
4592
  function renameTokenValues(themeJson, rename) {
3857
4593
  const tokens = parseInternalWire(themeJson, "themeJson (the theme's flattened token values)");
@@ -3867,23 +4603,40 @@ function renameDeclarationRecords(declarationScalesJson, rename) {
3867
4603
  }
3868
4604
  //#endregion
3869
4605
  //#region pipeline/system-config.ts
4606
+ /** The loader's evaluation of the system file. A failure on a file that
4607
+ * calls `createSystem` with no binding leads with discovery's warning line
4608
+ * for it, then keeps the loader's own error. */
4609
+ function evaluateSystemModule(engine, systemPath, rootDir, conditions) {
4610
+ try {
4611
+ return engine.loadSystemModule(systemPath, rootDir, void 0, conditions);
4612
+ } catch (error) {
4613
+ let source;
4614
+ try {
4615
+ source = readFileSync(systemPath, "utf-8");
4616
+ } catch {
4617
+ throw error;
4618
+ }
4619
+ const record = engineModuleParser(engine)?.(source, systemPath);
4620
+ const call = record && unimportedCreateSystemCall(systemPath, record);
4621
+ if (!call) throw error;
4622
+ throw new Error(`${warnLine(call)}\n${String(error)}`, { cause: error });
4623
+ }
4624
+ }
3870
4625
  /**
3871
4626
  * Load and normalize a SystemInstance; `prefix` namespaces every CSS
3872
4627
  * variable name. Error handling stays at the call site.
3873
4628
  */
3874
4629
  function loadSystemConfig(engineApi, opts) {
3875
- const { loadSystemModule } = engineApi();
3876
- const config = loadSystemModule(opts.systemPath, opts.rootDir);
4630
+ const config = evaluateSystemModule(engineApi(), opts.systemPath, opts.rootDir, opts.conditions);
3877
4631
  let scalesJson = config.scalesJson;
3878
4632
  let variableMapJson = config.variableMapJson;
3879
4633
  const registrations = splitInvalidPropertyRegistrations(config.variableCss);
3880
4634
  let variableCss = registrations.css;
3881
4635
  let contextualVarsJson = config.contextualVarsJson || null;
3882
4636
  let declarationScalesJson = config.declarationScalesJson || null;
3883
- const declaredNames = Object.values(parseInternalWire(contextualVarsJson ?? "{}", "contextualVarsJson (the theme's contextual variable names)")).flat();
3884
- let contextualProperties = declaredNames.map((name) => `--${name}`);
3885
- let legacyPrefixedContextualVars = [];
4637
+ let contextualProperties = Object.values(parseInternalWire(contextualVarsJson ?? "{}", "contextualVarsJson (the theme's contextual variable names)")).flat().map((name) => `--${name}`);
3886
4638
  let prefixNameConflicts = [];
4639
+ let generatedNamesJson;
3887
4640
  if (opts.prefix && opts.prefixContextualVars) {
3888
4641
  const resolved = applyPropertyNames(opts.prefix, {
3889
4642
  variableMapJson,
@@ -3899,15 +4652,14 @@ function loadSystemConfig(engineApi, opts) {
3899
4652
  contextualProperties = resolved.contextualProperties;
3900
4653
  prefixNameConflicts = resolved.nameConflicts;
3901
4654
  declarationScalesJson = resolved.declarationScalesJson;
4655
+ generatedNamesJson = resolved.generatedNamesJson;
3902
4656
  } else if (opts.prefix) {
3903
- legacyPrefixedContextualVars = declaredNames;
3904
- contextualProperties = declaredNames.map((name) => `--${opts.prefix}-${name}`);
3905
- const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0);
4657
+ const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0, declarationScalesJson || void 0);
3906
4658
  variableMapJson = prefixed.variableMapJson;
3907
4659
  variableCss = prefixed.variableCss;
3908
4660
  if (prefixed.themeJson) scalesJson = prefixed.themeJson;
3909
- if (prefixed.contextualVarsJson) contextualVarsJson = prefixed.contextualVarsJson;
3910
- if (declarationScalesJson) declarationScalesJson = prefixVariableReferences(opts.prefix, declarationScalesJson);
4661
+ if (prefixed.declarationScalesJson) declarationScalesJson = prefixed.declarationScalesJson;
4662
+ generatedNamesJson = prefixed.generatedNamesJson;
3911
4663
  }
3912
4664
  const system = {
3913
4665
  propConfigJson: config.propConfig,
@@ -3931,7 +4683,7 @@ function loadSystemConfig(engineApi, opts) {
3931
4683
  if (config.propertyRecordsJson) system.propertyRecordsJson = config.propertyRecordsJson;
3932
4684
  if (registrations.invalid.length > 0) system.invalidPropertyRegistrations = registrations.invalid;
3933
4685
  if (prefixNameConflicts.length > 0) system.prefixNameConflicts = prefixNameConflicts;
3934
- if (legacyPrefixedContextualVars.length > 0) system.legacyPrefixedContextualVars = [...new Set(legacyPrefixedContextualVars)];
4686
+ if (generatedNamesJson) system.generatedNamesJson = generatedNamesJson;
3935
4687
  return system;
3936
4688
  }
3937
4689
  //#endregion
@@ -3940,8 +4692,7 @@ function runStructuralSelfCheck(input) {
3940
4692
  const failures = [];
3941
4693
  if (input.componentCount === 0) failures.push("No component CSS produced — check the system file and its includes list");
3942
4694
  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`);
3943
- for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "empty") failures.push(`include '${specifier}' resolved but discovered no component sources`);
3944
- else if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
4695
+ for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
3945
4696
  if ((input.variableMapJson == null || Object.keys(parseInternalWire(input.variableMapJson, "variableMapJson (the theme's token → variable map)")).length > 0) && !input.variableCss.includes(":root")) failures.push("No :root variable block found in variable CSS");
3946
4697
  if ((input.assembledCss ?? `${input.variableCss}\n${input.globalCss}\n${input.componentCss}`).includes("__TRANSFORM__")) failures.push("Unresolved __TRANSFORM__ placeholders found in CSS output");
3947
4698
  if (input.componentCount > 0 && (input.componentCss.length > 0 || input.assembledCss !== void 0)) {
@@ -3975,7 +4726,8 @@ function buildDynamicPropConfig(dynamicProps) {
3975
4726
  kind: meta.kind,
3976
4727
  slotClass: meta.slotClass,
3977
4728
  memberVars: meta.memberVars,
3978
- declarationScaleValues: meta.declarationScaleValues
4729
+ declarationScaleValues: meta.declarationScaleValues,
4730
+ ...meta.productionConditions && { productionConditions: meta.productionConditions }
3979
4731
  };
3980
4732
  continue;
3981
4733
  }
@@ -3993,6 +4745,7 @@ function buildDynamicPropConfig(dynamicProps) {
3993
4745
  if (meta.strict) entry.strict = true;
3994
4746
  if (meta.keywords?.length) entry.keywords = meta.keywords;
3995
4747
  if (meta.currentVar) entry.currentVar = meta.currentVar;
4748
+ if (meta.productionConditions) entry.productionConditions = meta.productionConditions;
3996
4749
  configEntries[propName] = entry;
3997
4750
  }
3998
4751
  return configEntries;
@@ -4031,6 +4784,191 @@ function assertNoErrorDiagnostics(diagnostics) {
4031
4784
  throw new Error(lines.join("\n"));
4032
4785
  }
4033
4786
  //#endregion
4787
+ //#region pipeline/kit-publication.ts
4788
+ /** Source a kit publishes and the loader or a bundler parses for imports. */
4789
+ const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
4790
+ ".ts",
4791
+ ".tsx",
4792
+ ".mts",
4793
+ ".cts",
4794
+ ".js",
4795
+ ".jsx",
4796
+ ".mjs",
4797
+ ".cjs"
4798
+ ]);
4799
+ const isFile = (path) => {
4800
+ try {
4801
+ return statSync$1(path).isFile();
4802
+ } catch {
4803
+ return false;
4804
+ }
4805
+ };
4806
+ const posixPath = (path) => path.split(sep).join("/");
4807
+ /**
4808
+ * The package-relative files `npm pack` publishes, read with npm's own rules
4809
+ * (`files`, `.npmignore`, `.gitignore` and the files npm always includes)
4810
+ * through a dry run that runs no scripts and needs no network.
4811
+ */
4812
+ function npmPublishedFiles(pkgRoot) {
4813
+ const windows = process.platform === "win32";
4814
+ const run = spawnSync(windows ? "npm.cmd" : "npm", [
4815
+ "pack",
4816
+ "--dry-run",
4817
+ "--json",
4818
+ "--ignore-scripts"
4819
+ ], {
4820
+ cwd: pkgRoot,
4821
+ encoding: "utf-8",
4822
+ shell: windows,
4823
+ env: {
4824
+ ...process.env,
4825
+ npm_config_update_notifier: "false"
4826
+ }
4827
+ });
4828
+ if (run.error || run.status !== 0) return {
4829
+ kind: "failed",
4830
+ reason: `npm pack --dry-run could not list the published files: ${String(run.error ?? run.stderr.trim())}`
4831
+ };
4832
+ let output;
4833
+ try {
4834
+ output = JSON.parse(run.stdout);
4835
+ } catch (error) {
4836
+ return {
4837
+ kind: "failed",
4838
+ reason: `npm pack --dry-run printed no JSON: ${String(error)}`
4839
+ };
4840
+ }
4841
+ const pack = Array.isArray(output) ? output[0] : void 0;
4842
+ const files = isJsonBlock(pack) ? pack.files : void 0;
4843
+ if (!Array.isArray(files)) return {
4844
+ kind: "failed",
4845
+ reason: "npm pack --dry-run printed no file list"
4846
+ };
4847
+ return {
4848
+ kind: "listed",
4849
+ files: new Set(files.flatMap((file) => isJsonBlock(file) && isJsonString(file.path) ? [posixPath(file.path)] : []))
4850
+ };
4851
+ }
4852
+ function animusTargets(manifest) {
4853
+ const targets = [];
4854
+ const unreadable = [];
4855
+ const visit = (subpath, value, underAnimus) => {
4856
+ if (isJsonString(value)) {
4857
+ if (underAnimus) targets.push([subpath, value]);
4858
+ } else if (Array.isArray(value)) for (const entry of value) visit(subpath, entry, underAnimus);
4859
+ else if (isJsonBlock(value)) for (const [key, entry] of Object.entries(value)) visit(subpath, entry, underAnimus || key === "animus");
4860
+ else if (underAnimus && value !== null) unreadable.push([subpath, JSON.stringify(value)]);
4861
+ };
4862
+ for (const [subpath, value] of exportsSubpaths(manifest)) visit(subpath, value, false);
4863
+ return {
4864
+ targets,
4865
+ unreadable
4866
+ };
4867
+ }
4868
+ /** Every file in the package outside `node_modules` and `.git`,
4869
+ * package-relative. */
4870
+ function packageFiles(pkgRoot, dir = "") {
4871
+ return readdirSync$1(join(pkgRoot, dir), { withFileTypes: true }).flatMap((entry) => {
4872
+ const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
4873
+ if (entry.isDirectory()) return entry.name === "node_modules" || entry.name === ".git" ? [] : packageFiles(pkgRoot, path);
4874
+ return entry.isFile() ? [path] : [];
4875
+ });
4876
+ }
4877
+ /** A `*` target pattern as a matcher of package-relative files. Node puts
4878
+ * one subpath into every `*`, so each later `*` repeats the first match. */
4879
+ function targetPattern(target) {
4880
+ const body = posixPath(target).replace(/^\.\//, "").split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).reduce((pattern, part, index) => index === 0 ? part : `${pattern}${index === 1 ? "(.+)" : "\\1"}${part}`);
4881
+ return new RegExp(`^${body}$`);
4882
+ }
4883
+ /**
4884
+ * Why the kit at `pkgRoot` would fail an installed consumer: each `animus`
4885
+ * source target, at any condition depth or as a pattern, and each file its
4886
+ * relative imports reach, must be in the package and among the files `npm
4887
+ * pack` publishes, and each bare package import a declared dependency.
4888
+ * Each line names the file, the import and the cause; none means the
4889
+ * published source is complete. With `artifactDir`, a build's artifact
4890
+ * directory inside the package that `npm pack` would publish is a line too.
4891
+ */
4892
+ function kitPublicationFailures(pkgRoot, engine, artifactDir) {
4893
+ let manifest;
4894
+ try {
4895
+ manifest = JSON.parse(readFileSync$1(join(pkgRoot, "package.json"), "utf-8"));
4896
+ } catch (error) {
4897
+ return [`package.json: cannot be read: ${String(error)}`];
4898
+ }
4899
+ const { targets, unreadable } = animusTargets(manifest);
4900
+ if (targets.length === 0 && unreadable.length === 0) return [];
4901
+ const { moduleSpecifiers } = engine;
4902
+ if (moduleSpecifiers === void 0) throw new Error("the native engine does not expose moduleSpecifiers, which the kit publication check needs — rebuild @animus-ui/extract");
4903
+ const publication = npmPublishedFiles(pkgRoot);
4904
+ if (publication.kind === "failed") return [`package.json: ${publication.reason}`];
4905
+ const published = (packageRelative) => publication.files.has(packageRelative);
4906
+ const declared = new Set([
4907
+ "dependencies",
4908
+ "peerDependencies",
4909
+ "optionalDependencies"
4910
+ ].flatMap((field) => {
4911
+ const block = isJsonBlock(manifest) ? manifest[field] : void 0;
4912
+ return isJsonBlock(block) ? Object.keys(block) : [];
4913
+ }));
4914
+ const ownName = isJsonBlock(manifest) && isJsonString(manifest.name) ? manifest.name : null;
4915
+ const failures = [];
4916
+ const rel = (file) => posixPath(relative(pkgRoot, file));
4917
+ const outside = (relPath) => relPath.startsWith("../") || isAbsolute(relPath);
4918
+ for (const [subpath, value] of unreadable) failures.push(`package.json: exports["${subpath}"] has an "animus" value, ${value}, that is no target path`);
4919
+ const queue = [];
4920
+ const admit = (subpath, relPath) => {
4921
+ if (published(relPath)) queue.push(resolve(pkgRoot, relPath));
4922
+ else failures.push(`package.json: the "animus" target of exports["${subpath}"], ${relPath}, is not in the published files`);
4923
+ };
4924
+ let files = null;
4925
+ for (const [subpath, target] of targets) {
4926
+ const relPath = rel(resolve(pkgRoot, target));
4927
+ if (outside(relPath)) failures.push(`package.json: the "animus" target of exports["${subpath}"], ${target}, is outside the package`);
4928
+ else if (target.includes("*")) {
4929
+ files ??= packageFiles(pkgRoot);
4930
+ const pattern = targetPattern(target);
4931
+ const matched = files.filter((file) => pattern.test(file));
4932
+ if (matched.length === 0) failures.push(`package.json: the "animus" target of exports["${subpath}"], ${target}, matches no file in the package`);
4933
+ for (const file of matched) admit(subpath, file);
4934
+ } else if (!isFile(resolve(pkgRoot, target))) failures.push(`package.json: the "animus" target of exports["${subpath}"], ${target}, does not exist in the package`);
4935
+ else admit(subpath, relPath);
4936
+ }
4937
+ const visited = /* @__PURE__ */ new Set();
4938
+ while (queue.length > 0) {
4939
+ const file = queue.shift();
4940
+ if (visited.has(file)) continue;
4941
+ visited.add(file);
4942
+ const specifiers = parseInternalWire(moduleSpecifiers(JSON.stringify([{
4943
+ path: file,
4944
+ source: readFileSync$1(file, "utf-8")
4945
+ }])), "moduleSpecifiers").files[file] ?? [];
4946
+ for (const specifier of specifiers) {
4947
+ if (specifier.startsWith(".")) {
4948
+ const target = relativeSourceCandidates(file, specifier).find(isFile);
4949
+ if (target === void 0) failures.push(`${rel(file)}: import '${specifier}' resolves to no file in the package`);
4950
+ else if (outside(rel(target))) failures.push(`${rel(file)}: import '${specifier}' resolves to ${rel(target)}, outside the package`);
4951
+ else if (!published(rel(target))) failures.push(`${rel(file)}: import '${specifier}' resolves to ${rel(target)}, which is not in the published files`);
4952
+ else if (CODE_EXTENSIONS.has(extname(target))) queue.push(target);
4953
+ continue;
4954
+ }
4955
+ if (specifier.startsWith("/") || /^[a-z][\w+.-]*:/i.test(specifier)) {
4956
+ if (!isBuiltin(specifier)) failures.push(`${rel(file)}: import '${specifier}' is not a package or relative import, so an installed consumer cannot resolve it`);
4957
+ continue;
4958
+ }
4959
+ const name = bareSpecifierPackageName(specifier);
4960
+ if (isBuiltin(name) || name === ownName || declared.has(name)) continue;
4961
+ failures.push(`${rel(file)}: import '${specifier}' names the package ${name}, which package.json does not declare in dependencies, peerDependencies or optionalDependencies`);
4962
+ }
4963
+ }
4964
+ const artifacts = artifactDir === void 0 ? "" : rel(artifactDir);
4965
+ if (artifacts !== "" && !outside(artifacts)) {
4966
+ const shipped = [...publication.files].filter((file) => file.startsWith(`${artifacts}/`));
4967
+ if (shipped.length > 0) failures.push(`${artifacts}/: npm pack would publish this build-artifact directory (${shipped.length} files) — add "${artifacts}/" to .npmignore or .gitignore, or list the published files in package.json "files"`);
4968
+ }
4969
+ return failures;
4970
+ }
4971
+ //#endregion
4034
4972
  //#region pipeline/replacement-plans.ts
4035
4973
  const snapshotByManifest = /* @__PURE__ */ new WeakMap();
4036
4974
  /** `null` (a context before its first analysis) snapshots to the empty plan
@@ -4107,4 +5045,4 @@ function realpathThroughNearestExistingAncestor(path) {
4107
5045
  }
4108
5046
  }
4109
5047
  //#endregion
4110
- export { DRIVER_NAMESPACES as $, createSourceCorpus as A, contentHash as At, unresolvableIncludesMessage as B, resolveThroughPathAliases as C, noKitFilesDiagnostics as Ct, reportSurvivingAssetPlaceholders as D, unresolvedParentName as Dt, generatedModuleCode as E, unreadableSourceDiagnostic as Et, findPackageRoot as F, validateLayerOrder as Ft, createSourceIdentity as G, isJsonBlock as H, firstOwners as I, __require as It, compareDiscoveryOrder as J, isPathWithinRoot as K, isExcludedPackageRelativePath as L, engineImportParser as M, ANIMUS_LAYERS as Mt, excludeCollectedPackages as N, assembleStylesheet as Nt, substituteAssetPlaceholders as O, vocabularyWitnessDiagnostics as Ot, extractSystemFilePackages as P, stripLeadingLayerDeclaration as Pt, DEFAULT_EXCLUDE as Q, resolveAbsolutePathSpecifier as R, resolveAssetFile as S, isUnresolvedParentDrop as St, findSheetAssetSpecifiers as T, systemLoadDiagnostics as Tt, isJsonString as U, walkPackageSources as V, readTsconfigAliasPairs as W, AnimusConfigError as X, discoverFiles as Y, CORE_OPTION_KEYS as Z, applyUnitFallback as _, preprocessMdx as _t, assertNoErrorDiagnostics as a, resolveVerbosity as at, buildPathAliasesJson as b, VOCABULARY_LEGACY_VERB as bt, runStructuralSelfCheck as c, createV2EngineApi as ct, serializeStaticCss as d, parseFilesJson as dt, REPLACEABLE_DEFAULT_EXCLUDE as et, camelToKebab as f, projectExternalFileOwners as ft, runProjectAnalysis as g, isEngineTransformExtension as gt, clearEngineCache as h, ENGINE_TRANSFORM_EXTENSIONS as ht, snapshotFilePlans as i, resolveMode as it, collectExternalPackageSources as j, buildAnalyzeProjectArgs as jt, substituteSheetAssets as k, parseInternalWire as kt, loadSystemConfig as l, ingestSourceEntries as lt, buildAnalysisInputs as m, DEFAULT_EXTENSIONS as mt, diffFilePlans as n, assertKnownOptionKeys as nt, buildSystemPropsModule as o, RETIRED_ENGINE_MESSAGE as ot, stableStringify as p, adaptSvelteSource as pt, sharesVolumeRoot as q, hashReplacementPlans as r, createExcludeMatcher as rt, buildDynamicPropConfig as s, assertNoRetiredEngineSelection as st, toWatchKeys as t, STRUCTURAL_EXCLUDE as tt, applyPrefix as u, isAdvisorySourceDiagnostic as ut, postProcessCss as v, INVALID_PROPERTY_REGISTRATION as vt, findAssetSpecifiers as w, surfaceManifestDiagnostics as wt, enforceExternalTokenContracts as x, isDeletedSource as xt, resolveLightningTargets as y, VOCABULARY_COLLISION as yt, staleDistIncludesMessage as z };
5048
+ export { discoverFiles as $, fileSideEffects as A, surfaceManifestDiagnostics as At, unresolvableIncludesMessage as B, substituteSheetAssets as Bt, resolveAssetFile as C, DiagnosticFailure as Ct, engineModuleParser as D, isDeletedSource as Dt, collectExternalPackageSources as E, VOCABULARY_LEGACY_VERB as Et, keepsKitSourceEffects as F, findAssetSpecifiers as Ft, KIT_DESCRIPTOR_FILE as G, assembleStylesheet as Gt, createSourceIdentity as H, contentHash as Ht, kitSourceModuleSideEffects as I, findSheetAssetSpecifiers as It, readKitDescriptor as J, __require as Jt, buildKitDescriptor as K, stripLeadingLayerDeclaration as Kt, resolveAbsolutePathSpecifier as L, generatedModuleCode as Lt, firstOwners as M, unreadableSourceDiagnostic as Mt, importedKitDiagnostics as N, unresolvedParentName as Nt, excludeCollectedPackages as O, isUnresolvedParentDrop as Ot, isExcludedPackageRelativePath as P, vocabularyWitnessDiagnostics as Pt, compareDiscoveryOrder as Q, sourceKitDependencies as R, reportSurvivingAssetPlaceholders as Rt, enforceExternalTokenContracts as S, DIAGNOSTIC_LEVELS as St, createSourceCorpus as T, VOCABULARY_COLLISION as Tt, isPathWithinRoot as U, buildAnalyzeProjectArgs as Ut, walkPackageSources as V, parseInternalWire as Vt, sharesVolumeRoot as W, ANIMUS_LAYERS as Wt, isJsonString as X, isJsonBlock as Y, readTsconfigAliasPairs as Z, runProjectAnalysis as _, adaptSvelteSource as _t, kitPublicationFailures as a, STRUCTURAL_EXCLUDE as at, resolveLightningTargets as b, isEngineTransformExtension as bt, buildDynamicPropConfig as c, resolveMode as ct, applyPrefix as d, assertNoRetiredEngineSelection as dt, AnimusConfigError as et, serializeStaticCss as f, createV2EngineApi as ft, clearEngineCache as g, projectExternalFileOwners as gt, buildAnalysisInputs as h, parseFilesJson as ht, snapshotFilePlans as i, REPLACEABLE_DEFAULT_EXCLUDE as it, findPackageRoot as j, systemLoadDiagnostics as jt, extractSystemFilePackages as k, noKitFilesDiagnostics as kt, runStructuralSelfCheck as l, resolveVerbosity as lt, stableStringify as m, isAdvisorySourceDiagnostic as mt, diffFilePlans as n, DEFAULT_EXCLUDE as nt, assertNoErrorDiagnostics as o, assertKnownOptionKeys as ot, camelToKebab as p, ingestSourceEntries as pt, kitDescriptorDiagnostics as q, validateLayerOrder as qt, hashReplacementPlans as r, DRIVER_NAMESPACES as rt, buildSystemPropsModule as s, createExcludeMatcher as st, toWatchKeys as t, CORE_OPTION_KEYS as tt, loadSystemConfig as u, RETIRED_ENGINE_MESSAGE as ut, applyUnitFallback as v, DEFAULT_EXTENSIONS as vt, resolveThroughPathAliases as w, INVALID_PROPERTY_REGISTRATION as wt, buildPathAliasesJson as x, preprocessMdx as xt, postProcessCss as y, ENGINE_TRANSFORM_EXTENSIONS as yt, staleDistIncludesMessage as z, substituteAssetPlaceholders as zt };