@velarscript/cli 0.12.1 → 0.14.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 (144) hide show
  1. package/README.md +5 -13
  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 +186 -106
  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 +71 -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 +8 -7
  50. package/dist/node-application.d.ts.map +1 -1
  51. package/dist/node-application.js +63 -32
  52. package/dist/node-application.js.map +1 -1
  53. package/dist/node-runtime-dependencies.d.ts +3 -0
  54. package/dist/node-runtime-dependencies.d.ts.map +1 -0
  55. package/dist/node-runtime-dependencies.js +32 -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/official-language-server-extensions.d.ts.map +1 -1
  62. package/dist/official-language-server-extensions.js +13 -0
  63. package/dist/official-language-server-extensions.js.map +1 -1
  64. package/dist/ownership-graph.d.ts.map +1 -1
  65. package/dist/ownership-graph.js +155 -38
  66. package/dist/ownership-graph.js.map +1 -1
  67. package/dist/package-manager.d.ts.map +1 -1
  68. package/dist/package-manager.js +4 -1
  69. package/dist/package-manager.js.map +1 -1
  70. package/dist/preview-server.d.ts.map +1 -1
  71. package/dist/preview-server.js +12 -4
  72. package/dist/preview-server.js.map +1 -1
  73. package/dist/production-build.d.ts.map +1 -1
  74. package/dist/production-build.js +24 -6
  75. package/dist/production-build.js.map +1 -1
  76. package/dist/production-verifier.js +3 -0
  77. package/dist/production-verifier.js.map +1 -1
  78. package/dist/project-check.d.ts +14 -1
  79. package/dist/project-check.d.ts.map +1 -1
  80. package/dist/project-check.js +17 -4
  81. package/dist/project-check.js.map +1 -1
  82. package/dist/project-format.d.ts +9 -0
  83. package/dist/project-format.d.ts.map +1 -1
  84. package/dist/project-format.js +13 -0
  85. package/dist/project-format.js.map +1 -1
  86. package/dist/project-semantic.d.ts.map +1 -1
  87. package/dist/project-semantic.js +15 -4
  88. package/dist/project-semantic.js.map +1 -1
  89. package/dist/project.d.ts +25 -0
  90. package/dist/project.d.ts.map +1 -1
  91. package/dist/project.js +341 -42
  92. package/dist/project.js.map +1 -1
  93. package/dist/reproduction.d.ts +18 -0
  94. package/dist/reproduction.d.ts.map +1 -1
  95. package/dist/reproduction.js +47 -6
  96. package/dist/reproduction.js.map +1 -1
  97. package/dist/stable-order.d.ts +17 -0
  98. package/dist/stable-order.d.ts.map +1 -0
  99. package/dist/stable-order.js +19 -0
  100. package/dist/stable-order.js.map +1 -0
  101. package/dist/standard-modules.d.ts +2 -2
  102. package/dist/standard-modules.d.ts.map +1 -1
  103. package/dist/standard-modules.js +2 -2
  104. package/dist/standard-modules.js.map +1 -1
  105. package/dist/static-deployment.d.ts.map +1 -1
  106. package/dist/static-deployment.js +27 -0
  107. package/dist/static-deployment.js.map +1 -1
  108. package/dist/test-runner.d.ts.map +1 -1
  109. package/dist/test-runner.js +196 -100
  110. package/dist/test-runner.js.map +1 -1
  111. package/dist/test-worker.d.ts +64 -0
  112. package/dist/test-worker.d.ts.map +1 -0
  113. package/dist/test-worker.js +161 -0
  114. package/dist/test-worker.js.map +1 -0
  115. package/dist/typescript-declarations.d.ts +1 -1
  116. package/dist/typescript-declarations.d.ts.map +1 -1
  117. package/dist/typescript-declarations.js +233 -21
  118. package/dist/typescript-declarations.js.map +1 -1
  119. package/dist/unowned-errors.d.ts +15 -2
  120. package/dist/unowned-errors.d.ts.map +1 -1
  121. package/dist/unowned-errors.js +13 -2
  122. package/dist/unowned-errors.js.map +1 -1
  123. package/dist/version.d.ts +1 -1
  124. package/dist/version.js +1 -1
  125. package/dist/workspace-index.d.ts.map +1 -1
  126. package/dist/workspace-index.js +42 -17
  127. package/dist/workspace-index.js.map +1 -1
  128. package/package.json +8 -8
  129. package/skill/ai-skill-node.md +91 -22
  130. package/skill/ai-skill-server.md +173 -0
  131. package/skill/ai-skill-web.md +51 -1
  132. package/skill/ai-skill.md +36 -13
  133. package/dist/official-script-language-service.d.ts +0 -2
  134. package/dist/official-script-language-service.d.ts.map +0 -1
  135. package/dist/official-script-language-service.js +0 -10
  136. package/dist/official-script-language-service.js.map +0 -1
  137. package/dist/official-tool-assets.d.ts +0 -3
  138. package/dist/official-tool-assets.d.ts.map +0 -1
  139. package/dist/official-tool-assets.js +0 -60
  140. package/dist/official-tool-assets.js.map +0 -1
  141. package/dist/script-language-service.d.ts +0 -82
  142. package/dist/script-language-service.d.ts.map +0 -1
  143. package/dist/script-language-service.js +0 -18
  144. package/dist/script-language-service.js.map +0 -1
