@velarscript/cli 0.14.1 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/project.js CHANGED
@@ -12,6 +12,7 @@ import { hostErrorMessage, isHostErrorCode } from "./host-error.js";
12
12
  import { canonicalizePotentialPath } from "./canonical-path.js";
13
13
  import { byCodeUnit } from "./stable-order.js";
14
14
  import { VELAR_VERSION } from "./version.js";
15
+ import { loadVelarLibraryArtifact, packageStableModulePath, rebaseModuleInterfaceIdentities, } from "./library-artifact.js";
15
16
  const MAX_PROJECT_RESOURCES = 1024;
16
17
  const MAX_JSON_RESOURCE_BYTES = 4 * 1024 * 1024;
17
18
  function missingExportMessage(source, name) {
@@ -65,6 +66,7 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
65
66
  const interfaceCache = new Map();
66
67
  const velarPackages = new Map();
67
68
  const velarImports = new Map();
69
+ const velarArtifactInterfaces = new Map();
68
70
  const resources = new Map();
69
71
  const resourceImports = new Map();
70
72
  const unsafeCssOwners = new Map();
@@ -342,17 +344,22 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
342
344
  continue;
343
345
  }
344
346
  try {
345
- const package_ = await resolveVelarSourcePackage(dependency.source, inputPath);
346
- assertVelarPackageCompatibility(package_, packageTarget, packageCapabilities);
347
+ const package_ = await resolveVelarSourcePackage(dependency.source, inputPath, packageTarget, packageCapabilities);
347
348
  const existing = velarPackages.get(package_.name);
348
349
  if (existing && existing.root !== package_.root) {
349
350
  recordResolution(inputPath, dependency.source, "VEL6002", `VelarScript package '${package_.name}' resolves to multiple installed versions; use one package instance per application build`);
350
351
  continue;
351
352
  }
352
353
  velarPackages.set(package_.name, package_);
353
- velarImports.set(projectImportKey(inputPath, dependency.source), package_.entryPath);
354
- importOrigins.set(package_.entryPath, { importer: inputPath, source: dependency.source });
355
- enqueue({ inputPath: package_.entryPath, package: package_ });
354
+ const importKey = projectImportKey(inputPath, dependency.source);
355
+ if (package_.artifact) {
356
+ velarArtifactInterfaces.set(importKey, package_.artifact.moduleInterface);
357
+ }
358
+ else {
359
+ velarImports.set(importKey, package_.entryPath);
360
+ importOrigins.set(package_.entryPath, { importer: inputPath, source: dependency.source });
361
+ enqueue({ inputPath: package_.entryPath, package: package_ });
362
+ }
356
363
  }
