@animus-ui/extract 0.1.23 → 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 +11 -1
  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 +13 -7
  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 +89 -0
  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 +3 -1
  20. package/dist/pipeline/engine-adapter.d.ts.map +1 -1
  21. package/dist/pipeline/index.d.ts +5 -2
  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 +32 -7
  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 +13 -2
  34. package/dist/pipeline/run-analysis.d.ts.map +1 -1
  35. package/dist/pipeline/source-ingestion.d.ts +9 -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-kA3VEMgt.mjs → pipeline-Bl20tbu_.mjs} +994 -321
  41. package/dist/{pipeline-B3JzXiSY.cjs → pipeline-CAH9mxnK.cjs} +1036 -302
  42. package/dist/session/extraction-session.d.ts +17 -4
  43. package/dist/session/extraction-session.d.ts.map +1 -1
  44. package/dist/session.cjs +54 -19
  45. package/dist/session.mjs +54 -19
  46. package/package.json +4 -4
@@ -31,6 +31,8 @@ let browserslist = require("browserslist");
31
31
  browserslist = __toESM(browserslist);
32
32
  let lightningcss = require("lightningcss");
33
33
  let _animus_ui_properties = require("@animus-ui/properties");
34
+ let node_child_process = require("node:child_process");
35
+ let node_module = require("node:module");
34
36
  //#region pipeline/assemble-stylesheet.ts
35
37
  /**
36
38
  * The cascade layer order, outermost first: its one definition. The engine
@@ -110,8 +112,13 @@ function buildAnalyzeProjectArgs(inputs) {
110
112
  inputs.transformSourcesJson,
111
113
  inputs.transformProvenanceJson ?? null
112
114
  ];
113
- if (inputs.declarationScalesJson || inputs.analysisContextJson) args.push(inputs.declarationScalesJson);
114
- if (inputs.analysisContextJson) args.push(inputs.analysisContextJson);
115
+ const tail = [
116
+ inputs.declarationScalesJson,
117
+ inputs.analysisContextJson,
118
+ inputs.generatedNamesJson
119
+ ];
120
+ const last = tail.findLastIndex((slot) => slot !== void 0 && slot !== "");
121
+ for (const slot of tail.slice(0, last + 1)) args.push(slot || void 0);
115
122
  return args;
116
123
  }
117
124
  //#endregion
@@ -257,13 +264,17 @@ function substituteSheetAssets(sheets, urlBySpecifier) {
257
264
  }
258
265
  const UNSUBSTITUTED_ASSET_CODE = "animus.asset.unsubstituted-placeholder";
259
266
  /** A placeholder in emitted CSS is a broken URL in the browser: it fails a
260
- * strict build and warns otherwise. */
267
+ * strict build, strict development reports it, and it warns otherwise. */
261
268
  function reportSurvivingAssetPlaceholders(css, report) {
262
269
  const specifiers = placeholdersInAssetCalls(css);
263
270
  if (specifiers.length === 0) return;
264
271
  const message = `${report.prefix} asset() placeholders reached ${report.surface ?? "emitted CSS"} unsubstituted: ${specifiers.join(", ")} (${UNSUBSTITUTED_ASSET_CODE})`;
265
- if (report.strict) throw new Error(message);
266
- report.warn(message);
272
+ if (!report.strict) {
273
+ report.warn(message);
274
+ return;
275
+ }
276
+ if (!report.reportErrors) throw new Error(message);
277
+ report.reportErrors(message);
267
278
  }
268
279
  /**
269
280
  * The runtime code an analysis generates: each dynamic prop config, which the
@@ -292,6 +303,15 @@ function locatedPrefix(diagnostic) {
292
303
  function droppedSuffix(diagnostic) {
293
304
  return diagnostic.dropped && !diagnostic.message.includes(diagnostic.dropped) ? ` — dropped: ${diagnostic.dropped}` : "";
294
305
  }
306
+ /** The record's code, when its message does not already quote it. */
307
+ function codeSuffix(diagnostic) {
308
+ return diagnostic.code && !diagnostic.message.includes(diagnostic.code) ? ` [${diagnostic.code}]` : "";
309
+ }
310
+ /** A warn-kind record's line after its mark:
311
+ * `file:line:column: component: message [code]`. */
312
+ function warnLine(diagnostic) {
313
+ return `${locationOf(diagnostic)}: ${diagnostic.component}: ${diagnostic.message}${droppedSuffix(diagnostic)}${codeSuffix(diagnostic)}`;
314
+ }
295
315
  /** Stable code for selector forms with no substitutable subject; the
296
316
  * extraction engine mints the same string. */
297
317
  const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
@@ -310,10 +330,19 @@ const DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set([
310
330
  "warn",
311
331
  "error"
312
332
  ]);
333
+ /** A key that selects every record of a kind: `kind:bail`, `kind:skip`,
334
+ * `kind:warn` or `kind:error`. */
335
+ const KIND_KEY = "kind:";
336
+ const DIAGNOSTIC_KINDS = /* @__PURE__ */ new Set([
337
+ "bail",
338
+ "skip",
339
+ "warn",
340
+ "error"
341
+ ]);
313
342
  /** The level `levels` sets for `code`: its exact entry, else the entry of
314
343
  * the longest prefix it starts with. */