package/dist/cli.js CHANGED
@@ -7,12 +7,10 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
7
7
  }
8
8
  return path;
9
9
  };
10
- import { cp, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
11
- import { createRequire } from "node:module";
12
- import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
13
- import { tmpdir } from "node:os";
10
+ import { copyFile, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
11
+ import { basename, dirname, extname, isAbsolute, join, parse as parsePath, relative, resolve } from "node:path";
12
+ import { homedir, tmpdir } from "node:os";
14
13
  import { fileURLToPath, pathToFileURL } from "node:url";
15
- import { formatSource } from "@velarscript/compiler";
16
14
  import { createVelarProject, parseCreateArguments } from "create-velar";
17
15
  import { projectImportKey } from "./project.js";
18
16
  import { checkResolvedProject, discoverVelarSources, formatCheckOutput, projectTestModules } from "./project-check.js";
@@ -21,10 +19,11 @@ import { runDevServer } from "./dev-server.js";
21
19
  import { nodeApplicationConfig, nodeApplicationEntry, nodeApplicationLauncherSource, runNodeApplication, runNodeDevelopment, } from "./node-application.js";
22
20
  import { createFrameworkArtifacts } from "./framework-host.js";
23
21
  import { resolveVelarProject } from "./config.js";
24
- import { STANDARD_MODULE_ADAPTER_DEPENDENCIES, standardModuleClosure, standardModuleSource, standardModuleSources } from "./standard-modules.js";
22
+ import { standardModuleClosure, standardModuleSource, standardModuleSources } from "./standard-modules.js";
25
23
  import { runTests } from "./test-runner.js";
26
24
  import { runProgram } from "./program-runner.js";
27
- import { buildProductionFramework, writeProductionManifest } from "./production-build.js";
25
+ import { buildProductionFramework, PRODUCTION_MANIFEST_NAME, writeProductionManifest } from "./production-build.js";
26
+ import { formatSourceChecked } from "./format-guard.js";
28
27
  import { VELAR_VERSION } from "./version.js";
29
28
  import { assertRequiredPublicAssets, copyPublicAssets, writeStaticDeployment } from "./static-deployment.js";
30
29
  import { verifyProductionBuild } from "./production-verifier.js";
@@ -37,8 +36,8 @@ import { loadApplicationPackageHost, validateApplicationPackageResult } from "./
37
36
  import { buildLanguageServerTool } from "./language-server-tool.js";
38
37
  import { applyProjectMechanicalFixes } from "./mechanical-fixer.js";
39
38
  import { bundleStandaloneJavaScript, needsStandaloneJavaScriptBundle } from "./standalone-build.js";
40
- import { resolveInstalledPackageRoot } from "./installed-package.js";
41
39
  import { BUILD_STAGING_MARKER } from "./build-staging.js";
40
+ import { writeServerConfigurationDependency, writeWebSocketDependency } from "./node-runtime-dependencies.js";
42
41
  import { assertUniqueEmbeddedModuleOutputs, embeddedModuleFileContents, embeddedModuleOutputPath, VELAR_EMBEDDED_MODULE_MARKER, } from "./embedded-modules.js";
43
42
  import { resourceOutputRelativePath, writeBuildResourcePackageManifests, writeProjectResources } from "./resource-output.js";
44
43
  async function main(arguments_) {
@@ -97,10 +96,11 @@ async function main(arguments_) {
97
96
  core: "ai-skill.md",
98
97
  web: "ai-skill-web.md",
99
98
  node: "ai-skill-node.md",
99
+ server: "ai-skill-server.md",
100
100
  desktop: "ai-skill-desktop.md",
101
101
  });
102
102
  if (rest.length > 1 || files[kind] === undefined) {
103
- process.stderr.write("velar skill: expected core, web, node, or desktop\n");
103
+ process.stderr.write("velar skill: expected core, web, node, server, or desktop\n");
104
104
  return 2;
105
105
  }
106
106
  process.stdout.write(await readFile(new URL(`../skill/${files[kind]}`, import.meta.url), "utf8"));
@@ -338,10 +338,19 @@ async function main(arguments_) {
338
338
  return 1;
339
339
  }
340
340
  const changed = [];
341
+ const unstable = [];
341
342
  try {
342
343
  for (const input of inputs) {
343
344
  const source = await readVelarSourceFile(input);
344
- const formatted = formatSource(source, { extensions: formattingExtensions });
345
+ const { text: formatted, stable } = formatSourceChecked(source, { extensions: formattingExtensions });
346
+ // Formatting is idempotent by contract. A result the formatter would
347
+ // change again is a formatter defect, and writing it would replace a
348
+ // module that compiles with one that may not, so the file keeps the
349
+ // bytes the author wrote and the command reports the defect.
350
+ if (!stable) {
351
+ unstable.push(input);
352
+ continue;
353
+ }
345
354
  if (formatted === source)
346
355
  continue;
347
356
  changed.push(input);
@@ -353,6 +362,12 @@ async function main(arguments_) {
353
362
  process.stderr.write(`velar format: ${hostErrorMessage(error)}\n`);
354
363
  return 1;
355
364
  }
365
+ if (unstable.length > 0) {
366
+ for (const input of unstable) {
367
+ process.stderr.write(`velar format: ${displayPath(input)}: the formatter did not reach a fixed point; the file was left unchanged\n`);
368
+ }
369
+ return 1;
370
+ }
356
371
  if (parsed.check && changed.length > 0) {
357
372
  for (const input of changed)
358
373
  process.stderr.write(`${displayPath(input)} is not formatted\n`);
@@ -381,8 +396,11 @@ async function main(arguments_) {
381
396
  projectConfig = await resolveVelarProject(parsed.input);
382
397
  if (projectConfig.framework)
383
398
  await runDevServer(projectConfig, parsed.port ?? 5173);
384
- else if (nodeApplicationConfig(projectConfig))
385
- await runNodeDevelopment(projectConfig, parsed.port === null ? {} : { port: parsed.port });
399
+ else if (nodeApplicationConfig(projectConfig)) {
400
+ if (parsed.port !== null)
401
+ throw new Error("Node application host and port belong to velar/server configuration; --port is available only to Web and Desktop development servers");
402
+ await runNodeDevelopment(projectConfig);
403
+ }
386
404
  else
387
405
  throw new Error("the project does not declare a Web, Desktop, or Node application target");
388
406
  }
@@ -400,11 +418,7 @@ async function main(arguments_) {
400
418
  }
401
419
  try {
402
420
  const projectConfig = await resolveVelarProject(parsed.input);
403
- const overrides = {
404
- ...(parsed.host === null ? {} : { host: parsed.host }),
405
- ...(parsed.port === null ? {} : { port: parsed.port }),
406
- };
407
- return await runNodeApplication(projectConfig, overrides);
421
+ return await runNodeApplication(projectConfig);
408
422
  }
409
423
  catch (error) {
410
424
  process.stderr.write(`velar serve: ${hostErrorMessage(error)}\n`);
@@ -442,7 +456,7 @@ async function main(arguments_) {
442
456
  }
443
457
  return runTests(projectConfig, parsed.input);
444
458
  }
445
- const parsed = command === "package" ? parsePackageArguments(rest) : parseCommandArguments(rest);
459
+ const parsed = command === "package" ? parsePackageArguments(rest) : parseCommandArguments(rest, command === "build");
446
460
  if (typeof parsed === "string") {
447
461
  process.stderr.write(`velar ${command}: ${parsed}\n`);
448
462
  return 2;
@@ -470,7 +484,13 @@ async function main(arguments_) {
470
484
  }
471
485
  if (command === "check") {
472
486
  const count = checked.compiled.size;
473
- process.stdout.write(`Checked ${count} module${count === 1 ? "" : "s"} from ${displayInput(parsed.input, projectConfig)}\n`);
487
+ // D89: a passing check that carried advisories says how many. Folding them
488
+ // into a silent "checked N modules" would hide the one thing the advisory
489
+ // channel exists to make impossible to miss; the exit code stays 0 either
490
+ // way, because an advisory is not a failure.
491
+ const advisories = checked.advisories.length;
492
+ process.stdout.write(`Checked ${count} module${count === 1 ? "" : "s"} from ${displayInput(parsed.input, projectConfig)}`
493
+ + `${advisories > 0 ? ` — ${advisories} advisor${advisories === 1 ? "y" : "ies"}` : ""}\n`);
474
494
  return 0;
475
495
  }
476
496
  if (command === "package") {
@@ -490,7 +510,7 @@ async function main(arguments_) {
490
510
  if (buildRequests > 1)
491
511
  throw new Error("application package host requested more than one framework build");
492
512
  const outputDirectory = packageFrameworkOutput(projectConfig.root, requestedOutput);
493
- frameworkBuild = writeFrameworkProductionApplication(project, outputDirectory);
513
+ frameworkBuild = writeFrameworkProductionApplication(project, outputDirectory, { forced: false, declared: false });
494
514
  await frameworkBuild;
495
515
  },
496
516
  });
@@ -535,9 +555,15 @@ async function main(arguments_) {
535
555
  return 0;
536
556
  }
537
557
  const outputDirectory = parsed.outputDirectory ? resolve(parsed.outputDirectory) : projectConfig.outDir;
558
+ // `outDir` is the project manifest's own declaration of a directory velar
559
+ // owns, and config.ts already refuses one that is the project root or that
560
+ // overlaps the entry or the public directory. `--out-dir` carries no such
561
+ // declaration, so a path it names has to prove ownership before the build
562
+ // replaces what is there.
563
+ const replacement = { forced: parsed.force, declared: outputDirectory === projectConfig.outDir };
538
564
  if (project.framework) {
539
565
  try {
540
- await writeFrameworkProductionApplication(project, outputDirectory);
566
+ await writeFrameworkProductionApplication(project, outputDirectory, replacement);
541
567
  }
542
568
  catch (error) {
543
569
  process.stderr.write(`velar build: ${hostErrorMessage(error)}\n`);
@@ -549,7 +575,7 @@ async function main(arguments_) {
549
575
  const nodeConfig = nodeApplicationConfig(projectConfig);
550
576
  if (nodeConfig) {
551
577
  try {
552
- await writeNodeProductionApplication(project, outputDirectory, nodeConfig);
578
+ await writeNodeProductionApplication(project, outputDirectory, nodeConfig, replacement);
553
579
  }
554
580
  catch (error) {
555
581
  process.stderr.write(`velar build: ${hostErrorMessage(error)}\n`);
@@ -558,7 +584,14 @@ async function main(arguments_) {
558
584
  process.stdout.write(`Built production Node app -> ${outputDirectory}\n`);
559
585
  return 0;
560
586
  }
561
- const staging = await prepareBuildStaging(outputDirectory);
587
+ let staging;
588
+ try {
589
+ staging = await prepareBuildStaging(outputDirectory, replacement);
590
+ }
591
+ catch (error) {
592
+ process.stderr.write(`velar build: ${hostErrorMessage(error)}\n`);
593
+ return 1;
594
+ }
562
595
  try {
563
596
  assertUniqueEmbeddedModuleOutputs(project.modules.map((module) => ({
564
597
  ownerPath: join(staging, module.relativePath.replace(/\.vel$/, ".js")),
@@ -582,12 +615,12 @@ async function main(arguments_) {
582
615
  process.stdout.write(`Built ${project.modules.length} module${project.modules.length === 1 ? "" : "s"} -> ${outputDirectory}\n`);
583
616
  return 0;
584
617
  }
585
- async function writeFrameworkProductionApplication(project, outputDirectory) {
618
+ async function writeFrameworkProductionApplication(project, outputDirectory, replacement) {
586
619
  if (!project.framework)
587
620
  throw new Error("the checked project has no framework host");
588
621
  const framework = project.framework;
589
622
  await assertRequiredPublicAssets(project.publicRoot, project.projectRoot, framework.host.requiredPublicAssets?.(framework.config) ?? []);
590
- const staging = await prepareBuildStaging(outputDirectory);
623
+ const staging = await prepareBuildStaging(outputDirectory, replacement);
591
624
  try {
592
625
  await copyPublicAssets(project.publicRoot, staging);
593
626
  const production = await buildProductionFramework(project, staging);
@@ -608,9 +641,12 @@ async function writeFrameworkProductionApplication(project, outputDirectory) {
608
641
  throw error;
609
642
  }
610
643
  }
611
- async function writeNodeProductionApplication(project, outputDirectory, config) {
612
- const entry = nodeApplicationEntry(project, config);
613
- const staging = await prepareBuildStaging(outputDirectory);
644
+ /** The receipt a node build leaves in its output; it also proves velar owns the directory. */
645
+ const NODE_BUILD_MANIFEST_NAME = "velar-node.json";
646
+ async function writeNodeProductionApplication(project, outputDirectory, config, replacement) {
647
+ const application = nodeApplicationEntry(project, config);
648
+ const entry = application.entry;
649
+ const staging = await prepareBuildStaging(outputDirectory, replacement);
614
650
  try {
615
651
  assertUniqueEmbeddedModuleOutputs(project.modules.map((module) => ({
616
652
  ownerPath: join(staging, module.relativePath.replace(/\.vel$/u, ".js")),
@@ -625,18 +661,18 @@ async function writeNodeProductionApplication(project, outputDirectory, config)
625
661
  await writeBuildResourcePackageManifests(project, staging);
626
662
  await writeNodeStandardModules(staging, project);
627
663
  await copyPublicAssets(project.publicRoot, join(staging, "public"), true);
664
+ if (requiredNodeStandardModules(project).has("velar/server")) {
665
+ await copyConventionalServerConfiguration(project.projectRoot, staging);
666
+ }
628
667
  const entryPath = `./${relative(project.sourceRoot, entry.inputPath).replace(/\.vel$/u, ".js").replaceAll("\\", "/")}`;
629
668
  const launcher = ".velar-node-entry.mjs";
630
- await writeFile(join(staging, launcher), nodeApplicationLauncherSource(entryPath, config, {}, false), "utf8");
669
+ await writeFile(join(staging, launcher), nodeApplicationLauncherSource(entryPath, config, false, application.kind), "utf8");
631
670
  await writeFile(join(staging, "package.json"), `${JSON.stringify({ name: "velar-node-build", private: true, type: "module" }, null, 2)}\n`, "utf8");
632
- await writeFile(join(staging, "velar-node.json"), `${JSON.stringify({
633
- formatVersion: 1,
671
+ await writeFile(join(staging, NODE_BUILD_MANIFEST_NAME), `${JSON.stringify({
672
+ formatVersion: 2,
634
673
  kind: "velar-node-build",
635
674
  entry: launcher,
636
675
  app: config.app,
637
- host: config.host,
638
- port: config.port,
639
- maxBodyBytes: config.maxBodyBytes,
640
676
  sourceMaps: config.build.sourceMaps,
641
677
  }, null, 2)}\n`, "utf8");
642
678
  await replaceOutputDirectory(staging, outputDirectory);
@@ -646,6 +682,37 @@ async function writeNodeProductionApplication(project, outputDirectory, config)
646
682
  throw error;
647
683
  }
648
684
  }
685
+ const CONVENTIONAL_SERVER_CONFIGURATION_FILE = "application.yml";
686
+ const UNSUPPORTED_CONVENTIONAL_SERVER_CONFIGURATION_FILES = ["application.yaml", "application.json"];
687
+ async function copyConventionalServerConfiguration(projectRoot, outputRoot) {
688
+ for (const name of UNSUPPORTED_CONVENTIONAL_SERVER_CONFIGURATION_FILES) {
689
+ const path = join(projectRoot, name);
690
+ try {
691
+ const metadata = await lstat(path);
692
+ if (metadata.isSymbolicLink() || !metadata.isFile())
693
+ throw new Error(`Conventional Server configuration '${path}' must be a regular file`);
694
+ throw new Error(`Unsupported conventional Server configuration '${path}'; rename it to application.yml or use a non-conventional explicit configuration path`);
695
+ }
696
+ catch (error) {
697
+ if (!isHostErrorCode(error, "ENOENT"))
698
+ throw error;
699
+ }
700
+ }
701
+ const path = join(projectRoot, CONVENTIONAL_SERVER_CONFIGURATION_FILE);
702
+ try {
703
+ const metadata = await lstat(path);
704
+ if (metadata.isSymbolicLink() || !metadata.isFile())
705
+ throw new Error(`Conventional Server configuration '${path}' must be a regular file`);
706
+ if (metadata.size > 1024 * 1024)
707
+ throw new Error(`Conventional Server configuration '${path}' cannot exceed 1 MiB`);
708
+ }
709
+ catch (error) {
710
+ if (isHostErrorCode(error, "ENOENT"))
711
+ return;
712
+ throw error;
713
+ }
714
+ await copyFile(path, join(outputRoot, CONVENTIONAL_SERVER_CONFIGURATION_FILE));
715
+ }
649
716
  function packageFrameworkOutput(root, input) {
650
717
  if (!isAbsolute(input))
651
718
  throw new Error("application package host requested a non-absolute framework output path");
@@ -661,8 +728,9 @@ function packageFrameworkOutput(root, input) {
661
728
  * output. A process cut can happen before or after either rename, so recovery
662
729
  * also finishes restoring the previous output when installation never began.
663
730
  */
664
- async function prepareBuildStaging(outputDirectory) {
731
+ async function prepareBuildStaging(outputDirectory, replacement) {
665
732
  const normalizedOutput = resolve(outputDirectory);
733
+ await assertReplaceableBuildOutput(normalizedOutput, replacement);
666
734
  const parent = dirname(normalizedOutput);
667
735
  await mkdir(parent, { recursive: true });
668
736
  await recoverInterruptedBuilds(normalizedOutput);
@@ -677,6 +745,67 @@ async function prepareBuildStaging(outputDirectory) {
677
745
  await writeFile(join(staging, BUILD_STAGING_MARKER), `${JSON.stringify(ownership, null, 2)}\n`, "utf8");
678
746
  return staging;
679
747
  }
748
+ /**
749
+ * A build replaces its output directory wholesale, so the path is accepted only
750
+ * when velar owns it: absent, empty, carrying a manifest a previous build wrote,
751
+ * or named by the project manifest's own `outDir`. Every other destructive path
752
+ * in this repository makes the same test first — `assertReplaceableReleaseOutput`
753
+ * in scripts/release-toolchain.mjs, `prepareDirectory` in reproduction.ts, and
754
+ * `createVelarProject` — and `--out-dir` was the one an author runs daily that
755
+ * did not. `--force` waives the ownership test and nothing else: the home
756
+ * directory, the filesystem root and a symbolic link stay refused.
757
+ */
758
+ async function assertReplaceableBuildOutput(outputDirectory, replacement) {
759
+ const directory = resolve(outputDirectory);
760
+ if (directory === parsePath(directory).root || directory === resolve(homedir())) {
761
+ throw new Error(`refusing to replace '${directory}': a build output cannot be the filesystem root or the home directory`);
762
+ }
763
+ let metadata;
764
+ try {
765
+ metadata = await lstat(directory);
766
+ }
767
+ catch (error) {
768
+ if (isHostErrorCode(error, "ENOENT") || isHostErrorCode(error, "ENOTDIR"))
769
+ return;
770
+ throw error;
771
+ }
772
+ if (metadata.isSymbolicLink())
773
+ throw new Error(`refusing to replace '${directory}': it is a symbolic link`);
774
+ if (!metadata.isDirectory())
775
+ throw new Error(`refusing to replace '${directory}': it is not a directory`);
776
+ if (replacement.forced || replacement.declared)
777
+ return;
778
+ if ((await readdir(directory)).length === 0)
779
+ return;
780
+ if (await isBuildOutputDirectory(directory))
781
+ return;
782
+ throw new Error(`refusing to replace '${directory}': it is not empty and was not produced by velar build (pass --force to overwrite)`);
783
+ }
784
+ /**
785
+ * The three build shapes leave three different receipts: a framework build
786
+ * writes `velar-build.json`, a node build writes `velar-node.json`, and a build
787
+ * cut between the two renames leaves the staging marker naming this same output.
788
+ */
789
+ async function isBuildOutputDirectory(directory) {
790
+ if (await buildStagingOwnership(directory, directory, null))
791
+ return true;
792
+ return await buildManifestKind(join(directory, PRODUCTION_MANIFEST_NAME)) === "velar-framework-build"
793
+ || await buildManifestKind(join(directory, NODE_BUILD_MANIFEST_NAME)) === "velar-node-build";
794
+ }
795
+ async function buildManifestKind(path) {
796
+ try {
797
+ const metadata = await lstat(path);
798
+ if (!metadata.isFile() || metadata.isSymbolicLink())
799
+ return null;
800
+ const parsed = JSON.parse(await readFile(path, "utf8"));
801
+ return Number.isSafeInteger(parsed.formatVersion) && typeof parsed.kind === "string" ? parsed.kind : null;
802
+ }
803
+ catch (error) {
804
+ if (isHostErrorCode(error, "ENOENT") || isHostErrorCode(error, "ENOTDIR") || error instanceof SyntaxError)
805
+ return null;
806
+ throw error;
807
+ }
808
+ }
680
809
  async function recoverInterruptedBuilds(outputDirectory) {
681
810
  const parent = dirname(outputDirectory);
682
811
  const prefix = `.velar-${basename(outputDirectory)}-`;
@@ -789,7 +918,8 @@ async function writeNodeStandardModules(outputRoot, project, replaceExisting = f
789
918
  await writeNodeStandardModulePackage(packageRoot, used, project);
790
919
  if (used.has("velar/websocket"))
791
920
  await writeWebSocketDependency(dirname(packageRoot));
792
- await writeAdapterDependencies(dirname(packageRoot), used);
921
+ if (used.has("velar/server"))
922
+ await writeServerConfigurationDependency(dirname(packageRoot));
793
923
  return;
794
924
  }
795
925
  const ownership = await generatedRuntimePackageOwnership(packageRoot);
@@ -810,50 +940,14 @@ async function writeNodeStandardModules(outputRoot, project, replaceExisting = f
810
940
  await rename(staging, packageRoot);
811
941
  if (used.has("velar/websocket"))
812
942
  await writeWebSocketDependency(dirname(packageRoot));
813
- await writeAdapterDependencies(dirname(packageRoot), used);
943
+ if (used.has("velar/server"))
944
+ await writeServerConfigurationDependency(dirname(packageRoot));
814
945
  }
815
946
  catch (error) {
816
947
  await rm(staging, { recursive: true, force: true });
817
948
  throw error;
818
949
  }
819
950
  }
820
- async function writeWebSocketDependency(nodeModulesRoot) {
821
- const target = join(nodeModulesRoot, "ws");
822
- try {
823
- const manifest = JSON.parse(await readFile(join(target, "package.json"), "utf8"));
824
- if (manifest.name !== "ws" || manifest.version !== "8.21.1")
825
- throw new Error(`Refusing to use incompatible WebSocket runtime package '${target}'`);
826
- return;
827
- }
828
- catch (error) {
829
- if (!isHostErrorCode(error, "ENOENT"))
830
- throw error;
831
- }
832
- const require = createRequire(import.meta.url);
833
- const source = dirname(require.resolve("ws/package.json"));
834
- await cp(source, target, { recursive: true, errorOnExist: true });
835
- }
836
- async function writeAdapterDependencies(nodeModulesRoot, used) {
837
- const require = createRequire(import.meta.url);
838
- for (const [moduleName, dependency] of STANDARD_MODULE_ADAPTER_DEPENDENCIES) {
839
- if (!used.has(moduleName))
840
- continue;
841
- const target = join(nodeModulesRoot, dependency.packageName);
842
- try {
843
- const manifest = JSON.parse(await readFile(join(target, "package.json"), "utf8"));
844
- if (manifest.name !== dependency.packageName || manifest.version !== dependency.version) {
845
- throw new Error(`Refusing to use incompatible ${moduleName} runtime package '${target}'`);
846
- }
847
- continue;
848
- }
849
- catch (error) {
850
- if (!isHostErrorCode(error, "ENOENT"))
851
- throw error;
852
- }
853
- const source = await resolveInstalledPackageRoot(dependency.packageName, dependency.packageName, require);
854
- await cp(source, target, { recursive: true, errorOnExist: true });
855
- }
856
- }
857
951
  function requiredNodeStandardModules(project) {
858
952
  const sources = standardModuleSources(project.compilerExtensions);
859
953
  const roots = new Set(project.modules.flatMap((module) => module.result.dependencies
@@ -1018,13 +1112,17 @@ function parseFormatArguments(arguments_) {
1018
1112
  }
1019
1113
  return { input, check };
1020
1114
  }
1021
- function parseCommandArguments(arguments_) {
1115
+ function parseCommandArguments(arguments_, allowForce = false) {
1022
1116
  let input = null;
1023
1117
  let output = null;
1024
1118
  let outputDirectory = null;
1119
+ let force = false;
1025
1120
  for (let index = 0; index < arguments_.length; index += 1) {
1026
1121
  const argument = arguments_[index];
1027
- if (argument === "--out" || argument === "--out-dir") {
1122
+ if (allowForce && argument === "--force") {
1123
+ force = true;
1124
+ }
1125
+ else if (argument === "--out" || argument === "--out-dir") {
1028
1126
  const value = arguments_[index + 1];
1029
1127
  if (!value || value.startsWith("--")) {
1030
1128
  return `${argument} requires a path`;
@@ -1049,7 +1147,7 @@ function parseCommandArguments(arguments_) {
1049
1147
  input = argument;
1050
1148
  }
1051
1149
  }
1052
- return { input, output, outputDirectory };
1150
+ return { input, output, outputDirectory, force };
1053
1151
  }
1054
1152
  function parseReproArguments(arguments_) {
1055
1153
  let input = null;
@@ -1089,7 +1187,7 @@ function parsePackageArguments(arguments_) {
1089
1187
  return `unexpected extra input '${arguments_[1]}'`;
1090
1188
  if (arguments_[0]?.startsWith("-"))
1091
1189
  return `unknown option '${arguments_[0]}'`;
1092
- return { input: arguments_[0] ?? null, output: null, outputDirectory: null };
1190
+ return { input: arguments_[0] ?? null, output: null, outputDirectory: null, force: false };
1093
1191
  }
1094
1192
  function parseDevArguments(arguments_) {
1095
1193
  let input = null;
@@ -1118,26 +1216,8 @@ function parseDevArguments(arguments_) {
1118
1216
  }
1119
1217
  function parseServeArguments(arguments_) {
1120
1218
  let input = null;
1121
- let host = null;
1122
- let port = null;
1123
- for (let index = 0; index < arguments_.length; index += 1) {
1124
- const argument = arguments_[index];
1125
- if (argument === "--host") {
1126
- const value = arguments_[index + 1];
1127
- if (!value || value.startsWith("--") || value.length > 255 || value.includes("\0"))
1128
- return "--host requires a bounded hostname or IP address";
1129
- host = value;
1130
- index += 1;
1131
- }
1132
- else if (argument === "--port") {
1133
- const value = arguments_[index + 1];
1134
- const parsed = value ? Number(value) : Number.NaN;
1135
- if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65_535)
1136
- return "--port requires an integer from 1 to 65535";
1137
- port = parsed;
1138
- index += 1;
1139
- }
1140
- else if (argument.startsWith("--")) {
1219
+ for (const argument of arguments_) {
1220
+ if (argument.startsWith("--")) {
1141
1221
  return `unknown option '${argument}'`;
1142
1222
  }
1143
1223
  else if (input) {
@@ -1147,7 +1227,7 @@ function parseServeArguments(arguments_) {
1147
1227
  input = argument;
1148
1228
  }
1149
1229
  }
1150
- return { input, host, port };
1230
+ return { input };
1151
1231
  }
1152
1232
  function parseTestArguments(arguments_) {
1153
1233
  let input = null;
@@ -1303,8 +1383,8 @@ function printHelp(output = process.stdout) {
1303
1383
  " velar remove <package>...",
1304
1384
  " velar update [package...]",
1305
1385
  " velar dev [entry.vel | project-directory] [--port <port>]",
1306
- " velar serve [project-directory] [--host <host>] [--port <port>]",
1307
- " velar build [entry.vel | project-directory] [--out-dir <directory>]",
1386
+ " velar serve [project-directory]",
1387
+ " velar build [entry.vel | project-directory] [--out-dir <directory>] [--force]",
1308
1388
  " velar run [entry.vel | project-directory] [--stack] [-- <program-arguments>...]",
1309
1389
  " velar verify [project-directory | build-directory]",
1310
1390
  " velar preview [project-directory | build-directory] [--port <port>]",
@@ -1316,7 +1396,7 @@ function printHelp(output = process.stdout) {
1316
1396
  " velar format [file.vel | project-directory] [--check]",
1317
1397
  " velar fix [entry.vel | project-directory]",
1318
1398
  " velar repro [entry.vel | project-directory] [--out-dir <directory>]",
1319
- " velar skill [core|web|node|desktop]",
1399
+ " velar skill [core|web|node|server|desktop]",
1320
1400
  " velar lsp",
1321
1401
  " velar --version",
1322
1402
  "",
@@ -1334,9 +1414,9 @@ function printCommandHelp(command, output = process.stdout) {
1334
1414
  add: ["Usage: velar add <package[@version]>... [--dev]", "Adds npm registry packages and activates packages that declare velar.extension metadata."],
1335
1415
  remove: ["Usage: velar remove <package>...", "Removes npm packages and their extension-owned VelarScript project configuration."],
1336
1416
  update: ["Usage: velar update [package...]", "Updates all or selected direct dependencies within package.json ranges through npm."],
1337
- dev: ["Usage: velar dev [entry.vel | project-directory] [--port <1-65535>]", "Watches a framework app or last-good Node ServeApp; Web defaults to 5173 and Node reads node.port."],
1338
- serve: ["Usage: velar serve [project-directory] [--host <host>] [--port <1-65535>]", "Checks and runs a Node ServeApp with production runtime behavior; node.host and node.port provide the defaults."],
1339
- build: ["Usage: velar build [entry.vel | project-directory] [--out-dir <directory>]", " velar build <single.vel> --out <file.js>", "Builds isolated Web/Desktop output, a standalone Node application, or JavaScript modules."],
1417
+ dev: ["Usage: velar dev [entry.vel | project-directory] [--port <1-65535>]", "Watches a framework app or last-good Node server factory; --port applies only to Web and Desktop development servers."],
1418
+ serve: ["Usage: velar serve [project-directory]", "Checks and runs a Node server factory with production runtime behavior; host and port belong to velar/server configuration."],
1419
+ build: ["Usage: velar build [entry.vel | project-directory] [--out-dir <directory>] [--force]", " velar build <single.vel> --out <file.js>", "Builds isolated Web/Desktop output, a standalone Node application, or JavaScript modules.", "--out-dir refuses a directory that is not empty and was not produced by a previous build; --force replaces one anyway."],
1340
1420
  package: ["Usage: velar package [project-directory]", "Packages an application through its target-owned native packaging host."],
1341
1421
  run: ["Usage: velar run [entry.vel | project-directory] [--stack] [-- <program-arguments>...]", "Compiles the resolved Core project and executes its entry module once on Node.js; arguments after '--' reach the program.", "--stack prints the full Node.js trace behind an uncaught program error instead of the VelarScript frames."],
1342
1422
  verify: ["Usage: velar verify [project-directory | build-directory]", "Verifies the exact production manifest, inventory, sizes, hashes, and relationships."],
@@ -1356,7 +1436,7 @@ function printCommandHelp(command, output = process.stdout) {
1356
1436
  "The bundle is extracted to a temporary directory and re-checked first; if the copy stops reproducing, the command says so rather than reporting a clean reproduction.",
1357
1437
  "The default location is .velar/repro inside the project, replaced on each run; a directory named with --out-dir must be empty.",
1358
1438
  ],
1359
- skill: ["Usage: velar skill [core|web|node|desktop]", "Prints one packaged, owner-specific VelarScript AI skill brief verbatim to stdout; the default is core."],
1439
+ skill: ["Usage: velar skill [core|web|node|server|desktop]", "Prints one packaged, owner-specific VelarScript AI skill brief verbatim to stdout; the default is core."],
1360
1440
  lsp: ["Usage: velar lsp", "Runs the stdio language server for an editor host."],
1361
1441
  };
1362
1442
  output.write(["VelarScript Compiler", "", ...(details[command] ?? []), ""].join("\n"));