@velarscript/cli 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +4 -12
  2. package/dist/browser-process-owner.d.ts +23 -0
  3. package/dist/browser-process-owner.d.ts.map +1 -1
  4. package/dist/browser-process-owner.js +46 -2
  5. package/dist/browser-process-owner.js.map +1 -1
  6. package/dist/browser-test-runner.d.ts.map +1 -1
  7. package/dist/browser-test-runner.js +110 -14
  8. package/dist/browser-test-runner.js.map +1 -1
  9. package/dist/cli.js +131 -68
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +28 -4
  13. package/dist/config.js.map +1 -1
  14. package/dist/dev-server.d.ts +18 -0
  15. package/dist/dev-server.d.ts.map +1 -1
  16. package/dist/dev-server.js +168 -8
  17. package/dist/dev-server.js.map +1 -1
  18. package/dist/extension-metadata.d.ts +6 -0
  19. package/dist/extension-metadata.d.ts.map +1 -1
  20. package/dist/extension-metadata.js +70 -4
  21. package/dist/extension-metadata.js.map +1 -1
  22. package/dist/format-guard.d.ts +20 -0
  23. package/dist/format-guard.d.ts.map +1 -0
  24. package/dist/format-guard.js +13 -0
  25. package/dist/format-guard.js.map +1 -0
  26. package/dist/framework-host.d.ts.map +1 -1
  27. package/dist/framework-host.js +15 -2
  28. package/dist/framework-host.js.map +1 -1
  29. package/dist/language-server-bundle-entry.js +0 -2
  30. package/dist/language-server-bundle-entry.js.map +1 -1
  31. package/dist/language-server-tool.d.ts.map +1 -1
  32. package/dist/language-server-tool.js +0 -2
  33. package/dist/language-server-tool.js.map +1 -1
  34. package/dist/language-server.d.ts +1 -1
  35. package/dist/language-server.d.ts.map +1 -1
  36. package/dist/language-server.js +87 -293
  37. package/dist/language-server.js.map +1 -1
  38. package/dist/local-request-guard.d.ts +18 -0
  39. package/dist/local-request-guard.d.ts.map +1 -0
  40. package/dist/local-request-guard.js +75 -0
  41. package/dist/local-request-guard.js.map +1 -0
  42. package/dist/mechanical-fixer.d.ts.map +1 -1
  43. package/dist/mechanical-fixer.js +116 -2
  44. package/dist/mechanical-fixer.js.map +1 -1
  45. package/dist/module-assets.d.ts +23 -0
  46. package/dist/module-assets.d.ts.map +1 -1
  47. package/dist/module-assets.js +216 -14
  48. package/dist/module-assets.js.map +1 -1
  49. package/dist/node-application.d.ts +7 -2
  50. package/dist/node-application.d.ts.map +1 -1
  51. package/dist/node-application.js +44 -14
  52. package/dist/node-application.js.map +1 -1
  53. package/dist/node-runtime-dependencies.d.ts +2 -0
  54. package/dist/node-runtime-dependencies.d.ts.map +1 -0
  55. package/dist/node-runtime-dependencies.js +24 -0
  56. package/dist/node-runtime-dependencies.js.map +1 -0
  57. package/dist/npm.d.ts +29 -0
  58. package/dist/npm.d.ts.map +1 -1
  59. package/dist/npm.js +39 -2
  60. package/dist/npm.js.map +1 -1
  61. package/dist/ownership-graph.d.ts.map +1 -1
  62. package/dist/ownership-graph.js +155 -38
  63. package/dist/ownership-graph.js.map +1 -1
  64. package/dist/package-manager.d.ts.map +1 -1
  65. package/dist/package-manager.js +4 -1
  66. package/dist/package-manager.js.map +1 -1
  67. package/dist/preview-server.d.ts.map +1 -1
  68. package/dist/preview-server.js +12 -4
  69. package/dist/preview-server.js.map +1 -1
  70. package/dist/production-build.d.ts.map +1 -1
  71. package/dist/production-build.js +24 -6
  72. package/dist/production-build.js.map +1 -1
  73. package/dist/production-verifier.js +3 -0
  74. package/dist/production-verifier.js.map +1 -1
  75. package/dist/project-check.d.ts +14 -1
  76. package/dist/project-check.d.ts.map +1 -1
  77. package/dist/project-check.js +17 -4
  78. package/dist/project-check.js.map +1 -1
  79. package/dist/project-format.d.ts +9 -0
  80. package/dist/project-format.d.ts.map +1 -1
  81. package/dist/project-format.js +13 -0
  82. package/dist/project-format.js.map +1 -1
  83. package/dist/project-semantic.d.ts.map +1 -1
  84. package/dist/project-semantic.js +15 -4
  85. package/dist/project-semantic.js.map +1 -1
  86. package/dist/project.d.ts +25 -0
  87. package/dist/project.d.ts.map +1 -1
  88. package/dist/project.js +341 -42
  89. package/dist/project.js.map +1 -1
  90. package/dist/reproduction.d.ts +18 -0
  91. package/dist/reproduction.d.ts.map +1 -1
  92. package/dist/reproduction.js +47 -6
  93. package/dist/reproduction.js.map +1 -1
  94. package/dist/stable-order.d.ts +17 -0
  95. package/dist/stable-order.d.ts.map +1 -0
  96. package/dist/stable-order.js +19 -0
  97. package/dist/stable-order.js.map +1 -0
  98. package/dist/standard-modules.d.ts +2 -2
  99. package/dist/standard-modules.d.ts.map +1 -1
  100. package/dist/standard-modules.js +2 -2
  101. package/dist/standard-modules.js.map +1 -1
  102. package/dist/static-deployment.d.ts.map +1 -1
  103. package/dist/static-deployment.js +27 -0
  104. package/dist/static-deployment.js.map +1 -1
  105. package/dist/test-runner.d.ts.map +1 -1
  106. package/dist/test-runner.js +196 -100
  107. package/dist/test-runner.js.map +1 -1
  108. package/dist/test-worker.d.ts +64 -0
  109. package/dist/test-worker.d.ts.map +1 -0
  110. package/dist/test-worker.js +161 -0
  111. package/dist/test-worker.js.map +1 -0
  112. package/dist/typescript-declarations.d.ts +1 -1
  113. package/dist/typescript-declarations.d.ts.map +1 -1
  114. package/dist/typescript-declarations.js +233 -21
  115. package/dist/typescript-declarations.js.map +1 -1
  116. package/dist/unowned-errors.d.ts +15 -2
  117. package/dist/unowned-errors.d.ts.map +1 -1
  118. package/dist/unowned-errors.js +13 -2
  119. package/dist/unowned-errors.js.map +1 -1
  120. package/dist/version.d.ts +1 -1
  121. package/dist/version.js +1 -1
  122. package/dist/workspace-index.d.ts.map +1 -1
  123. package/dist/workspace-index.js +42 -17
  124. package/dist/workspace-index.js.map +1 -1
  125. package/package.json +7 -8
  126. package/skill/ai-skill-node.md +91 -12
  127. package/skill/ai-skill-web.md +51 -1
  128. package/skill/ai-skill.md +36 -13
  129. package/dist/official-script-language-service.d.ts +0 -2
  130. package/dist/official-script-language-service.d.ts.map +0 -1
  131. package/dist/official-script-language-service.js +0 -10
  132. package/dist/official-script-language-service.js.map +0 -1
  133. package/dist/official-tool-assets.d.ts +0 -3
  134. package/dist/official-tool-assets.d.ts.map +0 -1
  135. package/dist/official-tool-assets.js +0 -60
  136. package/dist/official-tool-assets.js.map +0 -1
  137. package/dist/script-language-service.d.ts +0 -82
  138. package/dist/script-language-service.d.ts.map +0 -1
  139. package/dist/script-language-service.js +0 -18
  140. package/dist/script-language-service.js.map +0 -1