315
- function levelFor(code, levels) {
316
- if (code === void 0 || levels === void 0) return void 0;
344
+ function codeLevel(code, levels) {
345
+ if (code === void 0) return void 0;
317
346
  if (Object.hasOwn(levels, code)) return levels[code];
318
347
  let longest;
319
348
  for (const [key, level] of Object.entries(levels)) {
@@ -326,11 +355,19 @@ function levelFor(code, levels) {
326
355
  }
327
356
  return longest?.level;
328
357
  }
358
+ /** The level `levels` sets for a record: its code's level, else its kind's. */
359
+ function levelFor({ code, kind }, levels) {
360
+ if (levels === void 0) return void 0;
361
+ const kindKey = `${KIND_KEY}${kind}`;
362
+ return codeLevel(code, levels) ?? (Object.hasOwn(levels, kindKey) ? levels[kindKey] : void 0);
363
+ }
329
364
  /** The level a record prints at: the option's entry for its code, else
330
- * `error` for an error-severity record under `strict`, else its own. */
365
+ * `error` for a hard error (an `error`-kind record) or an error-severity
366
+ * record under `strict`, else its own. */
331
367
  function effectiveLevel(diagnostic, policy) {
332
- const level = levelFor(diagnostic.code, policy.levels);
368
+ const level = levelFor(diagnostic, policy.levels);
333
369
  if (level !== void 0) return level;
370
+ if (diagnostic.kind === "error") return "error";
334
371
  if (diagnostic.severity === "info") return "info";
335
372
  return policy.strict && diagnostic.severity === "error" ? "error" : "warn";
336
373
  }
@@ -349,6 +386,7 @@ function knownDiagnosticCodes(engineCodesJson) {
349
386
  /** The keys of `levels` that name no known code, exactly or as a prefix. */
350
387
  function unknownDiagnosticKeys(levels, known) {
351
388
  return Object.keys(levels).filter((key) => {
389
+ if (key.startsWith(KIND_KEY)) return !DIAGNOSTIC_KINDS.has(key.slice(5));
352
390
  if (!key.endsWith(".*")) return !known.has(key);
353
391
  const prefix = key.slice(0, -1);
354
392
  return ![...known].some((code) => code.startsWith(prefix));
@@ -403,9 +441,22 @@ function collectSelectorAliasDiagnostics(selectorAliasesJson) {
403
441
  const UNREADABLE_SOURCE_FILE = "animus.ingestion.unreadable-source-file";
404
442
  /** A kit the system extends resolved, yet discovery found none of its files. */
405
443
  const NO_KIT_FILES = "animus.discovery.no-kit-files";
444
+ /** A kit with no `animus` export condition: discovery guesses its source
445
+ * from `src/`. */
446
+ const KIT_WITHOUT_SOURCE_CONDITION = "animus.discovery.kit-without-source-condition";
447
+ /** A kit's `animus` export condition names a file that is missing or outside
448
+ * its package, so that entry is not read from source. */
449
+ const INVALID_KIT_SOURCE_CONDITION = "animus.discovery.invalid-source-condition";
450
+ /** A kit the application imports whose system the application's system
451
+ * does not include: its components are not extracted. */
452
+ const KIT_SYSTEM_NOT_INCLUDED = "animus.kit.system-not-included";
406
453
  /** A system file's `createSystem` binding that discovery could not follow to
407
454
  * Animus's factory, so it is no root. */
408
455
  const UNPROVEN_ROOT_BINDING = "animus.discovery.unproven-root-binding";
456
+ /** A system file calls `createSystem` with no import or local binding. The
457
+ * system loader evaluates the file without auto-imports, where the name is
458
+ * undefined, so the call is no root. */
459
+ const UNIMPORTED_CREATE_SYSTEM = "animus.discovery.unimported-create-system";
409
460
  /** What a skipped source file costs: the analysis never sees what it
410
461
  * renders. */
411
462
  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";
@@ -441,6 +492,11 @@ function noKitFilesDiagnostics(outcomes) {
441
492
  severity: severityFor(NO_KIT_FILES)
442
493
  }));
443
494
  }
495
+ /** A kit's descriptor declares a format this Animus does not read. */
496
+ const KIT_UNSUPPORTED_FORMAT = "animus.kit.unsupported-format";
497
+ /** A kit's descriptor records a definition its shipped source no longer
498
+ * declares. */
499
+ const KIT_STALE_DESCRIPTOR = "animus.kit.stale-descriptor";
444
500
  /** The collision entry code minted by the system package's merge. */
445
501
  const VOCABULARY_COLLISION = "animus.vocabulary.collision";
446
502
  /** A theme `@property` registration browsers would ignore; it is not emitted. */
@@ -466,9 +522,6 @@ const PROPERTY_FALLBACK_SELF_REFERENCE = "animus.property.fallback-self-referenc
466
522
  * another directly, so every property in the cycle is invalid at
467
523
  * computed-value time. */
468
524
  const PROPERTY_CURRENT_VAR_CYCLE = "animus.property.current-var-cycle";
469
- /** A prefix renamed contextual variables without `prefixContextualVars`, so
470
- * their declared names no longer resolve. */
471
- const PREFIX_CONTEXTUAL_VARS_UNPREFIXED = "animus.prefix.contextual-vars-unprefixed";
472
525
  /** Under `prefixContextualVars`, a final name that collides with a runtime
473
526
  * transport variable or a theme variable of the same spelling. */
474
527
  const PREFIX_NAME_CONFLICT = "animus.prefix.name-conflict";
@@ -482,7 +535,13 @@ const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
482
535
  [SELECTOR_UNSUPPORTED_SUBJECT, "error"],
483
536
  [UNREADABLE_SOURCE_FILE, "error"],
484
537
  [NO_KIT_FILES, "warn"],
538
+ [KIT_WITHOUT_SOURCE_CONDITION, "warn"],
539
+ [INVALID_KIT_SOURCE_CONDITION, "error"],
540
+ [KIT_SYSTEM_NOT_INCLUDED, "error"],
541
+ [KIT_UNSUPPORTED_FORMAT, "error"],
542
+ [KIT_STALE_DESCRIPTOR, "warn"],
485
543
  [UNPROVEN_ROOT_BINDING, "warn"],
544
+ [UNIMPORTED_CREATE_SYSTEM, "warn"],
486
545
  [VOCABULARY_COLLISION, "warn"],
487
546
  [VOCABULARY_LEGACY_VERB, "warn"],
488
547
  [INVALID_PROPERTY_REGISTRATION, "error"],
@@ -492,7 +551,6 @@ const DIAGNOSTIC_SEVERITY = /* @__PURE__ */ new Map([
492
551
  [PROPERTY_SELF_REFERENCE, "warn"],
493
552
  [PROPERTY_FALLBACK_SELF_REFERENCE, "warn"],
494
553
  [PROPERTY_CURRENT_VAR_CYCLE, "warn"],
495
- [PREFIX_CONTEXTUAL_VARS_UNPREFIXED, "warn"],
496
554
  [PREFIX_NAME_CONFLICT, "error"],
497
555
  [PROPERTY_LEGACY_TOKEN_COLLISION, "warn"],
498
556
  [PROPERTY_TOKEN_COLLISION, "error"]
@@ -547,15 +605,6 @@ function systemLoadDiagnostics(system) {
547
605
  code: INVALID_PROPERTY_REGISTRATION,
548
606
  severity: severityFor(INVALID_PROPERTY_REGISTRATION)
549
607
  }));
550
- const prefixed = system.legacyPrefixedContextualVars ?? [];
551
- const unprefixed = prefixed.length === 0 ? [] : [{
552
- file: "system",
553
- component: "prefix",
554
- kind: "warn",
555
- 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})`,
556
- code: PREFIX_CONTEXTUAL_VARS_UNPREFIXED,
557
- severity: severityFor(PREFIX_CONTEXTUAL_VARS_UNPREFIXED)
558
- }];
559
608
  const conflicts = (system.prefixNameConflicts ?? []).map(({ name, final, reason }) => ({
560
609
  file: "system",
561
610
  component: `--${name}`,
@@ -567,7 +616,6 @@ function systemLoadDiagnostics(system) {
567
616
  return [
568
617
  ...vocabularyWitnessDiagnostics(system.vocabularyWitnessesJson),
569
618
  ...registrations,
570
- ...unprefixed,
571
619
  ...conflicts,
572
620
  ...tokenCollisionDiagnostics(system)
573
621
  ];
@@ -603,13 +651,14 @@ function tokenCollisionDiagnostics(system) {
603
651
  }
604
652
  /**
605
653
  * The single strict-escalation policy point for both extraction plugins:
606
- * error-severity diagnostics throw together under `strict`, else warn.
654
+ * error-severity diagnostics throw together under `strict`, else warn. A
655
+ * development session reports them through `reportErrors` instead.
607
656
  */
608
657
  function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
609
- const errors = [];
658
+ const errors = /* @__PURE__ */ new Set();
610
659
  if (policy.levels && policy.knownCodes && !checkedLevels.has(policy.levels)) {
611
660
  checkedLevels.add(policy.levels);
612
- for (const key of unknownDiagnosticKeys(policy.levels, policy.knownCodes)) warn(`⚠ 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)`);
661
+ 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)`);
613
662
  }
614
663
  const diagnostics = policy.prepend?.length ? [...policy.prepend, ...manifest.diagnostics ?? []] : manifest.diagnostics ?? [];
615
664
  for (const diagnostic of diagnostics) {
@@ -618,22 +667,28 @@ function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
618
667
  let line = null;
619
668
  const mark = level === "info" ? "ℹ" : "⚠";
620
669
  const message = `${diagnostic.message}${droppedSuffix(diagnostic)}`;
621
- if (diagnostic.kind === "bail") line = `${mark} ${locatedPrefix(diagnostic)}${diagnostic.component} not extracted: ${message}`;
622
- else if (diagnostic.kind === "skip") line = `${mark} ${locatedPrefix(diagnostic)}${diagnostic.component}: skipped ${message}`;
623
- else if (diagnostic.kind === "warn") line = `${mark} ${locationOf(diagnostic)}: ${diagnostic.component}: ${message}`;
670
+ if (diagnostic.kind === "bail") line = `${mark} ${locatedPrefix(diagnostic)}${diagnostic.component} not extracted: ${message}${codeSuffix(diagnostic)}`;
671
+ else if (diagnostic.kind === "skip") line = `${mark} ${locatedPrefix(diagnostic)}${diagnostic.component}: skipped ${message}${codeSuffix(diagnostic)}`;
672
+ else if (diagnostic.kind === "warn" || diagnostic.kind === "error") line = `${mark} ${warnLine(diagnostic)}`;
624
673
  if (line === null) continue;
625
- if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
626
674
  if (level === "info") {
627
675
  policy.info?.(line);
628
676
  continue;
629
677
  }
630
678
  if (level === "error") {
631
- errors.push(`${diagnostic.code ?? "error"} — ${locatedPrefix(diagnostic)}${diagnostic.component}: ${message}`);
679
+ const subject = diagnostic.kind === "warn" || diagnostic.kind === "error" ? `${locationOf(diagnostic)}: ` : locatedPrefix(diagnostic);
680
+ errors.add(`${diagnostic.code ?? "error"} — ${subject}${diagnostic.component}: ${message}`);
632
681
  continue;
633
682
  }
634
683
  warn(line);
635
684
  }
636
- if (errors.length > 0) throw new DiagnosticFailure(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
685
+ if (errors.size === 0) return;
686
+ const message = `[animus] strict: ${errors.size} error diagnostic(s):\n${[...errors].join("\n")}`;
687
+ if (policy.reportErrors) {
688
+ policy.reportErrors(message);
689
+ return;
690
+ }
691
+ throw new DiagnosticFailure(message);
637
692
  }
638
693
  //#endregion
639
694
  //#region pipeline/mdx-preprocessor.ts
@@ -1600,7 +1655,8 @@ function createSourceIngestor(host) {
1600
1655
  const fatal = diagnostics.filter((diagnostic) => !isAdvisorySourceDiagnostic(diagnostic));
1601
1656
  const invalidOriginals = new Set(fatal.map((diagnostic) => diagnostic.originalPath));
1602
1657
  if (diagnostics.length === 0) return invalidOriginals;
1603
- if (host.strict() && fatal.length > 0) {
1658
+ const reportError = host.strict() ? host.reportErrors?.() : void 0;
1659
+ if (host.strict() && fatal.length > 0 && !reportError) {
1604
1660
  const lines = fatal.map((diagnostic) => `${diagnostic.code} ${diagnostic.originalPath}: ${diagnostic.message} (${SKIPPED_SOURCE_COST})`);
1605
1661
  throw new Error(`${host.prefix} ${lines.join(`\n${host.prefix} `)}`);
1606
1662
  }
@@ -1614,7 +1670,8 @@ function createSourceIngestor(host) {
1614
1670
  }
1615
1671
  if (warned.has(line)) continue;
1616
1672
  warned.add(line);
1617
- host.warn(`${host.prefix} ${line}`);
1673
+ if (reportError && skipped) reportError(`${host.prefix} ${line}`);
1674
+ else host.warn(`${host.prefix} ${line}`);
1618
1675
  }
1619
1676
  return invalidOriginals;
1620
1677
  },
@@ -1641,8 +1698,9 @@ function createV2EngineApi(deps) {
1641
1698
  return {
1642
1699
  loadSystemModule: (...args) => native.loadSystemModule(...args),
1643
1700
  extractFacts: (filesJson) => native.extractFacts(filesJson),
1701
+ moduleSpecifiers: (filesJson) => native.moduleSpecifiers(filesJson),
1644
1702
  diagnosticCodes: () => native.diagnosticCodes(),
1645
- analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson, transformSourcesJson, transformProvenanceJson, declarationScalesJson, analysisContextJson) => {
1703
+ analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson, transformSourcesJson, transformProvenanceJson, declarationScalesJson, analysisContextJson, generatedNamesJson) => {
1646
1704
  const filesJson = deps.rehydrateFilesJson ? deps.rehydrateFilesJson(filesJsonRaw) : filesJsonRaw;
1647
1705
  const sent = /* @__PURE__ */ new Map();
1648
1706
  for (const entry of parseFilesJson(filesJson, label)) sent.set(entry.path, entry.source);
@@ -1670,6 +1728,7 @@ function createV2EngineApi(deps) {
1670
1728
  transformProvenanceJson: transformProvenanceJson ?? void 0,
1671
1729
  declarationScalesJson,
1672
1730
  analysisContextJson,
1731
+ generatedNamesJson,
1673
1732
  devMode
1674
1733
  };
1675
1734
  const engine = new native.ExtractEngine(config);
@@ -2005,114 +2064,6 @@ function walkFiles(dir, rootDir, exclude, extensionsSet) {
2005
2064
  return results;
2006
2065
  }
2007
2066
  //#endregion
2008
- //#region pipeline/source-identity.ts
2009
- /**
2010
- * One derivation of source identity for discovery, watch ingestion, ownership
2011
- * and deletion: event paths are lookup INPUTS only and never fork identity.
2012
- */
2013
- /**
2014
- * True when `target` is `root` or a descendant, decided by `relative()`
2015
- * shape: string prefixing would let `/ui` claim `/ui-old`.
2016
- */
2017
- function isPathWithinRoot(root, target, pathApi = node_path.default) {
2018
- const rel = pathApi.relative(root, target);
2019
- return rel === "" || !pathApi.isAbsolute(rel) && rel !== ".." && !rel.startsWith(".." + pathApi.sep);
2020
- }
2021
- /**
2022
- * True when two paths share a platform root (win32 drive letter or UNC
2023
- * share; always true on posix). Drive letters compare case-insensitively.
2024
- */
2025
- function sharesVolumeRoot(a, b, pathApi = node_path.default) {
2026
- const volumeOf = (p) => pathApi.parse(pathApi.resolve(p)).root.toLowerCase();
2027
- return volumeOf(a) === volumeOf(b);
2028
- }
2029
- function canonicalize(p) {
2030
- try {
2031
- return (0, node_fs.realpathSync)(p);
2032
- } catch {
2033
- return null;
2034
- }
2035
- }
2036
- function createSourceIdentity(rootDir) {
2037
- const normalizedRootDir = node_path.default.normalize(rootDir);
2038
- const canonicalRootDir = canonicalize(normalizedRootDir) ?? normalizedRootDir;
2039
- const roots = [];
2040
- const aliases = /* @__PURE__ */ new Map();
2041
- const owningRecord = (canonicalPath) => {
2042
- let owner = null;
2043
- for (const record of roots) {
2044
- if (!isPathWithinRoot(record.canonical, canonicalPath)) continue;
2045
- if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2046
- }
2047
- return owner;
2048
- };
2049
- return {
2050
- rootDir: normalizedRootDir,
2051
- canonicalRootDir,
2052
- registerExternalRoot(root) {
2053
- const asRegistered = node_path.default.normalize(root);
2054
- const canonical = canonicalize(asRegistered) ?? asRegistered;
2055
- const existing = roots.find((record) => record.canonical === canonical);
2056
- if (existing) return existing.canonical;
2057
- roots.push({
2058
- asRegistered,
2059
- canonical
2060
- });
2061
- return canonical;
2062
- },
2063
- externalRoots() {
2064
- return roots.map((record) => record.canonical);
2065
- },
2066
- resolveSourceId(inputPath) {
2067
- const lexical = node_path.default.normalize(inputPath);
2068
- const canonical = canonicalize(lexical);
2069
- if (canonical === null) return null;
2070
- const owner = owningRecord(canonical);
2071
- const ownedBy = (record) => {
2072
- const pathInRoot = node_path.default.relative(record.canonical, canonical);
2073
- return {
2074
- sourceKey: node_path.default.relative(normalizedRootDir, node_path.default.join(record.asRegistered, pathInRoot)),
2075
- owningRoot: record.canonical,
2076
- pathInRoot
2077
- };
2078
- };
2079
- const ownerIsMoreSpecific = owner !== null && owner.canonical.length > canonicalRootDir.length;
2080
- let resolved;
2081
- if (!ownerIsMoreSpecific && isPathWithinRoot(canonicalRootDir, canonical)) {
2082
- const key = node_path.default.relative(canonicalRootDir, canonical);
2083
- resolved = {
2084
- sourceKey: key,
2085
- owningRoot: null,
2086
- pathInRoot: key
2087
- };
2088
- } else if (owner) resolved = ownedBy(owner);
2089
- else return null;
2090
- aliases.set(lexical, resolved);
2091
- aliases.set(canonical, resolved);
2092
- return resolved;
2093
- },
2094
- resolveDeletedSourceId(inputPath) {
2095
- return aliases.get(node_path.default.normalize(inputPath)) ?? null;
2096
- },
2097
- containingExternalRoot(inputPath) {
2098
- const lexical = node_path.default.normalize(inputPath);
2099
- let owner = null;
2100
- for (const record of roots) {
2101
- if (!(isPathWithinRoot(record.canonical, lexical) || isPathWithinRoot(record.asRegistered, lexical))) continue;
2102
- if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2103
- }
2104
- if (owner) return owner.canonical;
2105
- const canonical = canonicalize(lexical);
2106
- if (canonical === null) return null;
2107
- for (const record of roots) {
2108
- if (!isPathWithinRoot(record.canonical, canonical)) continue;
2109
- if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2110
- }
2111
- return owner?.canonical ?? null;
2112
- }
2113
- };
2114
- }
2115
- //#endregion
2116
2067
  //#region pipeline/tsconfig-paths.ts
2117
2068
  /**
2118
2069
  * tsconfig `paths` → alias pairs, for drivers with no live bundler config.
@@ -2196,10 +2147,10 @@ function decodePaths(value) {
2196
2147
  function decodeExtends(value) {
2197
2148
  return (Array.isArray(value) ? value : [value]).filter(isJsonString);
2198
2149
  }
2199
- function readConfig(path$4) {
2150
+ function readConfig(path$8) {
2200
2151
  let raw;
2201
2152
  try {
2202
- raw = (0, fs.readFileSync)(path$4, "utf-8");
2153
+ raw = (0, fs.readFileSync)(path$8, "utf-8");
2203
2154
  } catch {
2204
2155
  return null;
2205
2156
  }
@@ -2212,7 +2163,7 @@ function readConfig(path$4) {
2212
2163
  const root = isJsonBlock(parsed) ? parsed : {};
2213
2164
  const compilerOptions = isJsonBlock(root.compilerOptions) ? root.compilerOptions : {};
2214
2165
  return {
2215
- dir: (0, path.dirname)(path$4),
2166
+ dir: (0, path.dirname)(path$8),
2216
2167
  baseUrl: isJsonString(compilerOptions.baseUrl) ? compilerOptions.baseUrl : null,
2217
2168
  paths: decodePaths(compilerOptions.paths),
2218
2169
  extends: decodeExtends(root.extends)
@@ -2244,8 +2195,8 @@ function loadChain(entryPath) {
2244
2195
  const visited = /* @__PURE__ */ new Set();
2245
2196
  const queue = [entryPath];
2246
2197
  while (queue.length > 0) {
2247
- const path$5 = queue.shift();
2248
- const key = (0, path.resolve)(path$5);
2198
+ const path$9 = queue.shift();
2199
+ const key = (0, path.resolve)(path$9);
2249
2200
  if (visited.has(key)) continue;
2250
2201
  visited.add(key);
2251
2202
  const node = readConfig(key);
@@ -2295,6 +2246,216 @@ function readTsconfigAliasPairs(rootDir) {
2295
2246
  return pairs;
2296
2247
  }
2297
2248
  //#endregion
2249
+ //#region pipeline/kit-descriptor.ts
2250
+ /** A kit's description, which `animus build --kit` writes at its package
2251
+ * root and the kit exports as `./animus.json`. */
2252
+ const KIT_DESCRIPTOR_FILE = "animus.json";
2253
+ /** A path with `/` separators, as descriptors key their components. */
2254
+ function posixPath$1(path) {
2255
+ return path.split(node_path.sep).join("/");
2256
+ }
2257
+ /** The descriptor of the kit a build analysed. */
2258
+ function buildKitDescriptor(manifest) {
2259
+ const components = {};
2260
+ for (const id of Object.keys(manifest.components).sort()) {
2261
+ const component = manifest.components[id];
2262
+ components[posixPath$1(id)] = {
2263
+ definition: component.definition_fingerprint,
2264
+ runtimeProps: [...new Set(component.system_prop_names)].sort()
2265
+ };
2266
+ }
2267
+ return {
2268
+ format: 1,
2269
+ system: manifest.system_fingerprint,
2270
+ components
2271
+ };
2272
+ }
2273
+ /** Reads the descriptor at a kit's package root; `null` when it has none. */
2274
+ function readKitDescriptor(pkgRoot, rootDir) {
2275
+ const path = (0, node_path.join)(pkgRoot, KIT_DESCRIPTOR_FILE);
2276
+ if (!(0, node_fs.existsSync)(path)) return null;
2277
+ const packageRoot = posixPath$1((0, node_path.relative)(rootDir, pkgRoot));
2278
+ let wire;
2279
+ try {
2280
+ wire = JSON.parse((0, node_fs.readFileSync)(path, "utf-8"));
2281
+ } catch {
2282
+ return {
2283
+ packageRoot,
2284
+ unreadable: "is not valid JSON: rebuild it with animus build --kit"
2285
+ };
2286
+ }
2287
+ const block = isJsonBlock(wire) ? wire : {};
2288
+ if (block.format !== 1) return {
2289
+ packageRoot,
2290
+ 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`
2291
+ };
2292
+ const definitions = {};
2293
+ const components = isJsonBlock(block.components) ? block.components : {};
2294
+ for (const [id, component] of Object.entries(components)) {
2295
+ const definition = isJsonBlock(component) ? component.definition : null;
2296
+ if (isJsonString(definition)) definitions[posixPath$1(id)] = definition;
2297
+ }
2298
+ return {
2299
+ packageRoot,
2300
+ definitions
2301
+ };
2302
+ }
2303
+ /**
2304
+ * A descriptor in a format this Animus cannot read fails the build; one
2305
+ * whose component definitions differ from the kit's shipped source, as the
2306
+ * analysis read it, or that describes a component its analysed file no
2307
+ * longer declares, was built from other source and is stale.
2308
+ */
2309
+ function kitDescriptorDiagnostics(records, manifest) {
2310
+ if (records.length === 0) return [];
2311
+ const diagnostics = [];
2312
+ const analysedById = new Map(Object.entries(manifest.components).map(([id, component]) => [posixPath$1(id), component]));
2313
+ const analysedFiles = new Set(Object.keys(manifest.fileFacts).map(posixPath$1));
2314
+ const declared = new Set(Object.entries(manifest.fileFacts).flatMap(([file, facts]) => facts.chains.map((chain) => `${posixPath$1(file)}::${chain.descriptor.binding}`)));
2315
+ for (const record of records) {
2316
+ const file = `${record.packageRoot}/${KIT_DESCRIPTOR_FILE}`;
2317
+ if ("unreadable" in record) {
2318
+ diagnostics.push({
2319
+ file,
2320
+ component: "kit",
2321
+ kind: "error",
2322
+ message: record.unreadable,
2323
+ code: KIT_UNSUPPORTED_FORMAT,
2324
+ severity: severityFor(KIT_UNSUPPORTED_FORMAT)
2325
+ });
2326
+ continue;
2327
+ }
2328
+ for (const [id, definition] of Object.entries(record.definitions)) {
2329
+ const key = `${record.packageRoot}/${id}`;
2330
+ const analysed = analysedById.get(key);
2331
+ const fileAnalysed = analysedFiles.has(key.slice(0, key.lastIndexOf("::")));
2332
+ let stale;
2333
+ if (analysed) {
2334
+ if (analysed.definition_fingerprint === definition) continue;
2335
+ stale = `describes ${id} with a definition other than the one its shipped source declares`;
2336
+ } else if (fileAnalysed && !declared.has(key)) stale = `describes ${id}, which its shipped source no longer declares`;
2337
+ else continue;
2338
+ diagnostics.push({
2339
+ file,
2340
+ component: analysed?.binding ?? id.slice(id.lastIndexOf("::") + 2),
2341
+ kind: "warn",
2342
+ message: `${stale}, so the descriptor is stale: rebuild it with animus build --kit`,
2343
+ code: KIT_STALE_DESCRIPTOR,
2344
+ severity: severityFor(KIT_STALE_DESCRIPTOR)
2345
+ });
2346
+ }
2347
+ }
2348
+ return diagnostics;
2349
+ }
2350
+ //#endregion
2351
+ //#region pipeline/source-identity.ts
2352
+ /**
2353
+ * One derivation of source identity for discovery, watch ingestion, ownership
2354
+ * and deletion: event paths are lookup INPUTS only and never fork identity.
2355
+ */
2356
+ /**
2357
+ * True when `target` is `root` or a descendant, decided by `relative()`
2358
+ * shape: string prefixing would let `/ui` claim `/ui-old`.
2359
+ */
2360
+ function isPathWithinRoot(root, target, pathApi = node_path.default) {
2361
+ const rel = pathApi.relative(root, target);
2362
+ return rel === "" || !pathApi.isAbsolute(rel) && rel !== ".." && !rel.startsWith(".." + pathApi.sep);
2363
+ }
2364
+ /**
2365
+ * True when two paths share a platform root (win32 drive letter or UNC
2366
+ * share; always true on posix). Drive letters compare case-insensitively.
2367
+ */
2368
+ function sharesVolumeRoot(a, b, pathApi = node_path.default) {
2369
+ const volumeOf = (p) => pathApi.parse(pathApi.resolve(p)).root.toLowerCase();
2370
+ return volumeOf(a) === volumeOf(b);
2371
+ }
2372
+ function canonicalize(p) {
2373
+ try {
2374
+ return (0, node_fs.realpathSync)(p);
2375
+ } catch {
2376
+ return null;
2377
+ }
2378
+ }
2379
+ function createSourceIdentity(rootDir) {
2380
+ const normalizedRootDir = node_path.default.normalize(rootDir);
2381
+ const canonicalRootDir = canonicalize(normalizedRootDir) ?? normalizedRootDir;
2382
+ const roots = [];
2383
+ const aliases = /* @__PURE__ */ new Map();
2384
+ const owningRecord = (canonicalPath) => {
2385
+ let owner = null;
2386
+ for (const record of roots) {
2387
+ if (!isPathWithinRoot(record.canonical, canonicalPath)) continue;
2388
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2389
+ }
2390
+ return owner;
2391
+ };
2392
+ return {
2393
+ rootDir: normalizedRootDir,
2394
+ canonicalRootDir,
2395
+ registerExternalRoot(root) {
2396
+ const asRegistered = node_path.default.normalize(root);
2397
+ const canonical = canonicalize(asRegistered) ?? asRegistered;
2398
+ const existing = roots.find((record) => record.canonical === canonical);
2399
+ if (existing) return existing.canonical;
2400
+ roots.push({
2401
+ asRegistered,
2402
+ canonical
2403
+ });
2404
+ return canonical;
2405
+ },
2406
+ externalRoots() {
2407
+ return roots.map((record) => record.canonical);
2408
+ },
2409
+ resolveSourceId(inputPath) {
2410
+ const lexical = node_path.default.normalize(inputPath);
2411
+ const canonical = canonicalize(lexical);
2412
+ if (canonical === null) return null;
2413
+ const owner = owningRecord(canonical);
2414
+ const ownedBy = (record) => {
2415
+ const pathInRoot = node_path.default.relative(record.canonical, canonical);
2416
+ return {
2417
+ sourceKey: node_path.default.relative(normalizedRootDir, node_path.default.join(record.asRegistered, pathInRoot)),
2418
+ owningRoot: record.canonical,
2419
+ pathInRoot
2420
+ };
2421
+ };
2422
+ const ownerIsMoreSpecific = owner !== null && owner.canonical.length > canonicalRootDir.length;
2423
+ let resolved;
2424
+ if (!ownerIsMoreSpecific && isPathWithinRoot(canonicalRootDir, canonical)) {
2425
+ const key = node_path.default.relative(canonicalRootDir, canonical);
2426
+ resolved = {
2427
+ sourceKey: key,
2428
+ owningRoot: null,
2429
+ pathInRoot: key
2430
+ };
2431
+ } else if (owner) resolved = ownedBy(owner);
2432
+ else return null;
2433
+ aliases.set(lexical, resolved);
2434
+ aliases.set(canonical, resolved);
2435
+ return resolved;
2436
+ },
2437
+ resolveDeletedSourceId(inputPath) {
2438
+ return aliases.get(node_path.default.normalize(inputPath)) ?? null;
2439
+ },
2440
+ containingExternalRoot(inputPath) {
2441
+ const lexical = node_path.default.normalize(inputPath);
2442
+ let owner = null;
2443
+ for (const record of roots) {
2444
+ if (!(isPathWithinRoot(record.canonical, lexical) || isPathWithinRoot(record.asRegistered, lexical))) continue;
2445
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2446
+ }
2447
+ if (owner) return owner.canonical;
2448
+ const canonical = canonicalize(lexical);
2449
+ if (canonical === null) return null;
2450
+ for (const record of roots) {
2451
+ if (!isPathWithinRoot(record.canonical, canonical)) continue;
2452
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
2453
+ }
2454
+ return owner?.canonical ?? null;
2455
+ }
2456
+ };
2457
+ }
2458
+ //#endregion
2298
2459
  //#region pipeline/discover-packages.ts
2299
2460
  function findPackageRoot(absEntryPath) {
2300
2461
  let pkgRoot = (0, path.dirname)(absEntryPath);
@@ -2320,7 +2481,7 @@ const PACKAGE_OUTPUT_EXCLUDES = [
2320
2481
  ".d.ts",
2321
2482
  ".map"
2322
2483
  ];
2323
- const isFile = (path$2) => {
2484
+ const isFile$1 = (path$2) => {
2324
2485
  try {
2325
2486
  return (0, fs.statSync)(path$2).isFile();
2326
2487
  } catch {
@@ -2332,8 +2493,9 @@ function resolveAbsolutePathSpecifier(absSpecifier, extensionsSet) {
2332
2493
  absSpecifier,
2333
2494
  ...Array.from(extensionsSet, (ext) => absSpecifier + ext),
2334
2495
  ...Array.from(extensionsSet, (ext) => (0, path.join)(absSpecifier, `index${ext}`))
2335
- ].find(isFile) ?? null;
2496
+ ].find(isFile$1) ?? null;
2336
2497
  }
2498
+ /** The package a bare specifier names: `@scope/name` or `name`. */
2337
2499
  function bareSpecifierPackageName(specifier) {
2338
2500
  const segments = specifier.split("/");
2339
2501
  return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
@@ -2348,6 +2510,76 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
2348
2510
  const subpath = specifier.slice(packageName.length + 1);
2349
2511
  return resolveAbsolutePathSpecifier((0, path.join)(srcDir, subpath || "index"), extensionsSet);
2350
2512
  }
2513
+ /** The package's parsed `package.json`, or null when it cannot be read. */
2514
+ function readPackageManifest(pkgRoot) {
2515
+ try {
2516
+ return JSON.parse((0, fs.readFileSync)((0, path.join)(pkgRoot, "package.json"), "utf-8"));
2517
+ } catch {
2518
+ return null;
2519
+ }
2520
+ }
2521
+ /** Each `exports` subpath and its value; a bare target or condition object
2522
+ * is the `.` entry. */
2523
+ function exportsSubpaths(manifest) {
2524
+ const exports = isJsonBlock(manifest) ? manifest.exports : void 0;
2525
+ if (exports === void 0 || exports === null) return [];
2526
+ const subpaths = isJsonBlock(exports) && Object.keys(exports).some((k) => k.startsWith(".")) ? exports : { ".": exports };
2527
+ return Object.entries(subpaths).filter(([subpath]) => subpath.startsWith("."));
2528
+ }
2529
+ /**
2530
+ * The export condition a kit gives each public entry's original source:
2531
+ * `{ "types": …, "animus": "./src/system.ts", "import": …, "default": … }`.
2532
+ * Hosts never resolve through it; discovery reads it, redirects the entry to
2533
+ * its source in every host, and the system loader prefers it.
2534
+ */
2535
+ const KIT_SOURCE_CONDITION = "animus";
2536
+ /** The package's kit source condition, or null when no exact `exports`
2537
+ * entry declares it: the package is no source kit. */
2538
+ function readKitSourceCondition(pkgRoot) {
2539
+ const condition = {
2540
+ entries: [],
2541
+ invalid: [],
2542
+ root: null
2543
+ };
2544
+ for (const [subpath, value] of exportsSubpaths(readPackageManifest(pkgRoot))) {
2545
+ if (subpath.includes("*") || !isJsonBlock(value)) continue;
2546
+ const target = value[KIT_SOURCE_CONDITION];
2547
+ if (!isJsonString(target)) continue;
2548
+ const file = (0, path.resolve)(pkgRoot, target);
2549
+ if (isPathWithinRoot(pkgRoot, file) && isFile$1(file)) condition.entries.push([subpath, file]);
2550
+ else condition.invalid.push([subpath, target]);
2551
+ }
2552
+ if (condition.entries.length === 0 && condition.invalid.length === 0) return null;
2553
+ condition.root = condition.entries.reduce((root, [, file]) => root === null ? (0, path.dirname)(file) : commonDirectory(root, (0, path.dirname)(file)), null);
2554
+ return condition;
2555
+ }
2556
+ function commonDirectory(a, b) {
2557
+ let dir = a;
2558
+ while (!isPathWithinRoot(dir, b)) dir = (0, path.dirname)(dir);
2559
+ return dir;
2560
+ }
2561
+ /** Whether the manifest at `dir` names the package `name`. */
2562
+ function isPackageNamed(dir, name) {
2563
+ const manifest = readPackageManifest(dir);
2564
+ return isJsonBlock(manifest) && manifest.name === name;
2565
+ }
2566
+ function realPath(path$3) {
2567
+ try {
2568
+ return (0, fs.realpathSync)(path$3);
2569
+ } catch {
2570
+ return path$3;
2571
+ }
2572
+ }
2573
+ /** The root of the installed package `name` as Node finds it from `fromDir`,
2574
+ * the first `node_modules/<name>` up the directory tree, at its real path,
2575
+ * as host resolvers report a linked package. */
2576
+ function locatePackageRoot(name, fromDir) {
2577
+ for (let dir = fromDir;; dir = (0, path.dirname)(dir)) {
2578
+ const candidate = (0, path.join)(dir, "node_modules", name);
2579
+ if ((0, fs.existsSync)((0, path.join)(candidate, "package.json"))) return realPath(candidate);
2580
+ if (dir === (0, path.dirname)(dir)) return null;
2581
+ }
2582
+ }
2351
2583
  /**
2352
2584
  * A package's `sideEffects` field, decoded the way Vite reads it: a boolean
2353
2585
  * as written, or the list's globs, one with no `/` matching the basename
@@ -2356,12 +2588,7 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
2356
2588
  * extglobs), which leaves the bundler's side-effectful default.
2357
2589
  */
2358
2590
  function readPackageSideEffects(pkgRoot) {
2359
- let manifest;
2360
- try {
2361
- manifest = JSON.parse((0, fs.readFileSync)((0, path.join)(pkgRoot, "package.json"), "utf-8"));
2362
- } catch {
2363
- return;
2364
- }
2591
+ const manifest = readPackageManifest(pkgRoot);
2365
2592
  const field = isJsonBlock(manifest) ? manifest.sideEffects : void 0;
2366
2593
  if (field === true || field === false) return field;
2367
2594
  if (!Array.isArray(field)) return void 0;
@@ -2369,6 +2596,77 @@ function readPackageSideEffects(pkgRoot) {
2369
2596
  if (globs.some((glob) => /[{}[\]()!+@]/.test(glob))) return void 0;
2370
2597
  return globs.map((glob) => globToRegExp(glob.includes("/") ? glob.replace(/^\.?\//, "") : `**/${glob}`));
2371
2598
  }
2599
+ /** Whether a bundler that reads the nearest `package.json`'s `sideEffects`
2600
+ * against the file's own path keeps `absFile`; undefined without a field
2601
+ * it reads, which keeps the bundler's side-effectful default. */
2602
+ function fileSideEffects(absFile) {
2603
+ const pkgRoot = findPackageRoot(absFile);
2604
+ const field = readPackageSideEffects(pkgRoot);
2605
+ if (field === void 0 || field === true || field === false) return field;
2606
+ return matchesSideEffects(field, pkgRoot, [absFile]);
2607
+ }
2608
+ /** Whether a `sideEffects` glob can name a code file: a last segment with
2609
+ * no extension can name any file, and an extension with glob syntax or of
2610
+ * a script can name code; `*.css` cannot. Brace alternatives are each read,
2611
+ * and braces this reading cannot expand count as naming code. */
2612
+ function globNamesCode(glob) {
2613
+ const alternatives = expandBraces(glob);
2614
+ if (alternatives === null) return true;
2615
+ return alternatives.some((alternative) => {
2616
+ const last = alternative.split("/").at(-1) ?? "";
2617
+ const dot = last.lastIndexOf(".");
2618
+ if (dot === -1) return true;
2619
+ const extension = last.slice(dot + 1);
2620
+ return /[*?[]/.test(extension) || /^[cm]?[jt]sx?$/.test(extension);
2621
+ });
2622
+ }
2623
+ /** Every alternative a glob's `{a,b}` groups spell, nested groups included;
2624
+ * null when its braces do not balance. */
2625
+ function expandBraces(glob) {
2626
+ const open = glob.indexOf("{");
2627
+ if (open === -1) return glob.includes("}") ? null : [glob];
2628
+ let depth = 0;
2629
+ const commas = [];
2630
+ for (let at = open; at < glob.length; at++) if (glob[at] === "{") depth++;
2631
+ else if (glob[at] === "," && depth === 1) commas.push(at);
2632
+ else if (glob[at] === "}" && --depth === 0) {
2633
+ const bounds = [
2634
+ open,
2635
+ ...commas,
2636
+ at
2637
+ ];
2638
+ const rest = expandBraces(glob.slice(at + 1));
2639
+ if (rest === null) return null;
2640
+ const expanded = [];
2641
+ for (let i = 0; i < bounds.length - 1; i++) {
2642
+ const inner = expandBraces(glob.slice(bounds[i] + 1, bounds[i + 1]));
2643
+ if (inner === null) return null;
2644
+ for (const middle of inner) for (const tail of rest) expanded.push(glob.slice(0, open) + middle + tail);
2645
+ }
2646
+ return expanded;
2647
+ }
2648
+ return null;
2649
+ }
2650
+ /**
2651
+ * True when a source module of the package that owns `absFile` must stay
2652
+ * side-effectful: its `sideEffects` list names shipped code, and no proven
2653
+ * map takes a shipped file to its source module, so a bundler that reads
2654
+ * the list against the source path can drop a listed effect. Undefined
2655
+ * leaves the bundler's own reading, which no list naming code can mislead.
2656
+ */
2657
+ function kitSourceModuleSideEffects(absFile) {
2658
+ const manifest = readPackageManifest(findPackageRoot(absFile));
2659
+ const field = isJsonBlock(manifest) ? manifest.sideEffects : void 0;
2660
+ if (!Array.isArray(field)) return void 0;
2661
+ return field.some((glob) => isJsonString(glob) && globNamesCode(glob)) ? true : void 0;
2662
+ }
2663
+ /** Whether a host keeps the module at `file` side-effectful: it lies under
2664
+ * one of the discovered kit source directories, and its package's list
2665
+ * could misread it (see `kitSourceModuleSideEffects`). A host asks this of
2666
+ * the resolved module, never of its importer alone. */
2667
+ function keepsKitSourceEffects(file, kitDirs) {
2668
+ return kitDirs.some((dir) => isPathWithinRoot(dir, file)) && kitSourceModuleSideEffects(file) === true;
2669
+ }
2372
2670
  function matchesSideEffects(globs, pkgRoot, absFiles) {
2373
2671
  return absFiles.some((file) => {
2374
2672
  const relPath = (0, path.relative)(pkgRoot, file).split(path.sep).join("/");
@@ -2392,18 +2690,9 @@ function exportTarget(value) {
2392
2690
  /** Each exact `exports` entry of the package at `pkgRoot`, as its subpath
2393
2691
  * and absolute target; `*` patterns name no single file and are skipped. */
2394
2692
  function packageExportEntries(pkgRoot) {
2395
- let manifest;
2396
- try {
2397
- manifest = JSON.parse((0, fs.readFileSync)((0, path.join)(pkgRoot, "package.json"), "utf-8"));
2398
- } catch {
2399
- return [];
2400
- }
2401
- const exports = isJsonBlock(manifest) ? manifest.exports : void 0;
2402
- if (exports === void 0 || exports === null) return [];
2403
- const subpaths = isJsonBlock(exports) && Object.keys(exports).some((k) => k.startsWith(".")) ? exports : { ".": exports };
2404
2693
  const entries = [];
2405
- for (const [subpath, value] of Object.entries(subpaths)) {
2406
- if (!subpath.startsWith(".") || subpath.includes("*")) continue;
2694
+ for (const [subpath, value] of exportsSubpaths(readPackageManifest(pkgRoot))) {
2695
+ if (subpath.includes("*")) continue;
2407
2696
  const target = exportTarget(value);
2408
2697
  if (target !== null) entries.push([subpath, (0, path.resolve)(pkgRoot, target)]);
2409
2698
  }
@@ -2442,6 +2731,16 @@ async function collectExternalPackageSources(opts) {
2442
2731
  const sourceEntrySideEffects = /* @__PURE__ */ new Map();
2443
2732
  /** `replaced` names the entry the redirect stands in for; it is only
2444
2733
  * needed, and so only resolved, for a glob list. */
2734
+ /** The entry `subpath` of the package resolves to without the redirect.
2735
+ * A resolver with no `import` condition, as Node's `require` has none,
2736
+ * finds no ESM-only entry, so the manifest's own target answers then. */
2737
+ const shippedEntry = async (specifier, pkgRoot, subpath) => {
2738
+ try {
2739
+ const resolved = await resolveSpecifier(specifier);
2740
+ if (resolved) return resolved;
2741
+ } catch {}
2742
+ return packageExportEntries(pkgRoot).find(([entry]) => entry === subpath)?.[1] ?? null;
2743
+ };
2445
2744
  const redirect = async (specifier, srcEntry, pkgRoot, replaced) => {
2446
2745
  sourceEntries.set(specifier, srcEntry);
2447
2746
  const field = readPackageSideEffects(pkgRoot);
@@ -2454,6 +2753,8 @@ async function collectExternalPackageSources(opts) {
2454
2753
  sourceEntrySideEffects.set(specifier, replacedEntry === null || matchesSideEffects(field, pkgRoot, [srcEntry, replacedEntry]));
2455
2754
  };
2456
2755
  const packageDirs = [];
2756
+ const linkedDirs = [];
2757
+ const outputDirs = [];
2457
2758
  const dirOwnerSets = {};
2458
2759
  const dirExtensions = {};
2459
2760
  const fileOwners = {};
@@ -2462,6 +2763,31 @@ async function collectExternalPackageSources(opts) {
2462
2763
  (dirOwnerSets[dir] ??= []).push(specifier);
2463
2764
  };
2464
2765
  const alreadyIngested = (relPath) => hasEntry(relPath) || pushed.has(relPath);
2766
+ /** Adds `file` to the analysis set unless it is there; false when it
2767
+ * cannot be read. */
2768
+ const ingest = (file, specifier) => {
2769
+ const relPath = (0, path.relative)(rootDir, file);
2770
+ if (alreadyIngested(relPath)) return true;
2771
+ let source;
2772
+ try {
2773
+ source = (0, fs.readFileSync)(file, "utf-8");
2774
+ } catch (err) {
2775
+ onUnreadable(relPath, err);
2776
+ return false;
2777
+ }
2778
+ onSourceRead?.(source, relPath, file);
2779
+ entries.push({
2780
+ path: relPath,
2781
+ source
2782
+ });
2783
+ pushed.add(relPath);
2784
+ fileOwners[relPath] ??= specifier;
2785
+ return true;
2786
+ };
2787
+ const diagnostics = [];
2788
+ const reportedPackages = /* @__PURE__ */ new Set();
2789
+ const kitDescriptors = [];
2790
+ const describedRoots = /* @__PURE__ */ new Set();
2465
2791
  for (const specifier of specifiers) {
2466
2792
  let absEntry;
2467
2793
  try {
@@ -2470,7 +2796,14 @@ async function collectExternalPackageSources(opts) {
2470
2796
  absEntry = null;
2471
2797
  }
2472
2798
  if (!absEntry && (0, path.isAbsolute)(specifier)) absEntry = resolveAbsolutePathSpecifier(specifier, extensionsSet);
2473
- if (!absEntry) {
2799
+ const packageName = (0, path.isAbsolute)(specifier) ? null : bareSpecifierPackageName(specifier);
2800
+ const resolvedRoot = absEntry ? findPackageRoot(absEntry) : null;
2801
+ const locatedRoot = packageName && locatePackageRoot(packageName, rootDir);
2802
+ const pkgRoot = resolvedRoot && (!locatedRoot || isPackageNamed(resolvedRoot, packageName) || realPath(resolvedRoot) === locatedRoot) ? resolvedRoot : locatedRoot || resolvedRoot;
2803
+ const condition = packageName && pkgRoot ? readKitSourceCondition(pkgRoot) : null;
2804
+ const entryKey = packageName && `.${specifier.slice(packageName.length)}`;
2805
+ absEntry ??= condition?.entries.find(([entry]) => entry === entryKey)?.[1] ?? null;
2806
+ if (!absEntry || !pkgRoot) {
2474
2807
  outcomes.push({
2475
2808
  specifier,
2476
2809
  outcome: "unresolvable",
@@ -2478,12 +2811,38 @@ async function collectExternalPackageSources(opts) {
2478
2811
  });
2479
2812
  continue;
2480
2813
  }
2481
- const pkgRoot = findPackageRoot(absEntry);
2814
+ const realRoot = realPath(pkgRoot);
2815
+ if (!describedRoots.has(realRoot)) {
2816
+ describedRoots.add(realRoot);
2817
+ const described = readKitDescriptor(pkgRoot, rootDir);
2818
+ if (described) kitDescriptors.push(described);
2819
+ }
2820
+ const linked = !isInstalledPackage(pkgRoot);
2482
2821
  const srcDir = (0, path.join)(pkgRoot, "src");
2483
2822
  let fileCount = 0;
2484
2823
  let staleDist = false;
2485
- if ((0, fs.existsSync)(srcDir)) {
2824
+ if (packageName && !reportedPackages.has(packageName)) {
2825
+ reportedPackages.add(packageName);
2826
+ diagnostics.push(...sourceConditionDiagnostics(packageName, condition));
2827
+ }
2828
+ if (condition && packageName) {
2829
+ for (const [entry, target] of condition.entries) {
2830
+ const entrySpecifier = packageName + entry.slice(1);
2831
+ packageMap[entrySpecifier] = (0, path.relative)(rootDir, target);
2832
+ await redirect(entrySpecifier, target, pkgRoot, () => shippedEntry(entrySpecifier, pkgRoot, entry));
2833
+ }
2834
+ packageMap[specifier] ??= (0, path.relative)(rootDir, absEntry);
2835
+ if (condition.root) {
2836
+ packageDirs.push(condition.root);
2837
+ if (linked) linkedDirs.push(condition.root);
2838
+ claimDir(condition.root, specifier);
2839
+ dirExtensions[condition.root] = [...extensionsSet];
2840
+ onPackageResolved?.(specifier, condition.root);
2841
+ for (const file of walkPackageSources(condition.root, extensionsSet)) if (ingest(file, specifier)) fileCount++;
2842
+ }
2843
+ } else if ((0, fs.existsSync)(srcDir)) {
2486
2844
  packageDirs.push(srcDir);
2845
+ if (linked) linkedDirs.push(srcDir);
2487
2846
  claimDir(srcDir, specifier);
2488
2847
  dirExtensions[srcDir] = [...extensionsSet];
2489
2848
  onPackageResolved?.(specifier, srcDir);
@@ -2493,49 +2852,23 @@ async function collectExternalPackageSources(opts) {
2493
2852
  const replaced = absEntry;
2494
2853
  await redirect(specifier, srcEntry, pkgRoot, async () => replaced);
2495
2854
  } else packageMap[specifier] = (0, path.relative)(rootDir, absEntry);
2496
- if (!(0, path.isAbsolute)(specifier)) {
2497
- const packageName = bareSpecifierPackageName(specifier);
2855
+ if (packageName) {
2498
2856
  if (packageName !== specifier && !(packageName in packageMap)) {
2499
2857
  const rootEntry = sourceEntryForSpecifier(packageName, srcDir, extensionsSet);
2500
2858
  if (rootEntry) {
2501
2859
  packageMap[packageName] = (0, path.relative)(rootDir, rootEntry);
2502
- await redirect(packageName, rootEntry, pkgRoot, async () => {
2503
- try {
2504
- return await resolveSpecifier(packageName);
2505
- } catch {
2506
- return null;
2507
- }
2508
- });
2860
+ await redirect(packageName, rootEntry, pkgRoot, () => shippedEntry(packageName, pkgRoot, "."));
2509
2861
  }
2510
2862
  }
2511
2863
  }
2512
2864
  const pkgFiles = walkPackageSources(srcDir, extensionsSet);
2513
- staleDist = !isInstalledPackage(pkgRoot) && distEntryIsStale(absEntry, srcDir, pkgFiles);
2514
- for (const pkgFile of pkgFiles) {
2515
- const relPath = (0, path.relative)(rootDir, pkgFile);
2516
- if (alreadyIngested(relPath)) {
2517
- fileCount++;
2518
- continue;
2519
- }
2520
- let source;
2521
- try {
2522
- source = (0, fs.readFileSync)(pkgFile, "utf-8");
2523
- } catch (err) {
2524
- onUnreadable(relPath, err);
2525
- continue;
2526
- }
2527
- onSourceRead?.(source, relPath, pkgFile);
2528
- entries.push({
2529
- path: relPath,
2530
- source
2531
- });
2532
- pushed.add(relPath);
2533
- fileOwners[relPath] ??= specifier;
2534
- fileCount++;
2535
- }
2865
+ staleDist = linked && distEntryIsStale(absEntry, srcDir, pkgFiles);
2866
+ for (const pkgFile of pkgFiles) if (ingest(pkgFile, specifier)) fileCount++;
2536
2867
  } else {
2537
2868
  const outputDir = (0, path.dirname)(absEntry);
2538
2869
  packageDirs.push(outputDir);
2870
+ if (linked) linkedDirs.push(outputDir);
2871
+ outputDirs.push(outputDir);
2539
2872
  claimDir(outputDir, specifier);
2540
2873
  onPackageResolved?.(specifier, outputDir);
2541
2874
  packageMap[specifier] = (0, path.relative)(rootDir, absEntry);
@@ -2547,37 +2880,13 @@ async function collectExternalPackageSources(opts) {
2547
2880
  return !PACKAGE_OUTPUT_EXCLUDES.some((pattern) => relToOutput.includes(pattern));
2548
2881
  });
2549
2882
  if (!outputFiles.includes(absEntry)) outputFiles.unshift(absEntry);
2550
- for (const outputFile of outputFiles) {
2551
- const outputRelPath = (0, path.relative)(rootDir, outputFile);
2552
- if (alreadyIngested(outputRelPath)) {
2553
- fileCount++;
2554
- continue;
2555
- }
2556
- let source;
2557
- try {
2558
- source = (0, fs.readFileSync)(outputFile, "utf-8");
2559
- } catch (err) {
2560
- onUnreadable(outputRelPath, err);
2561
- continue;
2562
- }
2563
- onSourceRead?.(source, outputRelPath, outputFile);
2564
- entries.push({
2565
- path: outputRelPath,
2566
- source
2567
- });
2568
- pushed.add(outputRelPath);
2569
- fileOwners[outputRelPath] ??= specifier;
2570
- fileCount++;
2571
- }
2883
+ for (const outputFile of outputFiles) if (ingest(outputFile, specifier)) fileCount++;
2572
2884
  }
2573
- if (!(0, path.isAbsolute)(specifier)) {
2574
- const packageName = bareSpecifierPackageName(specifier);
2575
- for (const [subpath, target] of packageExportEntries(pkgRoot)) {
2576
- const entrySpecifier = packageName + subpath.slice(1);
2577
- const targetRelPath = (0, path.relative)(rootDir, target);
2578
- if (entrySpecifier in packageMap || !alreadyIngested(targetRelPath)) continue;
2579
- packageMap[entrySpecifier] = targetRelPath;
2580
- }
2885
+ if (packageName) for (const [subpath, target] of packageExportEntries(pkgRoot)) {
2886
+ const entrySpecifier = packageName + subpath.slice(1);
2887
+ const targetRelPath = (0, path.relative)(rootDir, target);
2888
+ if (entrySpecifier in packageMap || !alreadyIngested(targetRelPath)) continue;
2889
+ packageMap[entrySpecifier] = targetRelPath;
2581
2890
  }
2582
2891
  outcomes.push({
2583
2892
  specifier,
@@ -2591,12 +2900,112 @@ async function collectExternalPackageSources(opts) {
2591
2900
  sourceEntries,
2592
2901
  sourceEntrySideEffects,
2593
2902
  packageDirs,
2903
+ linkedDirs,
2904
+ outputDirs,
2594
2905
  dirOwnerSets,
2595
2906
  dirExtensions,
2596
2907
  fileOwners,
2597
- outcomes
2908
+ outcomes,
2909
+ diagnostics: [...diagnostics, ...noKitFilesDiagnostics(outcomes)],
2910
+ kitDescriptors
2598
2911
  };
2599
2912
  }
2913
+ /** The dependency names a manifest lists in `fields`. */
2914
+ function dependencyNames(manifest, fields) {
2915
+ if (!isJsonBlock(manifest)) return [];
2916
+ const names = /* @__PURE__ */ new Set();
2917
+ for (const field of fields) {
2918
+ const deps = manifest[field];
2919
+ if (isJsonBlock(deps)) for (const name of Object.keys(deps)) names.add(name);
2920
+ }
2921
+ return [...names];
2922
+ }
2923
+ /** The source kits the application at `rootDir` declares as dependencies.
2924
+ * A bundler serves an installed one as compiled code unless told
2925
+ * otherwise: Next keeps it external on the server unless it is in
2926
+ * `transpilePackages`, and Vite's optimizer prebundles it. */
2927
+ function sourceKitDependencies(rootDir) {
2928
+ return dependencyNames(readPackageManifest(rootDir), ["dependencies", "devDependencies"]).flatMap((name) => {
2929
+ const pkgRoot = locatePackageRoot(name, rootDir);
2930
+ if (pkgRoot === null || readKitSourceCondition(pkgRoot) === null) return [];
2931
+ return [{
2932
+ name,
2933
+ installed: isInstalledPackage(pkgRoot),
2934
+ dependencies: dependencyNames(readPackageManifest(pkgRoot), ["dependencies"])
2935
+ }];
2936
+ });
2937
+ }
2938
+ /** A specifier that names a package: not relative or absolute, and no URL,
2939
+ * scheme (`node:`, `virtual:`), subpath import or virtual id. */
2940
+ function isPackageSpecifier(specifier) {
2941
+ return /^(?:@[^/\\:]+\/)?[^./\\#\0:][^:]*$/.test(specifier);
2942
+ }
2943
+ /**
2944
+ * An error for each kit the application's own `files` import or re-export
2945
+ * whose system the application's system does not include: a package that
2946
+ * declares the kit source condition, with no kit the system extends
2947
+ * (`systemKits`) in that package. One app build has exactly one system, and
2948
+ * every kit's system is part of it, so such a kit is not extracted against
2949
+ * a system it was not built for. One batch parse reads the imports of every
2950
+ * file the engine parses as it is; an adapted source (`.svelte`, `.mdx`)
2951
+ * reaches the engine only as its generated children.
2952
+ */
2953
+ function importedKitDiagnostics(files, engine, rootDir, systemKits) {
2954
+ const { extractFacts } = engine;
2955
+ const parsed = files.filter(({ path: path$4 }) => isEngineTransformExtension(path$4));
2956
+ if (!extractFacts || parsed.length === 0) return [];
2957
+ let facts;
2958
+ try {
2959
+ facts = parseInternalWire(extractFacts(JSON.stringify(parsed.map(({ path: path$5, source }) => ({
2960
+ path: path$5,
2961
+ source
2962
+ })))), "extractFacts");
2963
+ } catch {
2964
+ return [];
2965
+ }
2966
+ const seen = new Set(systemKits.filter((specifier) => !(0, path.isAbsolute)(specifier)).map(bareSpecifierPackageName));
2967
+ const diagnostics = [];
2968
+ for (const [path$6, file] of Object.entries(facts.files)) {
2969
+ if (file.parsePanicked) continue;
2970
+ const specifiers = [...file.imports.map((fact) => fact.source), ...file.exports.flatMap((fact) => fact.source === null ? [] : [fact.source])];
2971
+ for (const specifier of specifiers) {
2972
+ if (!isPackageSpecifier(specifier)) continue;
2973
+ const name = bareSpecifierPackageName(specifier);
2974
+ if (seen.has(name)) continue;
2975
+ seen.add(name);
2976
+ const pkgRoot = locatePackageRoot(name, (0, path.dirname)((0, path.resolve)(rootDir, path$6)));
2977
+ if (pkgRoot && readKitSourceCondition(pkgRoot)) diagnostics.push({
2978
+ file: path$6,
2979
+ component: name,
2980
+ kind: "warn",
2981
+ 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>)`,
2982
+ code: KIT_SYSTEM_NOT_INCLUDED,
2983
+ severity: severityFor(KIT_SYSTEM_NOT_INCLUDED)
2984
+ });
2985
+ }
2986
+ }
2987
+ return diagnostics;
2988
+ }
2989
+ /** A kit without the source condition, warned once, or each condition entry
2990
+ * whose target is missing or outside the package. */
2991
+ function sourceConditionDiagnostics(packageName, condition) {
2992
+ if (condition === null) return [{
2993
+ file: packageName,
2994
+ component: "kit",
2995
+ kind: "warn",
2996
+ 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`,
2997
+ code: KIT_WITHOUT_SOURCE_CONDITION,
2998
+ severity: severityFor(KIT_WITHOUT_SOURCE_CONDITION)
2999
+ }];
3000
+ return condition.invalid.map(([subpath, target]) => ({
3001
+ file: packageName,
3002
+ component: "kit",
3003
+ kind: "warn",
3004
+ message: `exports["${subpath}"].${KIT_SOURCE_CONDITION} names ${target}, which is missing or outside the package, so that entry is not read from source`,
3005
+ code: INVALID_KIT_SOURCE_CONDITION,
3006
+ severity: severityFor(INVALID_KIT_SOURCE_CONDITION)
3007
+ }));
3008
+ }
2600
3009
  function firstOwners(dirOwnerSets) {
2601
3010
  const owners = {};
2602
3011
  for (const [dir, specifiers] of Object.entries(dirOwnerSets)) if (specifiers.length > 0) owners[dir] = specifiers[0];
@@ -2642,10 +3051,14 @@ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
2642
3051
  sourceEntries,
2643
3052
  sourceEntrySideEffects,
2644
3053
  packageDirs: collected.packageDirs.filter((dir) => !rejectedDirs.includes(dir)),
3054
+ linkedDirs: collected.linkedDirs.filter((dir) => !rejectedDirs.includes(dir)),
3055
+ outputDirs: collected.outputDirs.filter((dir) => !rejectedDirs.includes(dir)),
2645
3056
  dirOwnerSets,
2646
3057
  dirExtensions,
2647
3058
  fileOwners,
2648
- outcomes: collected.outcomes
3059
+ outcomes: collected.outcomes,
3060
+ diagnostics: collected.diagnostics,
3061
+ kitDescriptors: collected.kitDescriptors.filter((record) => !rejectedDirs.some((dir) => isPathWithinRoot((0, path.resolve)(rootDir, record.packageRoot), dir)))
2649
3062
  };
2650
3063
  }
2651
3064
  function unresolvableIncludesMessage(outcomes) {
@@ -2666,15 +3079,16 @@ function staleDistIncludesMessage(outcomes) {
2666
3079
  function engineModuleParser(engine) {
2667
3080
  const { extractFacts } = engine;
2668
3081
  if (!extractFacts) return void 0;
2669
- return (source, path$3) => {
3082
+ return (source, path$7) => {
2670
3083
  try {
2671
3084
  const facts = parseInternalWire(extractFacts(JSON.stringify([{
2672
- path: path$3,
3085
+ path: path$7,
2673
3086
  source
2674
- }])), "extractFacts").files[path$3];
3087
+ }])), "extractFacts").files[path$7];
2675
3088
  return facts && !facts.parsePanicked ? {
2676
3089
  imports: facts.imports,
2677
- exports: facts.exports
3090
+ exports: facts.exports,
3091
+ unboundCreateSystemCalls: facts.unboundCreateSystemCalls ?? []
2678
3092
  } : null;
2679
3093
  } catch {
2680
3094
  return null;
@@ -2685,7 +3099,7 @@ const ANIMUS_SYSTEM_SPECIFIER = /^@animus-ui\/system(?:\/.*)?$/;
2685
3099
  /** The file a specifier names from `importer`: a relative one by the
2686
3100
  * ingestion's probe order, a bare one by the host's package resolution. */
2687
3101
  async function resolveModuleFile(importer, specifier, resolvePackage) {
2688
- if (specifier.startsWith(".")) return relativeSourceCandidates(importer, specifier).find(isFile) ?? null;
3102
+ if (specifier.startsWith(".")) return relativeSourceCandidates(importer, specifier).find(isFile$1) ?? null;
2689
3103
  try {
2690
3104
  return await resolvePackage?.(specifier) ?? null;
2691
3105
  } catch {
@@ -2770,13 +3184,15 @@ const NAMESPACE_IMPORT = /\bimport\s+(?:[a-zA-Z_$][a-zA-Z0-9_$]*\s*,\s*)?\*\s*as
2770
3184
  * `import()` of a module. A binding named `createSystem` shown to be
2771
3185
  * something else is no root. One whose identity discovery cannot follow
2772
3186
  * keeps the admission spelling gave it, so a resolution limit never drops a
2773
- * kit. Both are reported. The bare name also anchors, as a global, unless
2774
- * the file binds it.
3187
+ * kit. Both are reported. A call of the bare name, which the file neither
3188
+ * imports nor declares, is no root: the system loader evaluates the file
3189
+ * without auto-imports, where the name is undefined. It is reported once.
2775
3190
  */
2776
- async function systemRoots(systemFilePath, source, imports, following) {
3191
+ async function systemRoots(systemFilePath, source, record, following) {
3192
+ const { imports } = record;
2777
3193
  const roots = /* @__PURE__ */ new Set();
2778
3194
  const identityOf = (specifier, name) => exportIdentity(systemFilePath, specifier, name, following, /* @__PURE__ */ new Set());
2779
- const called = (callee) => new RegExp(`(?<![a-zA-Z0-9_$.])${escapeRegExp$1(callee)}\\s*\\(`).test(source);
3195
+ const called = (callee) => callOf(callee).test(source);
2780
3196
  /** Admits a binding named `createSystem` by its identity: an unknown one
2781
3197
  * keeps `spellingAdmits`, and every unproven one is reported. */
2782
3198
  const admit = (local, offset, identity, spellingAdmits) => {
@@ -2796,11 +3212,8 @@ async function systemRoots(systemFilePath, source, imports, following) {
2796
3212
  ...locationIn(source, offset)
2797
3213
  });
2798
3214
  };
2799
- let bindsName = false;
2800
3215
  for (const binding of imports) {
2801
- const named = binding.imported === "createSystem" || binding.local === "createSystem";
2802
- if (binding.local === "createSystem") bindsName = true;
2803
- if (named) {
3216
+ if (binding.imported === "createSystem" || binding.local === "createSystem") {
2804
3217
  const statement = new RegExp(`\\bimport\\b[^;]*?\\bfrom\\s*['"]${escapeRegExp$1(binding.source)}['"]`, "g");
2805
3218
  let offset = 0;
2806
3219
  for (const match of source.matchAll(statement)) {
@@ -2831,19 +3244,47 @@ async function systemRoots(systemFilePath, source, imports, following) {
2831
3244
  why: `it is destructured from '${initializer}', which is not a module namespace`
2832
3245
  });
2833
3246
  };
3247
+ for (const { local, offset, initializer } of destructuredCreateSystems(source)) admit(local, offset, await destructuredFrom(initializer), local === "createSystem");
3248
+ const unimported = unimportedCreateSystemCall(systemFilePath, record);
3249
+ if (unimported) following.report?.(unimported);
3250
+ return roots;
3251
+ }
3252
+ /** A call of `callee` as a whole name, not a member. */
3253
+ function callOf(callee) {
3254
+ return new RegExp(`(?<![a-zA-Z0-9_$.])${escapeRegExp$1(callee)}\\s*\\(`);
3255
+ }
3256
+ /** Each `createSystem` property a declaration destructures: its local name,
3257
+ * its offset in `source`, and the destructured initializer. */
3258
+ function* destructuredCreateSystems(source) {
2834
3259
  for (const match of source.matchAll(/\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*([^;\n]+)/g)) {
2835
3260
  const [whole, pattern, initializer] = match;
2836
3261
  for (const property of pattern.split(",")) {
2837
3262
  const binding = /^\s*createSystem\s*(?::\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*(?:=[\s\S]*)?$/.exec(property);
2838
3263
  if (!binding) continue;
2839
- const local = binding[1] ?? "createSystem";
2840
- if (local === "createSystem") bindsName = true;
2841
- admit(local, match.index + whole.indexOf("createSystem"), await destructuredFrom(initializer.trim()), local === "createSystem");
3264
+ yield {
3265
+ local: binding[1] ?? "createSystem",
3266
+ offset: match.index + whole.indexOf("createSystem"),
3267
+ initializer: initializer.trim()
3268
+ };
2842
3269
  }
2843
3270
  }
2844
- const declaresName = /\b(?:function\*?|class)\s+createSystem\b|\b(?:const|let|var)\s+createSystem\b/.test(source);
2845
- if (!bindsName && !declaresName) roots.add("createSystem");
2846
- return roots;
3271
+ }
3272
+ /** The warning for a `createSystem` call that the parser's scopes leave
3273
+ * unbound: no import, declaration or parameter names it. Null when there is
3274
+ * none, or when the parser reports no scopes. */
3275
+ function unimportedCreateSystemCall(systemFilePath, record) {
3276
+ const call = record.unboundCreateSystemCalls?.[0];
3277
+ if (!call) return null;
3278
+ return {
3279
+ file: systemFilePath,
3280
+ component: "createSystem",
3281
+ kind: "warn",
3282
+ 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'`,
3283
+ code: UNIMPORTED_CREATE_SYSTEM,
3284
+ severity: severityFor(UNIMPORTED_CREATE_SYSTEM),
3285
+ line: call[0],
3286
+ column: call[1]
3287
+ };
2847
3288
  }
2848
3289
  /** A pattern matching a call of any of `callees` as a whole name, or null
2849
3290
  * when there is none to anchor on. */
@@ -2859,9 +3300,8 @@ async function extractSystemFilePackages(systemFilePath, parseModule, report, re
2859
3300
  return [];
2860
3301
  }
2861
3302
  const parsed = parseModule?.(source, systemFilePath) ?? null;
2862
- const parsedImports = parsed && parsed.imports.length > 0 ? parsed.imports : null;
2863
3303
  let rootCall = "createSystem";
2864
- if (parseModule && parsedImports) rootCall = rootCallPattern([...await systemRoots(systemFilePath, source, parsedImports, {
3304
+ if (parseModule && parsed) rootCall = rootCallPattern([...await systemRoots(systemFilePath, source, parsed, {
2865
3305
  parseModule,
2866
3306
  resolvePackage,
2867
3307
  report
@@ -3033,9 +3473,9 @@ async function extractSystemFilePackages(systemFilePath, parseModule, report, re
3033
3473
  }
3034
3474
  if (identifiers.size === 0) return [];
3035
3475
  const importMap = /* @__PURE__ */ new Map();
3036
- for (const binding of parsedImports ?? []) importMap.set(binding.local, binding.source);
3476
+ for (const binding of parsed?.imports ?? []) importMap.set(binding.local, binding.source);
3037
3477
  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;
3038
- if (!parsedImports) {
3478
+ if (!parsed) {
3039
3479
  let importMatch;
3040
3480
  while ((importMatch = importRegex.exec(source)) !== null) {
3041
3481
  const [, comboDefault, namedImports, defaultImport, specifier] = importMatch;
@@ -3287,8 +3727,13 @@ function enforceExternalTokenContracts(opts) {
3287
3727
  sourceTokens
3288
3728
  });
3289
3729
  if (messages.length === 0) return;
3290
- if (opts.strict) throw new Error(`${opts.prefix} ${messages.join("\n")}`);
3291
- for (const message of messages) opts.warn(message);
3730
+ if (!opts.strict) {
3731
+ for (const message of messages) opts.warn(message);
3732
+ return;
3733
+ }
3734
+ const message = `${opts.prefix} ${messages.join("\n")}`;
3735
+ if (!opts.reportErrors) throw new Error(message);
3736
+ opts.reportErrors(message);
3292
3737
  }
3293
3738
  //#endregion
3294
3739
  //#region pipeline/path-aliases.ts
@@ -3900,11 +4345,14 @@ function buildAnalysisInputs(opts) {
3900
4345
  externalDirsJson: opts.externalDirs?.length && hasSourceThemeManifests(opts.system) ? JSON.stringify(opts.externalDirs) : null
3901
4346
  };
3902
4347
  if (opts.system.declarationScalesJson) inputs.declarationScalesJson = opts.system.declarationScalesJson;
4348
+ if (opts.system.generatedNamesJson) inputs.generatedNamesJson = opts.system.generatedNamesJson;
3903
4349
  const context = opts.analysisContext;
3904
4350
  if (context?.skippedSources?.length || context?.unbundledComputedImports || context?.packageDirs?.length) inputs.analysisContextJson = JSON.stringify({
3905
4351
  skippedSources: context.skippedSources ?? [],
3906
4352
  unbundledComputedImports: context.unbundledComputedImports ?? false,
3907
- packageDirs: context.packageDirs ?? []
4353
+ packageDirs: context.packageDirs ?? [],
4354
+ linkedDirs: context.linkedDirs ?? [],
4355
+ outputDirs: context.outputDirs ?? []
3908
4356
  });
3909
4357
  return inputs;
3910
4358
  }
@@ -3945,7 +4393,12 @@ function runProjectAnalysis(engineApi, opts) {
3945
4393
  t = performance.now();
3946
4394
  const manifest = JSON.parse(manifestJson);
3947
4395
  const systemErrors = systemLoadDiagnostics(opts.system).filter((diagnostic) => diagnostic.kind === "error");
3948
- if (systemErrors.length > 0) manifest.diagnostics = [...systemErrors, ...manifest.diagnostics ?? []];
4396
+ const kitDiagnostics = kitDescriptorDiagnostics(opts.kitDescriptors ?? [], manifest);
4397
+ if (systemErrors.length > 0 || kitDiagnostics.length > 0) manifest.diagnostics = [
4398
+ ...systemErrors,
4399
+ ...kitDiagnostics,
4400
+ ...manifest.diagnostics ?? []
4401
+ ];
3949
4402
  const componentCss = applyUnitFallback(manifest.css);
3950
4403
  const propertyDiagnostics = checkCustomProperties({
3951
4404
  system: opts.system,
@@ -3956,6 +4409,7 @@ function runProjectAnalysis(engineApi, opts) {
3956
4409
  surfaceManifestDiagnostics(manifest, opts.warn, {
3957
4410
  strict: opts.strict,
3958
4411
  info: opts.info,
4412
+ reportErrors: opts.reportErrors,
3959
4413
  levels: opts.diagnostics,
3960
4414
  knownCodes: opts.diagnostics ? knownDiagnosticCodes(diagnosticCodes?.()) : void 0,
3961
4415
  prepend: [
@@ -4063,36 +4517,34 @@ function renameCustomProperties(css, names) {
4063
4517
  }
4064
4518
  //#endregion
4065
4519
  //#region pipeline/prefix.ts
4066
- /** Prefix variable references without changing their matching rules. */
4067
- function prefixVariableReferences(prefix, value) {
4068
- if (!prefix) return value;
4069
- return value.replace(/var\(--([a-zA-Z][\w-]*)\)/g, `var(--${prefix}-$1)`);
4070
- }
4071
- function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson) {
4520
+ /**
4521
+ * The prefix without `prefixContextualVars`: every name Animus generates (a
4522
+ * variable the theme's map or variable CSS defines) takes `--${prefix}-`
4523
+ * wherever it is defined, registered or read, `var()` fallbacks at any depth
4524
+ * included. Every other name stays as written: each declared contextual
4525
+ * variable, and any custom property an author writes.
4526
+ */
4527
+ function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson, declarationScalesJson) {
4072
4528
  if (!prefix) return {
4073
4529
  variableMapJson,
4074
4530
  variableCss,
4075
4531
  themeJson,
4076
- contextualVarsJson
4532
+ contextualVarsJson,
4533
+ declarationScalesJson
4077
4534
  };
4078
- const map = JSON.parse(variableMapJson);
4079
- const prefixed = {};
4080
- for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
4081
- let css = variableCss;
4082
- css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
4083
- css = prefixVariableReferences(prefix, css);
4084
- css = css.replace(/@property(\s+)--([a-zA-Z][\w-]*)/g, `@property$1--${prefix}-$2`);
4535
+ const contextual = new Set(contextualVarsJson ? Object.values(parseInternalWire(contextualVarsJson, "contextualVarsJson (the theme's contextual variable names)")).flat() : []);
4536
+ const variableMap = parseInternalWire(variableMapJson, "variableMapJson (the theme's token variables)");
4537
+ const generated = [...Object.values(variableMap).map((name) => name.replace(/^--/, "")), ...themeDefinitions(variableCss)].filter((name) => !contextual.has(name));
4538
+ const names = createPropertyNames([], prefix, generated);
4539
+ const rename = (value) => renameCustomProperties(value, names);
4085
4540
  const result = {
4086
- variableMapJson: JSON.stringify(prefixed),
4087
- variableCss: css
4541
+ variableMapJson: JSON.stringify(Object.fromEntries(Object.entries(variableMap).map(([key, name]) => [key, rename(name)]))),
4542
+ variableCss: rename(variableCss)
4088
4543
  };
4089
- if (themeJson) result.themeJson = prefixVariableReferences(prefix, themeJson);
4090
- if (contextualVarsJson) {
4091
- const ctxVars = JSON.parse(contextualVarsJson);
4092
- const prefixedCtx = {};
4093
- for (const [scale, names] of Object.entries(ctxVars)) prefixedCtx[scale] = names.map((name) => `${prefix}-${name}`);
4094
- result.contextualVarsJson = JSON.stringify(prefixedCtx);
4095
- }
4544
+ if (themeJson) result.themeJson = renameTokenValues(themeJson, rename);
4545
+ if (contextualVarsJson) result.contextualVarsJson = contextualVarsJson;
4546
+ if (generated.length > 0) result.generatedNamesJson = JSON.stringify(Object.fromEntries(generated.map((name) => [name, `${prefix}-${name}`])));
4547
+ if (declarationScalesJson) result.declarationScalesJson = renameDeclarationRecords(declarationScalesJson, rename);
4096
4548
  return result;
4097
4549
  }
4098
4550
  /** The runtime's transport variables are `--animus-<prop>`. */
@@ -4108,7 +4560,8 @@ function applyPropertyNames(prefix, artifacts) {
4108
4560
  const byScale = artifacts.contextualVarsJson ? parseInternalWire(artifacts.contextualVarsJson, "contextualVarsJson (the theme's contextual variable names)") : null;
4109
4561
  const declared = Object.values(byScale ?? {}).flat();
4110
4562
  const variableMap = parseInternalWire(artifacts.variableMapJson, "variableMapJson (the theme's token variables)");
4111
- const managed = createPropertyNames(declared, prefix, [...Object.values(variableMap).map((name) => name.replace(/^--/, "")), ...[...artifacts.variableCss.matchAll(THEME_DEFINITION)].map((m) => m[1])]);
4563
+ const themeDefined = [...Object.values(variableMap).map((name) => name.replace(/^--/, "")), ...themeDefinitions(artifacts.variableCss)];
4564
+ const managed = createPropertyNames(declared, prefix, themeDefined);
4112
4565
  const nameConflicts = [...[...new Set(declared)].flatMap((name) => {
4113
4566
  const final = managed.finalName(name) ?? name;
4114
4567
  return final.startsWith(TRANSPORT_NAMESPACE) ? [{
@@ -4132,11 +4585,30 @@ function applyPropertyNames(prefix, artifacts) {
4132
4585
  }))]))),
4133
4586
  declarationScalesJson: artifacts.declarationScalesJson === null ? null : renameDeclarationRecords(artifacts.declarationScalesJson, rename),
4134
4587
  nameConflicts,
4135
- contextualProperties: [...new Set(declared)].map((name) => `--${managed.finalName(name) ?? name}`)
4588
+ contextualProperties: [...new Set(declared)].map((name) => `--${managed.finalName(name) ?? name}`),
4589
+ generatedNamesJson: generatedNamesJson(themeDefined.filter((name) => !declared.includes(name)), managed)
4136
4590
  };
4137
4591
  }
4138
- /** A custom property the theme's CSS defines or registers. */
4139
- const THEME_DEFINITION = /(?:@property\s+|(?:^|[\s;{]))--([\w-]+)(?=\s*[:{])/g;
4592
+ /** The generated names' final names, for authored component styles. */
4593
+ function generatedNamesJson(names, managed) {
4594
+ const entries = [...new Set(names)].flatMap((name) => {
4595
+ const final = managed.finalName(name);
4596
+ return final === void 0 ? [] : [[name, final]];
4597
+ });
4598
+ return entries.length === 0 ? null : JSON.stringify(Object.fromEntries(entries));
4599
+ }
4600
+ /** The custom properties the theme's CSS declares or `@property` registers,
4601
+ * read as CSS tokenizes it: a name in a string or comment defines nothing. */
4602
+ function themeDefinitions(variableCss) {
4603
+ const tokens = (0, _animus_ui_properties.tokenize)(variableCss).filter((token) => token.type !== "whitespace");
4604
+ return tokens.flatMap((token, index) => {
4605
+ if (token.type !== "ident" || !token.value.startsWith("--")) return [];
4606
+ const previous = tokens[index - 1];
4607
+ const declared = (previous === void 0 || previous.type === "{" || previous.type === ";") && tokens[index + 1]?.type === ":";
4608
+ const registered = previous?.type === "at-keyword" && previous.value.toLowerCase() === "property";
4609
+ return declared || registered ? [token.value.slice(2)] : [];
4610
+ });
4611
+ }
4140
4612
  /** Renames inside the token map's values. */
4141
4613
  function renameTokenValues(themeJson, rename) {
4142
4614
  const tokens = parseInternalWire(themeJson, "themeJson (the theme's flattened token values)");
@@ -4152,23 +4624,40 @@ function renameDeclarationRecords(declarationScalesJson, rename) {
4152
4624
  }
4153
4625
  //#endregion
4154
4626
  //#region pipeline/system-config.ts
4627
+ /** The loader's evaluation of the system file. A failure on a file that
4628
+ * calls `createSystem` with no binding leads with discovery's warning line
4629
+ * for it, then keeps the loader's own error. */
4630
+ function evaluateSystemModule(engine, systemPath, rootDir, conditions) {
4631
+ try {
4632
+ return engine.loadSystemModule(systemPath, rootDir, void 0, conditions);
4633
+ } catch (error) {
4634
+ let source;
4635
+ try {
4636
+ source = (0, fs.readFileSync)(systemPath, "utf-8");
4637
+ } catch {
4638
+ throw error;
4639
+ }
4640
+ const record = engineModuleParser(engine)?.(source, systemPath);
4641
+ const call = record && unimportedCreateSystemCall(systemPath, record);
4642
+ if (!call) throw error;
4643
+ throw new Error(`${warnLine(call)}\n${String(error)}`, { cause: error });
4644
+ }
4645
+ }
4155
4646
  /**
4156
4647
  * Load and normalize a SystemInstance; `prefix` namespaces every CSS
4157
4648
  * variable name. Error handling stays at the call site.
4158
4649
  */
4159
4650
  function loadSystemConfig(engineApi, opts) {
4160
- const { loadSystemModule } = engineApi();
4161
- const config = loadSystemModule(opts.systemPath, opts.rootDir);
4651
+ const config = evaluateSystemModule(engineApi(), opts.systemPath, opts.rootDir, opts.conditions);
4162
4652
  let scalesJson = config.scalesJson;
4163
4653
  let variableMapJson = config.variableMapJson;
4164
4654
  const registrations = splitInvalidPropertyRegistrations(config.variableCss);
4165
4655
  let variableCss = registrations.css;
4166
4656
  let contextualVarsJson = config.contextualVarsJson || null;
4167
4657
  let declarationScalesJson = config.declarationScalesJson || null;
4168
- const declaredNames = Object.values(parseInternalWire(contextualVarsJson ?? "{}", "contextualVarsJson (the theme's contextual variable names)")).flat();
4169
- let contextualProperties = declaredNames.map((name) => `--${name}`);
4170
- let legacyPrefixedContextualVars = [];
4658
+ let contextualProperties = Object.values(parseInternalWire(contextualVarsJson ?? "{}", "contextualVarsJson (the theme's contextual variable names)")).flat().map((name) => `--${name}`);
4171
4659
  let prefixNameConflicts = [];
4660
+ let generatedNamesJson;
4172
4661
  if (opts.prefix && opts.prefixContextualVars) {
4173
4662
  const resolved = applyPropertyNames(opts.prefix, {
4174
4663
  variableMapJson,
@@ -4184,15 +4673,14 @@ function loadSystemConfig(engineApi, opts) {
4184
4673
  contextualProperties = resolved.contextualProperties;
4185
4674
  prefixNameConflicts = resolved.nameConflicts;
4186
4675
  declarationScalesJson = resolved.declarationScalesJson;
4676
+ generatedNamesJson = resolved.generatedNamesJson;
4187
4677
  } else if (opts.prefix) {
4188
- legacyPrefixedContextualVars = declaredNames;
4189
- contextualProperties = declaredNames.map((name) => `--${opts.prefix}-${name}`);
4190
- const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0);
4678
+ const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0, declarationScalesJson || void 0);
4191
4679
  variableMapJson = prefixed.variableMapJson;
4192
4680
  variableCss = prefixed.variableCss;
4193
4681
  if (prefixed.themeJson) scalesJson = prefixed.themeJson;
4194
- if (prefixed.contextualVarsJson) contextualVarsJson = prefixed.contextualVarsJson;
4195
- if (declarationScalesJson) declarationScalesJson = prefixVariableReferences(opts.prefix, declarationScalesJson);
4682
+ if (prefixed.declarationScalesJson) declarationScalesJson = prefixed.declarationScalesJson;
4683
+ generatedNamesJson = prefixed.generatedNamesJson;
4196
4684
  }
4197
4685
  const system = {
4198
4686
  propConfigJson: config.propConfig,
@@ -4216,7 +4704,7 @@ function loadSystemConfig(engineApi, opts) {
4216
4704
  if (config.propertyRecordsJson) system.propertyRecordsJson = config.propertyRecordsJson;
4217
4705
  if (registrations.invalid.length > 0) system.invalidPropertyRegistrations = registrations.invalid;
4218
4706
  if (prefixNameConflicts.length > 0) system.prefixNameConflicts = prefixNameConflicts;
4219
- if (legacyPrefixedContextualVars.length > 0) system.legacyPrefixedContextualVars = [...new Set(legacyPrefixedContextualVars)];
4707
+ if (generatedNamesJson) system.generatedNamesJson = generatedNamesJson;
4220
4708
  return system;
4221
4709
  }
4222
4710
  //#endregion
@@ -4225,8 +4713,7 @@ function runStructuralSelfCheck(input) {
4225
4713
  const failures = [];
4226
4714
  if (input.componentCount === 0) failures.push("No component CSS produced — check the system file and its includes list");
4227
4715
  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`);
4228
- for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "empty") failures.push(`include '${specifier}' resolved but discovered no component sources [${NO_KIT_FILES}]`);
4229
- else if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
4716
+ for (const { specifier, outcome } of input.externalOutcomes ?? []) if (outcome === "unresolvable") failures.push(`include '${specifier}' could not be resolved`);
4230
4717
  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");
4231
4718
  if ((input.assembledCss ?? `${input.variableCss}\n${input.globalCss}\n${input.componentCss}`).includes("__TRANSFORM__")) failures.push("Unresolved __TRANSFORM__ placeholders found in CSS output");
4232
4719
  if (input.componentCount > 0 && (input.componentCss.length > 0 || input.assembledCss !== void 0)) {
@@ -4260,7 +4747,8 @@ function buildDynamicPropConfig(dynamicProps) {
4260
4747
  kind: meta.kind,
4261
4748
  slotClass: meta.slotClass,
4262
4749
  memberVars: meta.memberVars,
4263
- declarationScaleValues: meta.declarationScaleValues
4750
+ declarationScaleValues: meta.declarationScaleValues,
4751
+ ...meta.productionConditions && { productionConditions: meta.productionConditions }
4264
4752
  };
4265
4753
  continue;
4266
4754
  }
@@ -4278,6 +4766,7 @@ function buildDynamicPropConfig(dynamicProps) {
4278
4766
  if (meta.strict) entry.strict = true;
4279
4767
  if (meta.keywords?.length) entry.keywords = meta.keywords;
4280
4768
  if (meta.currentVar) entry.currentVar = meta.currentVar;
4769
+ if (meta.productionConditions) entry.productionConditions = meta.productionConditions;
4281
4770
  configEntries[propName] = entry;
4282
4771
  }
4283
4772
  return configEntries;
@@ -4316,6 +4805,191 @@ function assertNoErrorDiagnostics(diagnostics) {
4316
4805
  throw new Error(lines.join("\n"));
4317
4806
  }
4318
4807
  //#endregion
4808
+ //#region pipeline/kit-publication.ts
4809
+ /** Source a kit publishes and the loader or a bundler parses for imports. */
4810
+ const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
4811
+ ".ts",
4812
+ ".tsx",
4813
+ ".mts",
4814
+ ".cts",
4815
+ ".js",
4816
+ ".jsx",
4817
+ ".mjs",
4818
+ ".cjs"
4819
+ ]);
4820
+ const isFile = (path) => {
4821
+ try {
4822
+ return (0, node_fs.statSync)(path).isFile();
4823
+ } catch {
4824
+ return false;
4825
+ }
4826
+ };
4827
+ const posixPath = (path) => path.split(node_path.sep).join("/");
4828
+ /**
4829
+ * The package-relative files `npm pack` publishes, read with npm's own rules
4830
+ * (`files`, `.npmignore`, `.gitignore` and the files npm always includes)
4831
+ * through a dry run that runs no scripts and needs no network.
4832
+ */
4833
+ function npmPublishedFiles(pkgRoot) {
4834
+ const windows = process.platform === "win32";
4835
+ const run = (0, node_child_process.spawnSync)(windows ? "npm.cmd" : "npm", [
4836
+ "pack",
4837
+ "--dry-run",
4838
+ "--json",
4839
+ "--ignore-scripts"
4840
+ ], {
4841
+ cwd: pkgRoot,
4842
+ encoding: "utf-8",
4843
+ shell: windows,
4844
+ env: {
4845
+ ...process.env,
4846
+ npm_config_update_notifier: "false"
4847
+ }
4848
+ });
4849
+ if (run.error || run.status !== 0) return {
4850
+ kind: "failed",
4851
+ reason: `npm pack --dry-run could not list the published files: ${String(run.error ?? run.stderr.trim())}`
4852
+ };
4853
+ let output;
4854
+ try {
4855
+ output = JSON.parse(run.stdout);
4856
+ } catch (error) {
4857
+ return {
4858
+ kind: "failed",
4859
+ reason: `npm pack --dry-run printed no JSON: ${String(error)}`
4860
+ };
4861
+ }
4862
+ const pack = Array.isArray(output) ? output[0] : void 0;
4863
+ const files = isJsonBlock(pack) ? pack.files : void 0;
4864
+ if (!Array.isArray(files)) return {
4865
+ kind: "failed",
4866
+ reason: "npm pack --dry-run printed no file list"
4867
+ };
4868
+ return {
4869
+ kind: "listed",
4870
+ files: new Set(files.flatMap((file) => isJsonBlock(file) && isJsonString(file.path) ? [posixPath(file.path)] : []))
4871
+ };
4872
+ }
4873
+ function animusTargets(manifest) {
4874
+ const targets = [];
4875
+ const unreadable = [];
4876
+ const visit = (subpath, value, underAnimus) => {
4877
+ if (isJsonString(value)) {
4878
+ if (underAnimus) targets.push([subpath, value]);
4879
+ } else if (Array.isArray(value)) for (const entry of value) visit(subpath, entry, underAnimus);
4880
+ else if (isJsonBlock(value)) for (const [key, entry] of Object.entries(value)) visit(subpath, entry, underAnimus || key === "animus");
4881
+ else if (underAnimus && value !== null) unreadable.push([subpath, JSON.stringify(value)]);
4882
+ };
4883
+ for (const [subpath, value] of exportsSubpaths(manifest)) visit(subpath, value, false);
4884
+ return {
4885
+ targets,
4886
+ unreadable
4887
+ };
4888
+ }
4889
+ /** Every file in the package outside `node_modules` and `.git`,
4890
+ * package-relative. */
4891
+ function packageFiles(pkgRoot, dir = "") {
4892
+ return (0, node_fs.readdirSync)((0, node_path.join)(pkgRoot, dir), { withFileTypes: true }).flatMap((entry) => {
4893
+ const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
4894
+ if (entry.isDirectory()) return entry.name === "node_modules" || entry.name === ".git" ? [] : packageFiles(pkgRoot, path);
4895
+ return entry.isFile() ? [path] : [];
4896
+ });
4897
+ }
4898
+ /** A `*` target pattern as a matcher of package-relative files. Node puts
4899
+ * one subpath into every `*`, so each later `*` repeats the first match. */
4900
+ function targetPattern(target) {
4901
+ const body = posixPath(target).replace(/^\.\//, "").split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).reduce((pattern, part, index) => index === 0 ? part : `${pattern}${index === 1 ? "(.+)" : "\\1"}${part}`);
4902
+ return new RegExp(`^${body}$`);
4903
+ }
4904
+ /**
4905
+ * Why the kit at `pkgRoot` would fail an installed consumer: each `animus`
4906
+ * source target, at any condition depth or as a pattern, and each file its
4907
+ * relative imports reach, must be in the package and among the files `npm
4908
+ * pack` publishes, and each bare package import a declared dependency.
4909
+ * Each line names the file, the import and the cause; none means the
4910
+ * published source is complete. With `artifactDir`, a build's artifact
4911
+ * directory inside the package that `npm pack` would publish is a line too.
4912
+ */
4913
+ function kitPublicationFailures(pkgRoot, engine, artifactDir) {
4914
+ let manifest;
4915
+ try {
4916
+ manifest = JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(pkgRoot, "package.json"), "utf-8"));
4917
+ } catch (error) {
4918
+ return [`package.json: cannot be read: ${String(error)}`];
4919
+ }
4920
+ const { targets, unreadable } = animusTargets(manifest);
4921
+ if (targets.length === 0 && unreadable.length === 0) return [];
4922
+ const { moduleSpecifiers } = engine;
4923
+ if (moduleSpecifiers === void 0) throw new Error("the native engine does not expose moduleSpecifiers, which the kit publication check needs — rebuild @animus-ui/extract");
4924
+ const publication = npmPublishedFiles(pkgRoot);
4925
+ if (publication.kind === "failed") return [`package.json: ${publication.reason}`];
4926
+ const published = (packageRelative) => publication.files.has(packageRelative);
4927
+ const declared = new Set([
4928
+ "dependencies",
4929
+ "peerDependencies",
4930
+ "optionalDependencies"
4931
+ ].flatMap((field) => {
4932
+ const block = isJsonBlock(manifest) ? manifest[field] : void 0;
4933
+ return isJsonBlock(block) ? Object.keys(block) : [];
4934
+ }));
4935
+ const ownName = isJsonBlock(manifest) && isJsonString(manifest.name) ? manifest.name : null;
4936
+ const failures = [];
4937
+ const rel = (file) => posixPath((0, node_path.relative)(pkgRoot, file));
4938
+ const outside = (relPath) => relPath.startsWith("../") || (0, node_path.isAbsolute)(relPath);
4939
+ for (const [subpath, value] of unreadable) failures.push(`package.json: exports["${subpath}"] has an "animus" value, ${value}, that is no target path`);
4940
+ const queue = [];
4941
+ const admit = (subpath, relPath) => {
4942
+ if (published(relPath)) queue.push((0, node_path.resolve)(pkgRoot, relPath));
4943
+ else failures.push(`package.json: the "animus" target of exports["${subpath}"], ${relPath}, is not in the published files`);
4944
+ };
4945
+ let files = null;
4946
+ for (const [subpath, target] of targets) {
4947
+ const relPath = rel((0, node_path.resolve)(pkgRoot, target));
4948
+ if (outside(relPath)) failures.push(`package.json: the "animus" target of exports["${subpath}"], ${target}, is outside the package`);
4949
+ else if (target.includes("*")) {
4950
+ files ??= packageFiles(pkgRoot);
4951
+ const pattern = targetPattern(target);
4952
+ const matched = files.filter((file) => pattern.test(file));
4953
+ if (matched.length === 0) failures.push(`package.json: the "animus" target of exports["${subpath}"], ${target}, matches no file in the package`);
4954
+ for (const file of matched) admit(subpath, file);
4955
+ } else if (!isFile((0, node_path.resolve)(pkgRoot, target))) failures.push(`package.json: the "animus" target of exports["${subpath}"], ${target}, does not exist in the package`);
4956
+ else admit(subpath, relPath);
4957
+ }
4958
+ const visited = /* @__PURE__ */ new Set();
4959
+ while (queue.length > 0) {
4960
+ const file = queue.shift();
4961
+ if (visited.has(file)) continue;
4962
+ visited.add(file);
4963
+ const specifiers = parseInternalWire(moduleSpecifiers(JSON.stringify([{
4964
+ path: file,
4965
+ source: (0, node_fs.readFileSync)(file, "utf-8")
4966
+ }])), "moduleSpecifiers").files[file] ?? [];
4967
+ for (const specifier of specifiers) {
4968
+ if (specifier.startsWith(".")) {
4969
+ const target = relativeSourceCandidates(file, specifier).find(isFile);
4970
+ if (target === void 0) failures.push(`${rel(file)}: import '${specifier}' resolves to no file in the package`);
4971
+ else if (outside(rel(target))) failures.push(`${rel(file)}: import '${specifier}' resolves to ${rel(target)}, outside the package`);
4972
+ else if (!published(rel(target))) failures.push(`${rel(file)}: import '${specifier}' resolves to ${rel(target)}, which is not in the published files`);
4973
+ else if (CODE_EXTENSIONS.has((0, node_path.extname)(target))) queue.push(target);
4974
+ continue;
4975
+ }
4976
+ if (specifier.startsWith("/") || /^[a-z][\w+.-]*:/i.test(specifier)) {
4977
+ if (!(0, node_module.isBuiltin)(specifier)) failures.push(`${rel(file)}: import '${specifier}' is not a package or relative import, so an installed consumer cannot resolve it`);
4978
+ continue;
4979
+ }
4980
+ const name = bareSpecifierPackageName(specifier);
4981
+ if ((0, node_module.isBuiltin)(name) || name === ownName || declared.has(name)) continue;
4982
+ failures.push(`${rel(file)}: import '${specifier}' names the package ${name}, which package.json does not declare in dependencies, peerDependencies or optionalDependencies`);
4983
+ }
4984
+ }
4985
+ const artifacts = artifactDir === void 0 ? "" : rel(artifactDir);
4986
+ if (artifacts !== "" && !outside(artifacts)) {
4987
+ const shipped = [...publication.files].filter((file) => file.startsWith(`${artifacts}/`));
4988
+ 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"`);
4989
+ }
4990
+ return failures;
4991
+ }
4992
+ //#endregion
4319
4993
  //#region pipeline/replacement-plans.ts
4320
4994
  const snapshotByManifest = /* @__PURE__ */ new WeakMap();
4321
4995
  /** `null` (a context before its first analysis) snapshots to the empty plan
@@ -4452,6 +5126,12 @@ Object.defineProperty(exports, "INVALID_PROPERTY_REGISTRATION", {
4452
5126
  return INVALID_PROPERTY_REGISTRATION;
4453
5127
  }
4454
5128
  });
5129
+ Object.defineProperty(exports, "KIT_DESCRIPTOR_FILE", {
5130
+ enumerable: true,
5131
+ get: function() {
5132
+ return KIT_DESCRIPTOR_FILE;
5133
+ }
5134
+ });
4455
5135
  Object.defineProperty(exports, "REPLACEABLE_DEFAULT_EXCLUDE", {
4456
5136
  enumerable: true,
4457
5137
  get: function() {
@@ -4542,6 +5222,12 @@ Object.defineProperty(exports, "buildDynamicPropConfig", {
4542
5222
  return buildDynamicPropConfig;
4543
5223
  }
4544
5224
  });
5225
+ Object.defineProperty(exports, "buildKitDescriptor", {
5226
+ enumerable: true,
5227
+ get: function() {
5228
+ return buildKitDescriptor;
5229
+ }
5230
+ });
4545
5231
  Object.defineProperty(exports, "buildPathAliasesJson", {
4546
5232
  enumerable: true,
4547
5233
  get: function() {
@@ -4644,6 +5330,12 @@ Object.defineProperty(exports, "extractSystemFilePackages", {
4644
5330
  return extractSystemFilePackages;
4645
5331
  }
4646
5332
  });
5333
+ Object.defineProperty(exports, "fileSideEffects", {
5334
+ enumerable: true,
5335
+ get: function() {
5336
+ return fileSideEffects;
5337
+ }
5338
+ });
4647
5339
  Object.defineProperty(exports, "findAssetSpecifiers", {
4648
5340
  enumerable: true,
4649
5341
  get: function() {
@@ -4680,6 +5372,12 @@ Object.defineProperty(exports, "hashReplacementPlans", {
4680
5372
  return hashReplacementPlans;
4681
5373
  }
4682
5374
  });
5375
+ Object.defineProperty(exports, "importedKitDiagnostics", {
5376
+ enumerable: true,
5377
+ get: function() {
5378
+ return importedKitDiagnostics;
5379
+ }
5380
+ });
4683
5381
  Object.defineProperty(exports, "ingestSourceEntries", {
4684
5382
  enumerable: true,
4685
5383
  get: function() {
@@ -4734,6 +5432,30 @@ Object.defineProperty(exports, "isUnresolvedParentDrop", {
4734
5432
  return isUnresolvedParentDrop;
4735
5433
  }
4736
5434
  });
5435
+ Object.defineProperty(exports, "keepsKitSourceEffects", {
5436
+ enumerable: true,
5437
+ get: function() {
5438
+ return keepsKitSourceEffects;
5439
+ }
5440
+ });
5441
+ Object.defineProperty(exports, "kitDescriptorDiagnostics", {
5442
+ enumerable: true,
5443
+ get: function() {
5444
+ return kitDescriptorDiagnostics;
5445
+ }
5446
+ });
5447
+ Object.defineProperty(exports, "kitPublicationFailures", {
5448
+ enumerable: true,
5449
+ get: function() {
5450
+ return kitPublicationFailures;
5451
+ }
5452
+ });
5453
+ Object.defineProperty(exports, "kitSourceModuleSideEffects", {
5454
+ enumerable: true,
5455
+ get: function() {
5456
+ return kitSourceModuleSideEffects;
5457
+ }
5458
+ });
4737
5459
  Object.defineProperty(exports, "loadSystemConfig", {
4738
5460
  enumerable: true,
4739
5461
  get: function() {
@@ -4776,6 +5498,12 @@ Object.defineProperty(exports, "projectExternalFileOwners", {
4776
5498
  return projectExternalFileOwners;
4777
5499
  }
4778
5500
  });
5501
+ Object.defineProperty(exports, "readKitDescriptor", {
5502
+ enumerable: true,
5503
+ get: function() {
5504
+ return readKitDescriptor;
5505
+ }
5506
+ });
4779
5507
  Object.defineProperty(exports, "readTsconfigAliasPairs", {
4780
5508
  enumerable: true,
4781
5509
  get: function() {
@@ -4854,6 +5582,12 @@ Object.defineProperty(exports, "snapshotFilePlans", {
4854
5582
  return snapshotFilePlans;
4855
5583
  }
4856
5584
  });
5585
+ Object.defineProperty(exports, "sourceKitDependencies", {
5586
+ enumerable: true,
5587
+ get: function() {
5588
+ return sourceKitDependencies;
5589
+ }
5590
+ });
4857
5591
  Object.defineProperty(exports, "stableStringify", {
4858
5592
  enumerable: true,
4859
5593
  get: function() {