357
364
  catch (error) {
358
365
  if (error instanceof JavaScriptOnlyPackageError) {
@@ -410,7 +417,8 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
410
417
  }
411
418
  let compiledModules = 0;
412
419
  let reusedModules = 0;
413
- for (const group of dependencyFirstCompilationGroups(loaded, velarImports, compilerExtensions)) {
420
+ const compilationGroups = dependencyFirstCompilationGroups(loaded, velarImports, compilerExtensions);
421
+ for (const group of compilationGroups) {
414
422
  const reusable = group.every((module) => !affected.has(module.inputPath));
415
423
  if (reusable) {
416
424
  for (const module of group) {
@@ -436,8 +444,8 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
436
444
  interfaceCache.delete(module.inputPath);
437
445
  const nextResults = new Map();
438
446
  for (const module of group) {
439
- const analysis = await createAnalysisContext(module, loaded, velarImports, failures, notices, declarationCache, externalTypeDependencies, interfaceCache, compiledInterfaces, compilerExtensions);
440
- const result = importedReactiveAssignmentDiagnostics(compile(module.text, {
447
+ const analysis = await createAnalysisContext(module, loaded, velarImports, velarArtifactInterfaces, failures, notices, declarationCache, externalTypeDependencies, interfaceCache, compiledInterfaces, compilerExtensions);
448
+ const compiled = importedReactiveAssignmentDiagnostics(compile(module.text, {
441
449
  path: module.inputPath,
442
450
  analysis,
443
451
  extensions: compilerExtensions,
@@ -445,6 +453,9 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
445
453
  sharedRuntimeModules: true,
446
454
  ...(options.exportTestFunctions ? { exportFunctions: new Set(module.inspection.moduleInterface.tests.map((item) => item.name)) } : {}),
447
455
  }), analysis.reactiveImports ?? new Map());
456
+ const result = module.package === null
457
+ ? compiled
458
+ : { ...compiled, moduleInterface: stableSourcePackageInterface(module, compiled.moduleInterface, loaded) };
448
459
  nextResults.set(module.inputPath, { inputPath: module.inputPath, relativePath: module.relativePath, result });
449
460
  }
450
461
  passResults = nextResults;
@@ -477,6 +488,19 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
477
488
  // machine's `LC_ALL`. Order by code unit over the POSIX-normalized
478
489
  // relative path instead.
479
490
  modules.sort((left, right) => byCodeUnit(left.relativePath, right.relativePath));
491
+ // Resolve every public barrel after the final SCC pass. A frozen library
492
+ // serializes this map, so source mode and artifact mode expose the same
493
+ // flattened contract even when the package entry only re-exports names.
494
+ // Walk the same dependency-first groups used for compilation: clearing the
495
+ // cache and starting at the entry would otherwise recurse through one host
496
+ // frame per module while resolving a legal 3000-module line.
497
+ interfaceCache.clear();
498
+ const moduleInterfaces = new Map();
499
+ for (const group of compilationGroups) {
500
+ for (const module of group) {
501
+ moduleInterfaces.set(module.inputPath, resolvedModuleInterface(module, loaded, velarImports, velarArtifactInterfaces, interfaceCache, compiledInterfaces, compilerExtensions));
502
+ }
503
+ }
480
504
  if (framework?.host.validateProject) {
481
505
  try {
482
506
  const messages = framework.host.validateProject({
@@ -503,10 +527,12 @@ export async function compileProjectEntries(entries, primaryEntry, overrides = n
503
527
  framework,
504
528
  capabilities,
505
529
  modules,
530
+ moduleInterfaces,
506
531
  failures: uniqueFailures(failures),
507
532
  notices: uniqueNotices(notices),
508
533
  velarPackages: [...velarPackages.values()],
509
534
  velarImports,
535
+ velarArtifactInterfaces,
510
536
  resources: [...resources.values()],
511
537
  resourceImports,
512
538
  externalTypeDependencies,
@@ -1154,7 +1180,7 @@ export function moduleInterfaceIdentity(interface_, extensions = []) {
1154
1180
  extensionExports,
1155
1181
  ]);
1156
1182
  }
1157
- async function createAnalysisContext(module, loaded, velarImports, failures, notices, declarationCache, externalTypeDependencies, interfaceCache, compiledInterfaces, compilerExtensions) {
1183
+ async function createAnalysisContext(module, loaded, velarImports, artifactInterfaces, failures, notices, declarationCache, externalTypeDependencies, interfaceCache, compiledInterfaces, compilerExtensions) {
1158
1184
  const imports = new Map();
1159
1185
  const dynamicImports = new Map();
1160
1186
  const reactiveImports = new Map();
@@ -1175,15 +1201,24 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1175
1201
  extensionModules.set(extensionId, values);
1176
1202
  }
1177
1203
  }
1204
+ for (const interface_ of new Set(artifactInterfaces.values())) {
1205
+ for (const [extensionId, data] of interface_.extensionData) {
1206
+ const values = extensionModules.get(extensionId) ?? [];
1207
+ values.push(data);
1208
+ extensionModules.set(extensionId, values);
1209
+ }
1210
+ }
1178
1211
  for (const dependency of module.inspection.dependencies) {
1179
1212
  if (dependency.dynamic) {
1213
+ const artifact = artifactInterfaces.get(projectImportKey(module.inputPath, dependency.source));
1180
1214
  const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
1181
1215
  ? resolve(dirname(module.inputPath), dependency.source)
1182
1216
  : null;
1183
1217
  const target = targetPath ? loaded.get(targetPath) : null;
1184
- if (!target)
1218
+ const interface_ = artifact
1219
+ ?? (target ? resolvedModuleInterface(target, loaded, velarImports, artifactInterfaces, interfaceCache, compiledInterfaces, compilerExtensions) : null);
1220
+ if (!interface_)
1185
1221
  continue;
1186
- const interface_ = resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions);
1187
1222
  if (interface_.reactiveExports.size > 0) {
1188
1223
  failures.push({
1189
1224
  path: module.inputPath,
@@ -1200,12 +1235,13 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1200
1235
  continue;
1201
1236
  }
1202
1237
  if (dependency.reExport) {
1203
- const interface_ = standardModuleInterface(dependency.source, compilerExtensions) ?? (() => {
1238
+ const interface_ = artifactInterfaces.get(projectImportKey(module.inputPath, dependency.source))
1239
+ ?? standardModuleInterface(dependency.source, compilerExtensions) ?? (() => {
1204
1240
  const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
1205
1241
  ? resolve(dirname(module.inputPath), dependency.source)
1206
1242
  : velarImports.get(projectImportKey(module.inputPath, dependency.source));
1207
1243
  const target = targetPath ? loaded.get(targetPath) : null;
1208
- return target ? resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions) : null;
1244
+ return target ? resolvedModuleInterface(target, loaded, velarImports, artifactInterfaces, interfaceCache, compiledInterfaces, compilerExtensions) : null;
1209
1245
  })();
1210
1246
  if (interface_) {
1211
1247
  for (const specifier of dependency.specifiers) {
@@ -1274,6 +1310,12 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1274
1310
  importReachableStandardTypeMetadata(standard, compilerExtensions, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes);
1275
1311
  continue;
1276
1312
  }
1313
+ const artifact = artifactInterfaces.get(projectImportKey(module.inputPath, dependency.source));
1314
+ if (artifact) {
1315
+ importInterface(module, dependency, artifact, imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, namedTypeBases, genericTypes, typeAliases, enums, classes, extensionImports, failures);
1316
+ importReachableStandardTypeMetadata(artifact, compilerExtensions, namedTypes, namedTypeReadonlyFields, namedTypeBases, genericTypes, enums, classes);
1317
+ continue;
1318
+ }
1277
1319
  const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
1278
1320
  ? resolve(dirname(module.inputPath), dependency.source)
1279
1321
  : velarImports.get(projectImportKey(module.inputPath, dependency.source));
@@ -1282,7 +1324,7 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1282
1324
  const target = loaded.get(targetPath);
1283
1325
  if (!target)
1284
1326
  continue;
1285
- const targetInterface = resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions);
1327
+ const targetInterface = resolvedModuleInterface(target, loaded, velarImports, artifactInterfaces, interfaceCache, compiledInterfaces, compilerExtensions);
1286
1328
  importInterface(module, dependency, targetInterface, imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, namedTypeBases, genericTypes, typeAliases, enums, classes, extensionImports, failures);
1287
1329
  // The same sink one step sideways: a project module can re-export a
1288
1330
  // signature returning a standard type it never declares either.
@@ -1305,11 +1347,12 @@ async function createAnalysisContext(module, loaded, velarImports, failures, not
1305
1347
  resources: module.resourceContents,
1306
1348
  };
1307
1349
  }
1308
- function resolvedModuleInterface(module, loaded, velarImports, cache, compiledInterfaces, compilerExtensions) {
1350
+ function resolvedModuleInterface(module, loaded, velarImports, artifactInterfaces, cache, compiledInterfaces, compilerExtensions) {
1309
1351
  const cached = cache.get(module.inputPath);
1310
1352
  if (cached)
1311
1353
  return cached;
1312
- const own = compiledInterfaces.get(module.inputPath) ?? module.inspection.moduleInterface;
1354
+ const rawOwn = compiledInterfaces.get(module.inputPath) ?? module.inspection.moduleInterface;
1355
+ const own = module.package === null ? rawOwn : stableSourcePackageInterface(module, rawOwn, loaded);
1313
1356
  const exports = new Map(own.exports);
1314
1357
  const mutableExports = new Set(own.mutableExports);
1315
1358
  const reactiveExports = new Map(own.reactiveExports);
@@ -1345,7 +1388,8 @@ function resolvedModuleInterface(module, loaded, velarImports, cache, compiledIn
1345
1388
  for (const dependency of module.inspection.dependencies) {
1346
1389
  if (dependency.javascript)
1347
1390
  continue;
1348
- let dependencyInterface = standardModuleInterface(dependency.source, compilerExtensions);
1391
+ let dependencyInterface = artifactInterfaces.get(projectImportKey(module.inputPath, dependency.source))
1392
+ ?? standardModuleInterface(dependency.source, compilerExtensions);
1349
1393
  if (!dependencyInterface) {
1350
1394
  const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
1351
1395
  ? resolve(dirname(module.inputPath), dependency.source)
@@ -1353,7 +1397,7 @@ function resolvedModuleInterface(module, loaded, velarImports, cache, compiledIn
1353
1397
  const target = targetPath ? loaded.get(targetPath) : null;
1354
1398
  if (!target)
1355
1399
  continue;
1356
- dependencyInterface = resolvedModuleInterface(target, loaded, velarImports, cache, compiledInterfaces, compilerExtensions);
1400
+ dependencyInterface = resolvedModuleInterface(target, loaded, velarImports, artifactInterfaces, cache, compiledInterfaces, compilerExtensions);
1357
1401
  }
1358
1402
  const aliases = new Map(dependency.specifiers
1359
1403
  .filter((specifier) => !specifier.namespace && specifier.imported !== "default")
@@ -1463,6 +1507,24 @@ function resolvedModuleInterface(module, loaded, velarImports, cache, compiledIn
1463
1507
  }
1464
1508
  return resolved;
1465
1509
  }
1510
+ /**
1511
+ * Source fallback and frozen artifacts must agree on nominal identities.
1512
+ * Absolute installation paths would make one record or class a different
1513
+ * type on every machine and would leak a publisher path into the artifact.
1514
+ */
1515
+ function stableSourcePackageInterface(module, interface_, loaded) {
1516
+ if (module.package === null)
1517
+ return interface_;
1518
+ return rebaseModuleInterfaceIdentities(interface_, [...loaded.values()].flatMap((candidate) => {
1519
+ const owner = candidate.package;
1520
+ if (owner === null)
1521
+ return [];
1522
+ return [{
1523
+ physical: candidate.inputPath,
1524
+ logical: packageStableModulePath(owner.name, owner.version, relative(owner.root, candidate.inputPath)),
1525
+ }];
1526
+ }));
1527
+ }
1466
1528
  /**
1467
1529
  * MOD-U7: the package is installed and its manifest reads fine — it is simply
1468
1530
  * a JavaScript package. That is the mirror image of BRG-U2 (a VelarScript
@@ -1472,7 +1534,7 @@ function resolvedModuleInterface(module, loaded, velarImports, cache, compiledIn
1472
1534
  */
1473
1535
  class JavaScriptOnlyPackageError extends Error {
1474
1536
  }
1475
- async function resolveVelarSourcePackage(source, importerPath) {
1537
+ async function resolveVelarSourcePackage(source, importerPath, target, capabilities) {
1476
1538
  const name = packageNameOf(source);
1477
1539
  if (source !== name)
1478
1540
  throw new Error("package subpaths are not supported; import the package entry by name");
@@ -1480,7 +1542,7 @@ async function resolveVelarSourcePackage(source, importerPath) {
1480
1542
  while (true) {
1481
1543
  const root = join(directory, "node_modules", ...name.split("/"));
1482
1544
  try {
1483
- return await velarPackageAtRoot(name, root);
1545
+ return await velarPackageAtRoot(name, root, target, capabilities);
1484
1546
  }
1485
1547
  catch (error) {
1486
1548
  if (error instanceof SyntaxError)
@@ -1494,11 +1556,12 @@ async function resolveVelarSourcePackage(source, importerPath) {
1494
1556
  directory = parent;
1495
1557
  }
1496
1558
  }
1497
- async function velarPackageAtRoot(name, root) {
1559
+ async function velarPackageAtRoot(name, root, target, capabilities) {
1498
1560
  const manifest = JSON.parse(await readBoundedText(join(root, "package.json"), 1024 * 1024, `Package manifest for '${name}'`));
1499
1561
  if (manifest.name !== undefined && manifest.name !== name) {
1500
1562
  throw new Error(`package name is '${String(manifest.name)}', expected '${name}'`);
1501
1563
  }
1564
+ const version = typeof manifest.version === "string" && manifest.version !== "" ? manifest.version : "0.0.0";
1502
1565
  const entry = manifest.velar?.entry;
1503
1566
  if (typeof entry !== "string" || entry.length === 0) {
1504
1567
  throw new JavaScriptOnlyPackageError("package.json must declare 'velar.entry'");
@@ -1514,15 +1577,63 @@ async function velarPackageAtRoot(name, root) {
1514
1577
  const requires = packageRequiresFields(manifest.velar?.requires);
1515
1578
  const requiredCapabilities = packageRequiredCapabilities(requires);
1516
1579
  const requiredLanguage = packageRequiredLanguage(requires);
1517
- return {
1580
+ const package_ = {
1518
1581
  name,
1582
+ version,
1519
1583
  root,
1520
1584
  entryPath,
1521
1585
  resources: packageResources(name, root, manifest.velar.resources, manifest.exports),
1522
1586
  targets,
1523
1587
  requiredCapabilities,
1524
1588
  requiredLanguage,
1589
+ artifact: null,
1525
1590
  };
1591
+ const artifacts = packageArtifactDescriptors(manifest.velar?.artifacts);
1592
+ if (artifacts.size > 0 && manifest.version === undefined)
1593
+ throw new Error("A package declaring 'velar.artifacts' must declare its version");
1594
+ if (target === undefined)
1595
+ return package_;
1596
+ const artifactTarget = artifacts.has(target)
1597
+ ? target
1598
+ : target !== "core" && artifacts.has("core")
1599
+ ? "core"
1600
+ : null;
1601
+ if (artifactTarget !== null) {
1602
+ assertVelarPackageTargetCapabilities(package_, target, capabilities ?? new Set());
1603
+ const artifact = await loadVelarLibraryArtifact({
1604
+ packageRoot: root,
1605
+ packageName: name,
1606
+ packageVersion: version,
1607
+ sourceEntry: entry,
1608
+ descriptor: artifacts.get(artifactTarget),
1609
+ target: artifactTarget,
1610
+ packageExports: manifest.exports,
1611
+ });
1612
+ return { ...package_, artifact };
1613
+ }
1614
+ assertVelarPackageCompatibility(package_, target, capabilities ?? new Set());
1615
+ return package_;
1616
+ }
1617
+ function packageArtifactDescriptors(value) {
1618
+ if (value === undefined)
1619
+ return new Map();
1620
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1621
+ throw new Error("'velar.artifacts' must be an object mapping core or node to a receipt path");
1622
+ }
1623
+ const entries = Object.entries(value);
1624
+ if (entries.length !== 1)
1625
+ throw new Error("Velar library ABI 1 requires exactly one artifact target per package");
1626
+ const artifacts = new Map();
1627
+ for (const [target, descriptor] of entries) {
1628
+ if (target !== "core" && target !== "node")
1629
+ throw new Error(`Velar library ABI 1 does not support artifact target '${target}'; supported targets are core and node`);
1630
+ if (typeof descriptor !== "string" || descriptor === "" || /[\u0000-\u001f\u007f]/u.test(descriptor) || isAbsolute(descriptor) || descriptor.includes("\\")
1631
+ || descriptor.split("/").some((part) => part === "" || part === "." || part === "..")) {
1632
+ throw new Error(`'velar.artifacts.${target}' must be a normalized package-relative receipt path`);
1633
+ }
1634
+ artifacts.set(target, descriptor);
1635
+ }
1636
+ return artifacts;
1526
1637
  }
1527
1638
  const velarPackageTargets = new Set(["core", "node", "web", "desktop"]);
1528
1639
  function packageTargets(value) {
@@ -1675,6 +1786,9 @@ function assertVelarPackageCompatibility(package_, target, capabilities) {
1675
1786
  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
1787
  }
1677
1788
  }
1789
+ assertVelarPackageTargetCapabilities(package_, target, capabilities);
1790
+ }
1791
+ function assertVelarPackageTargetCapabilities(package_, target, capabilities) {
1678
1792
  if (!package_.targets.includes(target)) {
1679
1793
  throw new Error(`package '${package_.name}' does not support the '${target}' target; supported targets: ${package_.targets.join(", ")}`);
1680
1794
  }