package/dist/project.js CHANGED
@@ -10,6 +10,8 @@ import { MAX_VELAR_PROJECT_MODULES, readVelarSourceFile, validateVelarSourceText
10
10
  import { readBoundedText } from "./bounded-text.js";
11
11
  import { hostErrorMessage, isHostErrorCode } from "./host-error.js";
12
12
  import { canonicalizePotentialPath } from "./canonical-path.js";
13
+ import { byCodeUnit } from "./stable-order.js";
14
+ import { VELAR_VERSION } from "./version.js";
13
15
  const MAX_PROJECT_RESOURCES = 1024;
14
16
  const MAX_JSON_RESOURCE_BYTES = 4 * 1024 * 1024;
15
17
  function missingExportMessage(source, name) {
@@ -103,8 +105,11 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
103
105
  scheduled.add(module.inputPath);
104
106
  pending.push(module);
105
107
  };
106
- while (pending.length > 0) {
107
- const pendingModule = pending.shift();
108
+ // An index cursor, not `shift()`: the queue is bounded by
109
+ // MAX_VELAR_PROJECT_MODULES, and shifting each of those entries off the
110
+ // front costs O(n) apiece, making the dependency walk itself quadratic.
111
+ for (let cursor = 0; cursor < pending.length; cursor += 1) {
112
+ const pendingModule = pending[cursor];
108
113
  const inputPath = pendingModule.inputPath;
109
114
  if (visited.has(inputPath))
110
115
  continue;
@@ -385,8 +390,10 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
385
390
  }
386
391
  }
387
392
  const modules = [];
388
- const affected = previous ? affectedModules(loaded, velarImports, resourceImports, previous, changedPaths) : new Set(loaded.keys());
389
393
  const previousModules = new Map(previous?.modules.map((module) => [module.inputPath, module]));
394
+ const affected = previous
395
+ ? affectedModules(loaded, velarImports, resourceImports, previous, previousModules, changedPaths)
396
+ : new Set(loaded.keys());
390
397
  for (const [dependency, importers] of previous?.externalTypeDependencies ?? []) {
391
398
  for (const importer of importers) {
392
399
  if (!loaded.has(importer) || affected.has(importer))
@@ -463,7 +470,13 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
463
470
  modules.push(...group.map((module) => passResults.get(module.inputPath)));
464
471
  }
465
472
  appendInitializationCycleDiagnostics(modules, loaded, velarImports, entryPath, resolutionDiagnostics);
466
- modules.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
473
+ // D90 R3(a): module order decides the concatenated stylesheet's bytes, its
474
+ // content hash, and `buildId`. `localeCompare` follows the collation the
475
+ // process environment selects, so it made those outputs — and the cascade
476
+ // winner between two equal-specificity rules — depend on the build
477
+ // machine's `LC_ALL`. Order by code unit over the POSIX-normalized
478
+ // relative path instead.
479
+ modules.sort((left, right) => byCodeUnit(left.relativePath, right.relativePath));
467
480
  if (framework?.host.validateProject) {
468
481
  try {
469
482
  const messages = framework.host.validateProject({
@@ -622,10 +635,10 @@ function standardModuleListing(source, interfaces) {
622
635
  }
623
636
  function migratedStandardPackageDiagnostic(source) {
624
637
  if (source === "velar/javascript") {
625
- return "Standard module 'velar/javascript' moved to package '@velarscript/script-analysis'; install it, then import from '@velarscript/script-analysis'";
638
+ return "Standard module 'velar/javascript' is not part of VelarScript; JavaScript and TypeScript tooling belongs to the consuming project";
626
639
  }
627
640
  if (source === "velar/text-buffer") {
628
- return "Standard module 'velar/text-buffer' moved to package '@velarscript/text-buffer'; install it, then import from '@velarscript/text-buffer'";
641
+ return "Standard module 'velar/text-buffer' is not part of VelarScript; applications must own or install their text-buffer implementation";
629
642
  }
630
643
  return null;
631
644
  }
@@ -667,7 +680,7 @@ function importedReactiveAssignmentDiagnostics(result, reactiveImports) {
667
680
  ? { ...result, diagnostics }
668
681
  : result;
669
682
  }
670
- function affectedModules(loaded, velarImports, resourceImports, previous, changedPaths) {
683
+ function affectedModules(loaded, velarImports, resourceImports, previous, previousModules, changedPaths) {
671
684
  const affected = new Set([...changedPaths].map((path) => resolve(path)));
672
685
  const reverse = new Map();
673
686
  const dependencies = (path, values, imports) => {
@@ -709,25 +722,30 @@ function affectedModules(loaded, velarImports, resourceImports, previous, change
709
722
  dependents.add(importer);
710
723
  reverse.set(dependency, dependents);
711
724
  }
725
+ // An index cursor, not `shift()`: the array shift is O(n) per element, so
726
+ // the closure walk was quadratic in the number of affected modules.
712
727
  const pending = [...affected];
713
- while (pending.length > 0) {
714
- const path = pending.shift();
715
- for (const dependent of reverse.get(path) ?? []) {
728
+ for (let cursor = 0; cursor < pending.length; cursor += 1) {
729
+ for (const dependent of reverse.get(pending[cursor]) ?? []) {
716
730
  if (affected.has(dependent))
717
731
  continue;
718
732
  affected.add(dependent);
719
733
  pending.push(dependent);
720
734
  }
721
735
  }
736
+ // A module the previous result never held is affected by definition. Asking
737
+ // that of the previous module *list* was a linear scan per loaded module —
738
+ // O(M²) string comparisons on every keystroke at the 4096-module cap — for
739
+ // a question the caller's map already answers in constant time.
722
740
  for (const module of loaded.keys())
723
- if (!previous.modules.some((item) => item.inputPath === module))
741
+ if (!previousModules.has(module))
724
742
  affected.add(module);
725
743
  return affected;
726
744
  }
727
745
  function dependencyFirstCompilationGroups(loaded, velarImports, compilerExtensions) {
728
746
  return stronglyConnectedPaths(loaded.keys(), (path) => moduleDependencies(loaded.get(path), loaded, velarImports, compilerExtensions)).map((group) => group
729
747
  .map((path) => loaded.get(path))
730
- .sort((left, right) => left.inputPath.localeCompare(right.inputPath)));
748
+ .sort((left, right) => byCodeUnit(left.inputPath, right.inputPath)));
731
749
  }
732
750
  /** Iterative Tarjan: the public 4096-module bound must not depend on host stack depth. */
733
751
  function stronglyConnectedPaths(paths, dependencies) {
@@ -915,7 +933,9 @@ function appendInitializationCycleDiagnostics(modules, loaded, velarImports, ent
915
933
  const additions = [];
916
934
  if (cyclic && (componentSizes.get(componentOf.get(path) ?? -1) ?? 0) > 1) {
917
935
  for (const read of compiled.initializationImportReads) {
918
- const target = originModule(resolveDependency(path, read.source), read.imported, loaded, resolveDependency);
936
+ const imported = resolveDependency(path, read.source);
937
+ const origin = originModule(imported, read.imported, loaded, resolveDependency);
938
+ const target = origin.module;
919
939
  if (target === null || target === path)
920
940
  continue;
921
941
  if (componentOf.get(target) !== componentOf.get(path))
@@ -924,13 +944,21 @@ function appendInitializationCycleDiagnostics(modules, loaded, velarImports, ent
924
944
  // initializes at link time, so a cycle member may call one before the
925
945
  // defining module evaluates. Every other export shape is in its
926
946
  // temporal dead zone until then.
927
- if (read.imported !== null && loaded.get(target)?.inspection.moduleInterface.hoistedExports?.has(read.imported))
947
+ //
948
+ // The exemption is a fact about the *origin* module's own export, so
949
+ // it must be asked with the name that module declares. An aliasing
950
+ // barrel (`export {value as helper}`) renames on the way through, and
951
+ // asking with the import-site alias answered about an unrelated
952
+ // export of the same name: it suppressed a real cycle read whenever
953
+ // the origin happened to have a `def` under the alias, and reported a
954
+ // correct program whenever the origin's `def` was renamed.
955
+ if (origin.name !== null && loaded.get(target)?.inspection.moduleInterface.hoistedExports?.has(origin.name))
928
956
  continue;
929
957
  const modulePosition = order.get(path);
930
958
  const targetPosition = order.get(target);
931
959
  if (modulePosition === undefined || targetPosition === undefined || targetPosition < modulePosition)
932
960
  continue;
933
- additions.push(diagnostic(INITIALIZATION_CYCLE_DIAGNOSTIC, `Move this read into a function, or extract the shared value into a third module; '${read.source}' has not initialized when this line runs`, read.span));
961
+ additions.push(diagnostic(INITIALIZATION_CYCLE_DIAGNOSTIC, `Move this read into a function, or extract the shared value into a third module; ${uninitializedModuleName(read.source, imported, target, loaded)} has not initialized when this line runs`, read.span));
934
962
  }
935
963
  }
936
964
  // MOD-I5: resolution failures overlay the same way, on the import
@@ -963,7 +991,7 @@ function appendInitializationCycleDiagnostics(modules, loaded, velarImports, ent
963
991
  ...compiled,
964
992
  // The compile() contract keeps diagnostics ordered by span.
965
993
  diagnostics: [...compiled.diagnostics, ...additions]
966
- .sort((left, right) => left.span.start - right.span.start || left.code.localeCompare(right.code)),
994
+ .sort((left, right) => left.span.start - right.span.start || byCodeUnit(left.code, right.code)),
967
995
  // The zero-diagnostics gate for code generation holds after the
968
996
  // project-level check as well.
969
997
  code: null,
@@ -978,9 +1006,11 @@ function appendInitializationCycleDiagnostics(modules, loaded, velarImports, ent
978
1006
  }
979
1007
  /**
980
1008
  * The module that declares an imported name, following `export {name} from
981
- * "source"` barrels. Judging a cycle read by the module the import names would
982
- * let a barrel hide the defining module, whose binding is the one that is
983
- * actually uninitialized at run time.
1009
+ * "source"` barrels, together with the name that module declares it under.
1010
+ * Judging a cycle read by the module the import names would let a barrel hide
1011
+ * the defining module, whose binding is the one that is actually
1012
+ * uninitialized at run time; judging it by the import-site alias would ask
1013
+ * the defining module about a name it may not use for this export.
984
1014
  */
985
1015
  function originModule(target, imported, loaded, resolveDependency) {
986
1016
  let current = target;
@@ -990,33 +1020,44 @@ function originModule(target, imported, loaded, resolveDependency) {
990
1020
  seen.add(`${current}\0${name}`);
991
1021
  const reExport = loaded.get(current)?.inspection.moduleInterface.reExports.get(name);
992
1022
  if (reExport === undefined)
993
- return current;
1023
+ return { module: current, name };
994
1024
  const next = resolveDependency(current, reExport.source);
995
1025
  if (next === null)
996
- return current;
1026
+ return { module: current, name };
997
1027
  current = next;
998
1028
  name = reExport.imported;
999
1029
  }
1000
- return current;
1030
+ return { module: current, name };
1031
+ }
1032
+ /**
1033
+ * The module the author must open. Through a re-export barrel the specifier
1034
+ * written at the import site names a module that has fully initialized, so
1035
+ * naming it alone states something false; name the origin and keep the
1036
+ * specifier as the route to it.
1037
+ */
1038
+ function uninitializedModuleName(source, imported, origin, loaded) {
1039
+ if (imported === origin)
1040
+ return `'${source}'`;
1041
+ return `'${loaded.get(origin)?.relativePath ?? origin}' (re-exported by '${source}')`;
1001
1042
  }
1002
1043
  export function moduleInterfaceIdentity(interface_, extensions = []) {
1003
1044
  const node = (kind, parts = []) => (`${kind.length}:${kind}${parts.map((part) => `${part.length}:${part}`).join("")}`);
1004
1045
  const typeMap = (values) => node("type-map", [...values]
1005
- .sort(([left], [right]) => left.localeCompare(right))
1046
+ .sort(([left], [right]) => byCodeUnit(left, right))
1006
1047
  .map(([name, type]) => node("type-entry", [name, analysisTypeIdentity(type)])));
1007
1048
  const names = (values) => node("names", [...values].sort());
1008
1049
  const types = (values) => node("types", values.map(analysisTypeIdentity));
1009
1050
  const namedTypes = node("named-types", [...interface_.namedTypes]
1010
- .sort(([left], [right]) => left.localeCompare(right))
1051
+ .sort(([left], [right]) => byCodeUnit(left, right))
1011
1052
  .map(([name, fields]) => node("named-type", [name, typeMap(fields)])));
1012
1053
  const namedTypeReadonlyFields = node("named-type-readonly-fields", [...(interface_.namedTypeReadonlyFields ?? new Map())]
1013
- .sort(([left], [right]) => left.localeCompare(right))
1054
+ .sort(([left], [right]) => byCodeUnit(left, right))
1014
1055
  .map(([name, fields]) => node("named-type-readonly", [name, names(fields)])));
1015
1056
  const namedTypeIdentities = node("named-type-identities", [...interface_.namedTypeIdentities]
1016
- .sort(([left], [right]) => left.localeCompare(right))
1057
+ .sort(([left], [right]) => byCodeUnit(left, right))
1017
1058
  .map(([name, identity]) => node("named-type-identity", [name, identity])));
1018
1059
  const namedTypeBases = node("named-type-bases", [...(interface_.namedTypeBases ?? new Map())]
1019
- .sort(([left], [right]) => left.localeCompare(right))
1060
+ .sort(([left], [right]) => byCodeUnit(left, right))
1020
1061
  .map(([name, base]) => node("named-type-base", [name, analysisTypeIdentity(base)])));
1021
1062
  // D55 rule 120, and batch M's lesson one layer out: a dependent compiled
1022
1063
  // against the parameter list, the bounds, *and* the template's field types.
@@ -1024,7 +1065,7 @@ export function moduleInterfaceIdentity(interface_, extensions = []) {
1024
1065
  // bound that does not enter this hash is a constraint that silently
1025
1066
  // disappears from every module already built against it.
1026
1067
  const genericTypes = node("generic-types", [...(interface_.genericTypes ?? new Map())]
1027
- .sort(([left], [right]) => left.localeCompare(right))
1068
+ .sort(([left], [right]) => byCodeUnit(left, right))
1028
1069
  .map(([name, info]) => node("generic-type", [
1029
1070
  name,
1030
1071
  info.identity,
@@ -1034,10 +1075,10 @@ export function moduleInterfaceIdentity(interface_, extensions = []) {
1034
1075
  names(info.readonlyFields ?? new Set()),
1035
1076
  ])));
1036
1077
  const enums = node("enums", [...interface_.enums]
1037
- .sort(([left], [right]) => left.localeCompare(right))
1078
+ .sort(([left], [right]) => byCodeUnit(left, right))
1038
1079
  .map(([name, info]) => node("enum", [name, info.identity, names(info.members)])));
1039
1080
  const classes = node("classes", [...interface_.classes]
1040
- .sort(([left], [right]) => left.localeCompare(right))
1081
+ .sort(([left], [right]) => byCodeUnit(left, right))
1041
1082
  .map(([name, info]) => node("class", [
1042
1083
  name,
1043
1084
  info.identity ?? "",
@@ -1075,14 +1116,14 @@ export function moduleInterfaceIdentity(interface_, extensions = []) {
1075
1116
  return `${value.length}:${value}`;
1076
1117
  };
1077
1118
  const extensionExports = node("extension-exports", [...interface_.extensionExports]
1078
- .sort(([left], [right]) => left.localeCompare(right))
1119
+ .sort(([left], [right]) => byCodeUnit(left, right))
1079
1120
  .map(([extensionId, values]) => {
1080
1121
  const identify = extensionOwners.get(extensionId)?.inspection?.interfaceExportIdentity;
1081
1122
  if (!identify) {
1082
1123
  throw new Error(`Compiler extension '${extensionId}' exports cross-module interface data without an interfaceExportIdentity contract`);
1083
1124
  }
1084
1125
  const entries = [...values]
1085
- .sort(([left], [right]) => left.localeCompare(right))
1126
+ .sort(([left], [right]) => byCodeUnit(left, right))
1086
1127
  .map(([name, value]) => {
1087
1128
  const identity = identify(name, value);
1088
1129
  if (typeof identity !== "string" || identity.length > 1024 * 1024) {
@@ -1104,10 +1145,10 @@ export function moduleInterfaceIdentity(interface_, extensions = []) {
1104
1145
  enums,
1105
1146
  classes,
1106
1147
  node("reactive", [...interface_.reactiveExports]
1107
- .sort(([left], [right]) => left.localeCompare(right))
1148
+ .sort(([left], [right]) => byCodeUnit(left, right))
1108
1149
  .map(([name, kind]) => node("reactive-entry", [name, kind]))),
1109
1150
  node("re-exports", [...interface_.reExports]
1110
- .sort(([left], [right]) => left.localeCompare(right))
1151
+ .sort(([left], [right]) => byCodeUnit(left, right))
1111
1152
  .map(([name, target]) => node("re-export", [name, target.source, target.imported]))),
1112
1153
  node("tests", interface_.tests.map((item) => `${item.name}\u0000${item.title}`).sort()),
1113
1154
  extensionExports,
@@ -1155,6 +1196,7 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1155
1196
  readonlyFields: new Set(interface_.exports.keys()),
1156
1197
  });
1157
1198
  importHiddenTypeMetadata(interface_, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes);
1199
+ importReachableStandardTypeMetadata(interface_, compilerExtensions, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes);
1158
1200
  continue;
1159
1201
  }
1160
1202
  if (dependency.reExport) {
@@ -1229,6 +1271,7 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1229
1271
  const standard = standardModuleInterface(dependency.source, compilerExtensions);
1230
1272
  if (standard) {
1231
1273
  importInterface(module, dependency, standard, imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, namedTypeBases, genericTypes, typeAliases, enums, classes, extensionImports, failures);
1274
+ importReachableStandardTypeMetadata(standard, compilerExtensions, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes);
1232
1275
  continue;
1233
1276
  }
1234
1277
  const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
@@ -1239,7 +1282,11 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1239
1282
  const target = loaded.get(targetPath);
1240
1283
  if (!target)
1241
1284
  continue;
1242
- importInterface(module, dependency, resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions), imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, namedTypeBases, genericTypes, typeAliases, enums, classes, extensionImports, failures);
1285
+ const targetInterface = resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions);
1286
+ importInterface(module, dependency, targetInterface, imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, namedTypeBases, genericTypes, typeAliases, enums, classes, extensionImports, failures);
1287
+ // The same sink one step sideways: a project module can re-export a
1288
+ // signature returning a standard type it never declares either.
1289
+ importReachableStandardTypeMetadata(targetInterface, compilerExtensions, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes);
1243
1290
  }
1244
1291
  return {
1245
1292
  imports,
@@ -1464,7 +1511,9 @@ async function velarPackageAtRoot(name, root) {
1464
1511
  if (extname(entryPath) !== ".vel")
1465
1512
  throw new Error("'velar.entry' must point to a .vel source file");
1466
1513
  const targets = packageTargets(manifest.velar?.targets);
1467
- const requiredCapabilities = packageRequiredCapabilities(manifest.velar?.requires);
1514
+ const requires = packageRequiresFields(manifest.velar?.requires);
1515
+ const requiredCapabilities = packageRequiredCapabilities(requires);
1516
+ const requiredLanguage = packageRequiredLanguage(requires);
1468
1517
  return {
1469
1518
  name,
1470
1519
  root,
@@ -1472,6 +1521,7 @@ async function velarPackageAtRoot(name, root) {
1472
1521
  resources: packageResources(name, root, manifest.velar.resources, manifest.exports),
1473
1522
  targets,
1474
1523
  requiredCapabilities,
1524
+ requiredLanguage,
1475
1525
  };
1476
1526
  }
1477
1527
  const velarPackageTargets = new Set(["core", "node", "web", "desktop"]);
@@ -1490,14 +1540,29 @@ function packageTargets(value) {
1490
1540
  }
1491
1541
  return targets;
1492
1542
  }
1493
- function packageRequiredCapabilities(value) {
1543
+ /**
1544
+ * D90 R13 asks the 'velar' section for an optional language generation. It is
1545
+ * spelled 'velar.requires.language' because 'velar.requires' is already the
1546
+ * package's "what I need from the toolchain" block: the language generation is
1547
+ * one more thing it needs, and it belongs next to 'capabilities' rather than
1548
+ * beside 'entry' and 'targets', which describe what the package *is*.
1549
+ *
1550
+ * The section is validated once, here, so that the readers below take an object
1551
+ * this function has already proved rather than an `unknown` each of them would
1552
+ * have to re-check.
1553
+ */
1554
+ function packageRequiresFields(value) {
1494
1555
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
1495
1556
  throw new Error("'velar.requires' must be an object");
1496
1557
  }
1497
1558
  const fields = value;
1498
- const unknown = Object.keys(fields).filter((field) => field !== "capabilities");
1499
- if (unknown.length > 0)
1500
- throw new Error(`'velar.requires' has unknown field '${unknown[0]}'`);
1559
+ const unknown = Object.keys(fields).filter((field) => field !== "capabilities" && field !== "language");
1560
+ if (unknown.length > 0) {
1561
+ throw new Error(`'velar.requires' has unknown field '${unknown[0]}'; the supported fields are 'capabilities' and 'language'`);
1562
+ }
1563
+ return fields;
1564
+ }
1565
+ function packageRequiredCapabilities(fields) {
1501
1566
  const capabilities = fields.capabilities;
1502
1567
  if (!Array.isArray(capabilities) || capabilities.length > 16) {
1503
1568
  throw new Error("'velar.requires.capabilities' must be a list with at most 16 entries");
@@ -1513,7 +1578,103 @@ function packageRequiredCapabilities(value) {
1513
1578
  }
1514
1579
  return required;
1515
1580
  }
1581
+ /**
1582
+ * D90 R13: the language generation this toolchain implements is VELAR_VERSION
1583
+ * without its patch component — a patch release never moves the language.
1584
+ */
1585
+ const TOOLCHAIN_LANGUAGE_GENERATION = VELAR_VERSION.split(".").slice(0, 2).join(".");
1586
+ const languageGenerationPattern = /^(0|[1-9][0-9]{0,3})\.(0|[1-9][0-9]{0,3})$/u;
1587
+ function parseLanguageGeneration(text) {
1588
+ const match = languageGenerationPattern.exec(text);
1589
+ if (match === null)
1590
+ return null;
1591
+ return { major: Number(match[1]), minor: Number(match[2]) };
1592
+ }
1593
+ function compareLanguageGenerations(left, right) {
1594
+ return left.major !== right.major ? left.major - right.major : left.minor - right.minor;
1595
+ }
1596
+ /**
1597
+ * D90 R13: reads the optional 'velar.requires.language' range. The grammar is
1598
+ * language-owned and deliberately small — one or two whitespace-separated
1599
+ * clauses over `<major>.<minor>`, because a package declares the *generation*
1600
+ * it was written against and a patch component would promise something the
1601
+ * language does not track.
1602
+ */
1603
+ function packageRequiredLanguage(fields) {
1604
+ const declared = fields.language;
1605
+ if (declared === undefined)
1606
+ return null;
1607
+ const malformed = new Error("'velar.requires.language' must be a language generation such as '0.12' or a range such as '>=0.11 <0.14'");
1608
+ if (typeof declared !== "string")
1609
+ throw malformed;
1610
+ // The clauses carry the meaning; the whitespace between them does not. A
1611
+ // manifest is hand-edited JSON, and an invisible trailing space is not a
1612
+ // reason to tell an author that '0.12 ' is not a generation.
1613
+ const text = declared.trim().split(/\s+/u).join(" ");
1614
+ const clauses = text.split(" ");
1615
+ if (clauses.length > 2)
1616
+ throw malformed;
1617
+ let lower = null;
1618
+ let upper = null;
1619
+ for (const clause of clauses) {
1620
+ const operator = /^(>=|<=|>|<)/u.exec(clause)?.[0] ?? "";
1621
+ const generation = parseLanguageGeneration(clause.slice(operator.length));
1622
+ if (generation === null)
1623
+ throw malformed;
1624
+ if (operator === ">=" || operator === ">") {
1625
+ if (lower !== null)
1626
+ throw malformed;
1627
+ lower = { generation, inclusive: operator === ">=" };
1628
+ }
1629
+ else if (operator === "<=" || operator === "<") {
1630
+ if (upper !== null)
1631
+ throw malformed;
1632
+ upper = { generation, inclusive: operator === "<=" };
1633
+ }
1634
+ else {
1635
+ // A bare generation is exactly that generation — it is both bounds at
1636
+ // once, so it never combines with another clause.
1637
+ if (clauses.length !== 1)
1638
+ throw malformed;
1639
+ lower = { generation, inclusive: true };
1640
+ upper = { generation, inclusive: true };
1641
+ }
1642
+ }
1643
+ if (lower !== null && upper !== null) {
1644
+ // A range that admits no generation at all is a typo, not a requirement.
1645
+ const order = compareLanguageGenerations(lower.generation, upper.generation);
1646
+ if (order > 0 || (order === 0 && !(lower.inclusive && upper.inclusive)))
1647
+ throw malformed;
1648
+ }
1649
+ return { text, lower, upper };
1650
+ }
1651
+ function languageRangeAdmits(range, generation) {
1652
+ if (range.lower !== null) {
1653
+ const order = compareLanguageGenerations(generation, range.lower.generation);
1654
+ if (order < 0 || (order === 0 && !range.lower.inclusive))
1655
+ return false;
1656
+ }
1657
+ if (range.upper !== null) {
1658
+ const order = compareLanguageGenerations(generation, range.upper.generation);
1659
+ if (order > 0 || (order === 0 && !range.upper.inclusive))
1660
+ return false;
1661
+ }
1662
+ return true;
1663
+ }
1516
1664
  function assertVelarPackageCompatibility(package_, target, capabilities) {
1665
+ // D90 R13: the generation comes first. A package written for another
1666
+ // generation of the language is not a package whose target list and
1667
+ // capability list can be trusted, and "it belongs to the previous language"
1668
+ // is the more useful thing for its author to hear.
1669
+ const declaredLanguage = package_.requiredLanguage;
1670
+ if (declaredLanguage !== null) {
1671
+ const current = parseLanguageGeneration(TOOLCHAIN_LANGUAGE_GENERATION);
1672
+ if (current === null)
1673
+ throw new Error(`VelarScript toolchain version '${VELAR_VERSION}' is not a language generation`);
1674
+ if (!languageRangeAdmits(declaredLanguage, current)) {
1675
+ throw new Error(`package '${package_.name}' requires VelarScript language ${declaredLanguage.text}; this toolchain implements ${TOOLCHAIN_LANGUAGE_GENERATION}; install a release of '${package_.name}' published for ${TOOLCHAIN_LANGUAGE_GENERATION}, or run the toolchain the package asks for — its sources are not wrong, they belong to another generation of the language`);
1676
+ }
1677
+ }
1517
1678
  if (!package_.targets.includes(target)) {
1518
1679
  throw new Error(`package '${package_.name}' does not support the '${target}' target; supported targets: ${package_.targets.join(", ")}`);
1519
1680
  }
@@ -1541,8 +1702,9 @@ function packageResources(name, root, value, exports) {
1541
1702
  }
1542
1703
  const fields = declaration;
1543
1704
  const unknown = Object.keys(fields).filter((field) => field !== "path" && field !== "type");
1544
- if (unknown.length > 0)
1545
- throw new Error(`'velar.resources.${subpath}' has unknown field '${unknown[0]}'`);
1705
+ if (unknown.length > 0) {
1706
+ throw new Error(`'velar.resources.${subpath}' has unknown field '${unknown[0]}'; the supported fields are 'path' and 'type'`);
1707
+ }
1546
1708
  if (fields.type !== "json")
1547
1709
  throw new Error(`'velar.resources.${subpath}.type' must be 'json'`);
1548
1710
  if (typeof fields.path !== "string" || fields.path === "" || isAbsolute(fields.path)
@@ -1724,6 +1886,143 @@ function importHiddenTypeMetadata(interface_, namedTypes, namedTypeReadonlyField
1724
1886
  classes.set(info.identity, info);
1725
1887
  }
1726
1888
  }
1889
+ /** The type identities a value type mentions, at any depth. */
1890
+ function collectTypeIdentities(type, into) {
1891
+ switch (type.kind) {
1892
+ case "named":
1893
+ if (type.identity)
1894
+ into.add(type.identity);
1895
+ if (type.application) {
1896
+ into.add(type.application.declaration);
1897
+ for (const argument of type.application.arguments)
1898
+ collectTypeIdentities(argument, into);
1899
+ }
1900
+ return;
1901
+ case "class":
1902
+ case "classConstructor":
1903
+ case "enum":
1904
+ case "enumMember":
1905
+ case "enumObject":
1906
+ if (type.identity)
1907
+ into.add(type.identity);
1908
+ return;
1909
+ case "typeObject":
1910
+ if (type.value)
1911
+ collectTypeIdentities(type.value, into);
1912
+ return;
1913
+ case "optional":
1914
+ collectTypeIdentities(type.inner, into);
1915
+ return;
1916
+ case "list":
1917
+ case "set":
1918
+ collectTypeIdentities(type.element, into);
1919
+ return;
1920
+ case "map":
1921
+ collectTypeIdentities(type.key, into);
1922
+ collectTypeIdentities(type.value, into);
1923
+ return;
1924
+ case "record":
1925
+ case "promise":
1926
+ case "runtimeType":
1927
+ collectTypeIdentities(type.value, into);
1928
+ return;
1929
+ case "object":
1930
+ for (const field of type.fields.values())
1931
+ collectTypeIdentities(field, into);
1932
+ return;
1933
+ case "function":
1934
+ case "action":
1935
+ case "intrinsic":
1936
+ for (const parameter of type.parameters)
1937
+ collectTypeIdentities(parameter, into);
1938
+ if (type.rest)
1939
+ collectTypeIdentities(type.rest, into);
1940
+ collectTypeIdentities(type.result, into);
1941
+ return;
1942
+ case "extension":
1943
+ for (const property of type.properties.values())
1944
+ collectTypeIdentities(property, into);
1945
+ for (const argument of type.arguments)
1946
+ collectTypeIdentities(argument, into);
1947
+ return;
1948
+ case "union":
1949
+ for (const member of type.members)
1950
+ collectTypeIdentities(member, into);
1951
+ return;
1952
+ default:
1953
+ }
1954
+ }
1955
+ const standardTypeOwnerIndexes = new WeakMap();
1956
+ /** Which standard module declares each standard type identity. */
1957
+ function standardTypeOwners(extensions) {
1958
+ const cached = standardTypeOwnerIndexes.get(extensions);
1959
+ if (cached)
1960
+ return cached;
1961
+ const owners = new Map();
1962
+ for (const interface_ of standardModuleInterfaces(extensions).values()) {
1963
+ for (const identity of interface_.namedTypeIdentities.values())
1964
+ if (!owners.has(identity))
1965
+ owners.set(identity, interface_);
1966
+ for (const info of interface_.genericTypes?.values() ?? [])
1967
+ if (!owners.has(info.identity))
1968
+ owners.set(info.identity, interface_);
1969
+ for (const info of interface_.enums.values())
1970
+ if (!owners.has(info.identity))
1971
+ owners.set(info.identity, interface_);
1972
+ for (const info of interface_.classes.values())
1973
+ if (info.identity && !owners.has(info.identity))
1974
+ owners.set(info.identity, interface_);
1975
+ }
1976
+ standardTypeOwnerIndexes.set(extensions, owners);
1977
+ return owners;
1978
+ }
1979
+ /**
1980
+ * A standard module's signatures routinely hand back a type another standard
1981
+ * module declares — `velar/fs`'s `readBytes` returns a `Bytes` owned by
1982
+ * `velar/binary`, `velar/serve`'s `Request.cancellation` is a `Cancellation`
1983
+ * owned by `velar/task`. The field table for such a type travels with its
1984
+ * declaring module, so importing only the module the author wrote left the
1985
+ * analyzer holding a named type it knew no members of, and it reported the
1986
+ * false fact that the type has no such field; adding an otherwise unused
1987
+ * import of the declaring module was the only way to make correct code
1988
+ * compile. Import the hidden metadata of every standard module reachable
1989
+ * through the imported module's own declared signatures instead.
1990
+ *
1991
+ * Only identity-keyed metadata travels, so nothing is bound to a local name
1992
+ * the author did not write — `Bytes` gains its members, and stays unspellable
1993
+ * until `velar/binary` is imported for real.
1994
+ */
1995
+ function importReachableStandardTypeMetadata(interface_, extensions, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes) {
1996
+ const owners = standardTypeOwners(extensions);
1997
+ if (owners.size === 0)
1998
+ return;
1999
+ const reached = new Set();
2000
+ const seed = (source) => {
2001
+ for (const type of source.exports.values())
2002
+ collectTypeIdentities(type, reached);
2003
+ for (const fields of source.namedTypes.values())
2004
+ for (const type of fields.values())
2005
+ collectTypeIdentities(type, reached);
2006
+ for (const base of source.namedTypeBases?.values() ?? [])
2007
+ collectTypeIdentities(base, reached);
2008
+ for (const info of source.genericTypes?.values() ?? [])
2009
+ for (const type of info.fields.values())
2010
+ collectTypeIdentities(type, reached);
2011
+ };
2012
+ seed(interface_);
2013
+ const visited = new Set([interface_]);
2014
+ // Transitive: a reachable type's own fields may name a third module's type.
2015
+ // A Set iterator visits entries added while it runs, and each owner is
2016
+ // seeded at most once, so this terminates at the number of standard modules.
2017
+ for (const identity of reached) {
2018
+ const owner = owners.get(identity);
2019
+ if (!owner || visited.has(owner))
2020
+ continue;
2021
+ visited.add(owner);
2022
+ importHiddenTypeMetadata(owner, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes);
2023
+ seed(owner);
2024
+ }
2025
+ }
1727
2026
  function importInterface(module, dependency, interface_, imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, namedTypeBases, genericTypes, typeAliases, enums, classes, extensionImports, failures) {
1728
2027
  const aliases = new Map(dependency.specifiers
1729
2028
  .filter((specifier) => !specifier.namespace && specifier.imported !== "default")