xbintsc 0.3.13 → 0.3.15

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 (122) hide show
  1. package/README.md +22 -22
  2. package/README.zh-CN.md +20 -19
  3. package/bin/xbintsc.js +1 -2
  4. package/dist/src/cli/main.js +4 -1
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/access.js +13 -0
  7. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  8. package/dist/src/codegen/generator/calls/invocation.js +8 -2
  9. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  10. package/dist/src/codegen/generator/context.d.ts +6 -0
  11. package/dist/src/codegen/generator/context.js +24 -2
  12. package/dist/src/codegen/generator/context.js.map +1 -1
  13. package/dist/src/codegen/generator/expressions/primary.js +5 -0
  14. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  15. package/dist/src/codegen/generator/tables.d.ts +5 -6
  16. package/dist/src/codegen/generator/tables.js +5 -6
  17. package/dist/src/codegen/generator/tables.js.map +1 -1
  18. package/dist/src/driver/compiler.js +15 -3
  19. package/dist/src/driver/compiler.js.map +1 -1
  20. package/dist/src/driver/index.d.ts +1 -2
  21. package/dist/src/driver/index.js +1 -2
  22. package/dist/src/driver/index.js.map +1 -1
  23. package/dist/src/driver/modules.d.ts +1 -1
  24. package/dist/src/driver/modules.js +235 -25
  25. package/dist/src/driver/modules.js.map +1 -1
  26. package/dist/src/driver/paths.d.ts +0 -9
  27. package/dist/src/driver/paths.js +0 -31
  28. package/dist/src/driver/paths.js.map +1 -1
  29. package/dist/src/driver/toolchain-provider.d.ts +10 -10
  30. package/dist/src/driver/toolchain-provider.js +9 -38
  31. package/dist/src/driver/toolchain-provider.js.map +1 -1
  32. package/dist/src/driver/toolchain.js +8 -2
  33. package/dist/src/driver/toolchain.js.map +1 -1
  34. package/dist/src/extensions/native.d.ts +2 -3
  35. package/dist/src/extensions/native.js +2 -3
  36. package/dist/src/extensions/native.js.map +1 -1
  37. package/dist/src/extensions/node/fs/fs-ops.d.ts +5 -2
  38. package/dist/src/extensions/node/fs/fs-ops.js +45 -6
  39. package/dist/src/extensions/node/fs/fs-ops.js.map +1 -1
  40. package/dist/src/extensions/node/fs/index.d.ts +6 -2
  41. package/dist/src/extensions/node/fs/index.js +22 -3
  42. package/dist/src/extensions/node/fs/index.js.map +1 -1
  43. package/dist/src/extensions/node/fs-promises/index.d.ts +4 -3
  44. package/dist/src/extensions/node/fs-promises/index.js +31 -3
  45. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  46. package/dist/tests/cli/main.test.js +8 -0
  47. package/dist/tests/cli/main.test.js.map +1 -1
  48. package/dist/tests/driver/compiler.test.js +28 -1
  49. package/dist/tests/driver/compiler.test.js.map +1 -1
  50. package/dist/tests/driver/modules.test.js +91 -4
  51. package/dist/tests/driver/modules.test.js.map +1 -1
  52. package/dist/tests/driver/paths.test.js +1 -9
  53. package/dist/tests/driver/paths.test.js.map +1 -1
  54. package/dist/tests/driver/toolchain-provider.test.js +12 -79
  55. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  56. package/dist/tests/e2e/compile-node.test.js +240 -0
  57. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  58. package/dist/tests/e2e/compile.test.js +12 -0
  59. package/dist/tests/e2e/compile.test.js.map +1 -1
  60. package/dist/tests/e2e/harness.js +11 -8
  61. package/dist/tests/e2e/harness.js.map +1 -1
  62. package/dist/tests/e2e/native-extensions.test.d.ts +4 -5
  63. package/dist/tests/e2e/native-extensions.test.js +6 -7
  64. package/dist/tests/e2e/native-extensions.test.js.map +1 -1
  65. package/dist/tests/e2e/node-modules.test.js +1 -1
  66. package/dist/tests/e2e/node-modules.test.js.map +1 -1
  67. package/dist/tests/e2e/npm-modules.test.d.ts +9 -0
  68. package/dist/tests/e2e/npm-modules.test.js +55 -0
  69. package/dist/tests/e2e/npm-modules.test.js.map +1 -0
  70. package/dist/tests/e2e/runtime-coverage.test.d.ts +11 -0
  71. package/dist/tests/e2e/runtime-coverage.test.js +646 -0
  72. package/dist/tests/e2e/runtime-coverage.test.js.map +1 -0
  73. package/package.json +1 -2
  74. package/runtime/ext_node/buffer/parts/prototype.inc +7 -0
  75. package/runtime/ext_node/buffer/parts/search.inc +8 -4
  76. package/runtime/ext_node/child_process/child_process.c +5 -0
  77. package/runtime/ext_node/fs/constants.c +113 -0
  78. package/runtime/ext_node/fs/copy_ops.c +145 -0
  79. package/runtime/ext_node/fs/dir.c +101 -0
  80. package/runtime/ext_node/fs/fd_ops.c +405 -0
  81. package/runtime/ext_node/fs/fs_common.h +388 -5
  82. package/runtime/ext_node/fs/fs_ops.c +100 -139
  83. package/runtime/ext_node/fs/glob.c +232 -0
  84. package/runtime/ext_node/fs/link_ops.c +91 -0
  85. package/runtime/ext_node/fs/meta_ops.c +403 -0
  86. package/runtime/ext_node/fs/promises.c +275 -60
  87. package/runtime/ext_node/fs/read_file.c +8 -4
  88. package/runtime/ext_node/fs/watch.c +52 -0
  89. package/runtime/ext_node/fs/write_file.c +20 -11
  90. package/runtime/ext_node/stream/stream.c +19 -2
  91. package/runtime/rt.h +8 -0
  92. package/runtime/rt_internal.h +10 -23
  93. package/runtime/xt_ext.h +0 -1
  94. package/runtime/xt_io.c +24 -3
  95. package/runtime/xt_stdlib2/helpers.inc +2 -4
  96. package/scripts/build-runtime.ts +7 -8
  97. package/scripts/coverage-runtime.ts +4 -4
  98. package/scripts/package-release.ts +12 -84
  99. package/src/cli/main.ts +4 -1
  100. package/src/codegen/generator/calls/access.ts +15 -0
  101. package/src/codegen/generator/calls/invocation.ts +8 -2
  102. package/src/codegen/generator/context.ts +30 -2
  103. package/src/codegen/generator/expressions/primary.ts +5 -0
  104. package/src/codegen/generator/tables.ts +5 -6
  105. package/src/driver/compiler.ts +16 -3
  106. package/src/driver/index.ts +1 -9
  107. package/src/driver/modules.ts +251 -26
  108. package/src/driver/paths.ts +0 -31
  109. package/src/driver/toolchain-provider.ts +12 -45
  110. package/src/driver/toolchain.ts +7 -2
  111. package/src/extensions/native.ts +2 -3
  112. package/src/extensions/node/fs/fs-ops.ts +48 -6
  113. package/src/extensions/node/fs/index.ts +25 -3
  114. package/src/extensions/node/fs-promises/index.ts +33 -4
  115. package/dist/src/driver/toolchain-download.d.ts +0 -50
  116. package/dist/src/driver/toolchain-download.js +0 -102
  117. package/dist/src/driver/toolchain-download.js.map +0 -1
  118. package/dist/tests/driver/toolchain-download.test.d.ts +0 -1
  119. package/dist/tests/driver/toolchain-download.test.js +0 -53
  120. package/dist/tests/driver/toolchain-download.test.js.map +0 -1
  121. package/scripts/fetch-toolchain.ts +0 -101
  122. package/src/driver/toolchain-download.ts +0 -135
@@ -29,16 +29,15 @@ const outDir = join(runtimeDir, "lib", platformSlug());
29
29
  const objDir = join(root, "build", "runtime-obj");
30
30
 
31
31
  if (process.platform === "win32") {
32
- // The Windows release ships no prebuilt archives yet: the current MSVC CI
33
- // cannot produce MinGW-compatible objects, so the driver compiles the C
34
- // sources on demand. Revisit once the bundled MinGW-w64 ABI lands.
35
- // See doc/self-contained-roadmap.md.
36
- console.log("xbintsc: skipped runtime archives on Windows until the MinGW-w64 toolchain lands");
32
+ // The driver links with the MSVC ABI on Windows, where the prebuilt archives
33
+ // are unnecessary: `build` compiles the runtime C sources on demand with the
34
+ // user's clang. (Producing MSVC-compatible COFF `.lib` archives is possible
35
+ // but not worth the extra step.)
36
+ console.log("xbintsc: skipping runtime archives on Windows; the driver compiles the C sources on demand");
37
37
  process.exit(0);
38
38
  }
39
39
 
40
- // Use the same toolchain the driver will: a bundled clang when present, with its
41
- // `LD_LIBRARY_PATH` (the legacy `libtinfo.so.5` on Linux) threaded through.
40
+ // Use the same toolchain the driver will.
42
41
  const toolchain = resolveToolchain();
43
42
  const clang = toolchain.clang;
44
43
  const childEnv = Object.assign({}, process.env, toolchain.env);
@@ -54,7 +53,7 @@ function findArchiver(clangPath: string): string {
54
53
  const exe = process.platform === "win32" ? ".exe" : "";
55
54
  const candidates = [
56
55
  process.env.xbintsc_AR,
57
- // A bundled toolchain ships `llvm-ar` next to clang.
56
+ // A clang install usually ships `llvm-ar` next to the driver.
58
57
  dir === "." ? undefined : join(dir, `llvm-ar${exe}`),
59
58
  "llvm-ar",
60
59
  "ar",
@@ -160,9 +160,9 @@ function runs(command: string): boolean {
160
160
 
161
161
  /**
162
162
  * Locate the LLVM tool `name` (`llvm-profdata`/`llvm-cov`) that matches the
163
- * clang the driver resolved. The bundled toolchains ship the tools next to
164
- * clang and cover the versioned system installs, so search there first: mixing
165
- * versions makes `llvm-profdata` reject the profile format.
163
+ * clang the driver resolved. Prefer the tool next to clang, then the
164
+ * version-suffixed system installs: mixing versions makes `llvm-profdata`
165
+ * reject the profile format.
166
166
  */
167
167
  function findTool(clang: string, name: string): string | undefined {
168
168
  const exe = process.platform === "win32" ? ".exe" : "";
@@ -282,7 +282,7 @@ function main(): void {
282
282
  profdataTool ? undefined : "llvm-profdata",
283
283
  covTool ? undefined : "llvm-cov",
284
284
  ].filter(Boolean).join(", ");
285
- skip(`${missing} not found for ${clang}; install LLVM or use the bundled toolchain`, options.strict);
285
+ skip(`${missing} not found for ${clang}; install the LLVM tools (llvm-profdata, llvm-cov)`, options.strict);
286
286
  }
287
287
 
288
288
  if (!options.reportOnly) runTests(options);
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Assemble a self-contained release archive for the current platform:
3
+ * Assemble a release archive for the current platform:
4
4
  *
5
5
  * dist/release/xbintsc-<version>-<os>-<arch>.tar.gz (or .tar.zst)
6
6
  * dist/release/xbintsc-<version>-<os>-<arch>.tar.gz.sha256
@@ -10,23 +10,24 @@
10
10
  *
11
11
  * The unpacked staging tree is built under `build/release-stage/`, never under
12
12
  * `dist/release/`, so an upload glob over `dist/release/*` cannot accidentally
13
- * ship the (multi-GB) tree alongside the archive.
13
+ * ship the (large) tree alongside the archive.
14
+ *
15
+ * xbintsc does not bundle a compiler: the archive holds only the self-hosted
16
+ * binary and the C runtime sources plus prebuilt archives. Users provide the
17
+ * toolchain themselves (see `doc/requirements.md`).
14
18
  *
15
19
  * Layout inside the archive:
16
20
  *
17
21
  * xbintsc-<os>-<arch>/
18
22
  * bin/xbintsc[.exe] the self-hosted compiler
19
23
  * runtime/ C runtime sources + prebuilt runtime/lib/<slug>
20
- * vendor/<slug>/ the bundled clang+lld toolchain (Linux/Windows)
21
24
  * README.md
22
25
  *
23
- * The compiler locates `runtime/` and `vendor/` relative to its own executable
24
- * (see `findRuntimeDir`/`findVendorDir`), so this tree runs with no system
25
- * toolchain installed.
26
+ * The compiler locates `runtime/` relative to its own executable (see
27
+ * `findRuntimeDir`), so this tree runs without any bundled toolchain.
26
28
  *
27
29
  * Inputs:
28
30
  * - the self-hosted binary staged at `dist/xbintsc[.exe]` (CI copies it there)
29
- * - `vendor/<slug>/`, fetched by `scripts/fetch-toolchain.ts`
30
31
  * - `runtime/lib/<slug>/`, built by `scripts/build-runtime.ts`
31
32
  */
32
33
 
@@ -37,9 +38,7 @@ import {
37
38
  chmodSync,
38
39
  existsSync,
39
40
  mkdirSync,
40
- readdirSync,
41
41
  readFileSync,
42
- realpathSync,
43
42
  rmSync,
44
43
  statSync,
45
44
  writeFileSync,
@@ -49,14 +48,12 @@ import { basename, dirname, join, resolve } from "node:path";
49
48
  import { fileURLToPath } from "node:url";
50
49
  import {
51
50
  findRuntimeDir,
52
- platformSlug,
53
51
  releaseArchiveBase,
54
52
  releaseArchiveExtension,
55
53
  releaseArchiveFileName,
56
54
  } from "../src/driver/paths.js";
57
55
 
58
56
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
59
- const slug = platformSlug();
60
57
  const base = releaseArchiveBase();
61
58
  const version = (
62
59
  JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { version: string }
@@ -77,55 +74,6 @@ function firstExisting(candidates: readonly string[]): string | undefined {
77
74
  return undefined;
78
75
  }
79
76
 
80
- /**
81
- * Copy only the parts of the fetched toolchain that xbintsc actually runs.
82
- *
83
- * The official LLVM release unpacks to ~7 GB: every MLIR/Flang/lldb tool, the
84
- * static archives and the full C++ headers. The compiler only shells out to the
85
- * clang driver, the lld linker and llvm-ar, and clang additionally needs its own
86
- * resource directory (`lib/clang/<version>`, the builtin headers and
87
- * compiler-rt). Copying just those keeps the archive ~100x smaller.
88
- */
89
- function stageToolchain(vendor: string, dest: string): void {
90
- const exe = process.platform === "win32" ? ".exe" : "";
91
- mkdirSync(join(dest, "bin"), { recursive: true });
92
-
93
- const real = (...candidates: readonly string[]): string | undefined => {
94
- const found = firstExisting(candidates.map((candidate) => join(vendor, candidate)));
95
- return found ? realpathSync(found) : undefined;
96
- };
97
- const copyAs = (source: string | undefined, name: string): void => {
98
- if (!source) return;
99
- const target = join(dest, "bin", name);
100
- copyFileSync(source, target);
101
- if (process.platform !== "win32") chmodSync(target, 0o755);
102
- };
103
-
104
- // In the official release `clang`/`ld.lld` are symlinks into the bin/ dir;
105
- // copy the resolved targets under the names the toolchain provider (and
106
- // clang's `-fuse-ld=lld`) expect next to `bin/clang`.
107
- copyAs(real(`bin/clang${exe}`, `bin/clang-18${exe}`), `clang${exe}`);
108
- copyAs(real(`bin/ld.lld${exe}`, `bin/lld${exe}`), `ld.lld${exe}`);
109
- copyAs(real(`bin/llvm-ar${exe}`, "bin/llvm-ar"), `llvm-ar${exe}`);
110
-
111
- // clang's resource directory: builtin headers + compiler-rt archives.
112
- const resourceDir = join(vendor, "lib", "clang");
113
- if (existsSync(resourceDir)) {
114
- cpSync(resourceDir, join(dest, "lib", "clang"), { recursive: true });
115
- }
116
-
117
- // Shared libraries the fetch placed next to the toolchain (on Linux that is
118
- // the legacy `libtinfo.so.5` the official build still links).
119
- const libDir = join(vendor, "lib");
120
- if (existsSync(libDir)) {
121
- for (const entry of readdirSync(libDir, { withFileTypes: true })) {
122
- if (/^(.*\.so(\.\d+)*|.*\.dylib|.*\.dll)$/.test(entry.name)) {
123
- cpSync(join(libDir, entry.name), join(dest, "lib", entry.name), { recursive: true });
124
- }
125
- }
126
- }
127
- }
128
-
129
77
  const distDir = join(root, "dist");
130
78
  const binary = firstExisting([
131
79
  join(distDir, `xbintsc${exeSuffix}`),
@@ -138,7 +86,7 @@ if (!binary) {
138
86
 
139
87
  // Archives go to `dist/release/`; the unpacked staging tree goes to
140
88
  // `build/release-stage/` so the upload glob over `dist/release/*` cannot pick up
141
- // the raw tree (which would ship the toolchain twice, and uncompressed).
89
+ // the raw tree.
142
90
  const releaseRoot = join(distDir, "release");
143
91
  const stageRoot = join(root, "build", "release-stage");
144
92
  const stage = join(stageRoot, base);
@@ -156,33 +104,13 @@ console.log(`xbintsc: packaged binary ${basename(binary)} (${statSync(binary).si
156
104
  cpSync(findRuntimeDir(), join(stage, "runtime"), { recursive: true });
157
105
  console.log("xbintsc: packaged runtime/");
158
106
 
159
- // 3. The bundled toolchain, when one was fetched for this platform.
160
- const vendor = join(root, "vendor", slug);
161
- if (existsSync(join(vendor, "bin"))) {
162
- if (process.platform === "win32") {
163
- // llvm-mingw spreads its MinGW-w64 CRT/import libraries across the tree, so
164
- // bundling it whole is the safe default there for now.
165
- cpSync(vendor, join(stage, "vendor", slug), { recursive: true });
166
- } else {
167
- stageToolchain(vendor, join(stage, "vendor", slug));
168
- }
169
- console.log(`xbintsc: packaged vendor/${slug}/`);
170
- } else if (process.platform === "linux" || process.platform === "win32") {
171
- console.warn(
172
- `xbintsc: warning: no vendor/${slug}/ found; the release will rely on a system toolchain. ` +
173
- "Run `npm run fetch-toolchain` first for a self-contained archive.",
174
- );
175
- } else {
176
- console.log(`xbintsc: no bundled toolchain for ${slug}; relying on the system SDK (macOS)`);
177
- }
178
-
179
- // 4. Human-facing notes.
107
+ // 3. Human-facing notes.
180
108
  for (const name of ["README.md", "LICENSE"]) {
181
109
  const source = join(root, name);
182
110
  if (existsSync(source)) copyFileSync(source, join(stage, name));
183
111
  }
184
112
 
185
- // 5. Archive it. Prefer zstd when available, otherwise gzip.
113
+ // 4. Archive it. Prefer zstd when available, otherwise gzip.
186
114
  const hasZstd = !spawnSync("zstd", ["--version"], { stdio: "ignore" }).error;
187
115
  const extension = releaseArchiveExtension(hasZstd);
188
116
  const archive = join(releaseRoot, `${archiveBase}${extension}`);
@@ -192,7 +120,7 @@ const tar = spawnSync("tar", tarArgs, { stdio: "inherit" });
192
120
  if (tar.error) fail(`failed to run tar: ${tar.error.message}`);
193
121
  if (tar.status !== 0) fail(`tar exited with ${tar.status}`);
194
122
 
195
- // 6. Checksum, so users can verify the download.
123
+ // 5. Checksum, so users can verify the download.
196
124
  const digest = createHash("sha256").update(readFileSync(archive)).digest("hex");
197
125
  writeFileSync(`${archive}.sha256`, `${digest} ${basename(archive)}\n`);
198
126
 
package/src/cli/main.ts CHANGED
@@ -172,7 +172,10 @@ function doctor(io: CliIo): number {
172
172
  io.stdout(`toolchain : ${toolchain.clang} (${toolchain.source})\n`);
173
173
  io.stdout(`compiler : ${firstLine(version) || "unknown"}\n`);
174
174
  } catch {
175
- io.stdout("toolchain : not found (install the Xcode Command Line Tools or set xbintsc_CLANG)\n");
175
+ io.stdout("toolchain : not found\n");
176
+ io.stdout(" install one of: Xcode Command Line Tools (`xcode-select --install`) on macOS,\n");
177
+ io.stdout(" clang+lld (apt/dnf/pacman) on Linux, or LLVM + Visual Studio C++ build tools on Windows,\n");
178
+ io.stdout(" then re-run; or set xbintsc_CLANG to a clang binary. See doc/requirements.md.\n");
176
179
  }
177
180
 
178
181
  try {
@@ -106,6 +106,21 @@ export const accessCallMethods: AccessCallMethods = {
106
106
  }
107
107
  }
108
108
  }
109
+ // Modules with no namespace dispatcher (`fs`, ...) can still expose value
110
+ // getters (`fs.constants`, `fs.promises`). Lower `fs.constants` the same
111
+ // way a named `import { constants } from "fs"` is lowered.
112
+ if (node.expression.kind === SyntaxKind.Identifier) {
113
+ const moduleExports = this.moduleExportsOfSymbol(
114
+ this.binding.symbolOfIdentifier.get(node.expression as Identifier),
115
+ );
116
+ const exported = moduleExports?.[node.name.text];
117
+ if (exported?.valueSymbol) {
118
+ this.extraDeclarations.add(`declare i64 @${exported.valueSymbol}(i32, i64*)`);
119
+ const result = this.reg();
120
+ this.emit(` ${result} = call i64 @${exported.valueSymbol}(i32 0, i64* null)`);
121
+ return result;
122
+ }
123
+ }
109
124
  const object = this.emitExpression(node.expression);
110
125
  return this.emitPropertyGet(object, node.name.text);
111
126
  },
@@ -91,8 +91,14 @@ export const invocationCallMethods: InvocationCallMethods = {
91
91
  return result;
92
92
  }
93
93
  if (callee.kind === SyntaxKind.Identifier) {
94
- if ((callee as Identifier).text === "super") return this.emitSuperConstructor(node);
95
- const symbol = this.binding.symbolOfIdentifier.get(callee as Identifier);
94
+ const identifier = callee as Identifier;
95
+ if (identifier.text === "super") return this.emitSuperConstructor(node);
96
+ const symbol = this.binding.symbolOfIdentifier.get(identifier);
97
+ // `require("x")` is CommonJS; the ESM form is the only supported import.
98
+ if (!symbol && identifier.text === "require") {
99
+ this.reportRequireUse(node);
100
+ return i64(XT_UNDEFINED);
101
+ }
96
102
  if (symbol && symbol.kind === SymbolKind.Import) {
97
103
  const exported = this.importExports.get(symbol.id);
98
104
  if (exported) return this.emitModuleExport(node, exported);
@@ -162,8 +162,8 @@ export class GeneratorContext {
162
162
  if (!symbols.some((symbol) => this.usedAsValue(symbol))) return;
163
163
  this.diagnostics.error(
164
164
  DiagnosticCode.ModuleNotFound,
165
- `module '${specifier}' is not supported: xbintsc can only import built-in platform modules and ` +
166
- "relative '.ts' files; third-party npm packages (node_modules) are not implemented yet",
165
+ `module '${specifier}' is not supported: xbintsc can import built-in platform modules, ` +
166
+ "relative '.ts' files and ESM packages under node_modules; CommonJS packages are not supported",
167
167
  declaration.moduleSpecifier,
168
168
  this.sourceFile.fileName,
169
169
  );
@@ -414,6 +414,34 @@ export class GeneratorContext {
414
414
  this.sourceFile.fileName,
415
415
  );
416
416
  }
417
+
418
+ /**
419
+ * Report a CommonJS `require(...)` use. xbintsc has no CommonJS module
420
+ * runtime, so point the user at the ESM `import` form (with the requested
421
+ * module in the hint when it is a string literal).
422
+ */
423
+ reportRequireUse(node: Node): void {
424
+ const specifier = requireSpecifier(node);
425
+ const replacement = specifier
426
+ ? `use an ESM import instead, e.g. \`import value from ${JSON.stringify(specifier)}\``
427
+ : "use an ESM `import` statement instead";
428
+ this.diagnostics.error(
429
+ DiagnosticCode.UnsupportedFeature,
430
+ `CommonJS \`require()\` is not supported; ${replacement}`,
431
+ node,
432
+ this.sourceFile.fileName,
433
+ );
434
+ }
435
+ }
436
+
437
+ /** The string literal passed to `require("...")`, when there is one. */
438
+ function requireSpecifier(node: Node): string | undefined {
439
+ if (node.kind !== SyntaxKind.CallExpression) return undefined;
440
+ const first = (node as { arguments?: readonly Node[] }).arguments?.[0];
441
+ if (first && first.kind === SyntaxKind.StringLiteral) {
442
+ return (first as unknown as { value: string }).value;
443
+ }
444
+ return undefined;
417
445
  }
418
446
 
419
447
  /** A bare module specifier (`fs`, `node:fs`, `@scope/pkg`), not a file path. */
@@ -235,6 +235,11 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
235
235
  const nameValue = this.stringValue(identifier.text);
236
236
  return this.runtimeCall("xt_error_constructor", [nameValue]);
237
237
  }
238
+ // A bare `require` reference is CommonJS; steer the user to `import`.
239
+ if (identifier.text === "require") {
240
+ this.reportRequireUse(identifier);
241
+ return i64(XT_UNDEFINED);
242
+ }
238
243
  this.diagnostics.error(
239
244
  DiagnosticCode.CannotFindName,
240
245
  `Cannot find name '${identifier.text}'`,
@@ -12,12 +12,11 @@ import {
12
12
  /**
13
13
  * True when a `try` frame must be saved with `_setjmpex` rather than `_setjmp`.
14
14
  *
15
- * 64-bit Windows on ARM has no `_setjmp`: both the UCRT and MinGW-w64 declare
16
- * `setjmp` as `_setjmpex(buf, sponentry)` there, so the linker expects
17
- * `_setjmpex` (with the caller's stack pointer at entry, `llvm.sponentry`,
18
- * instead of its frame pointer). clang lowers a C `setjmp` on this target to
19
- * exactly that pair. Checked at code-generation time because xbintsc builds for
20
- * its own host.
15
+ * 64-bit Windows on ARM has no `_setjmp`: the UCRT declares `setjmp` as
16
+ * `_setjmpex(buf, sponentry)` there, so the linker expects `_setjmpex` (with the
17
+ * caller's stack pointer at entry, `llvm.sponentry`, instead of its frame
18
+ * pointer). clang lowers a C `setjmp` on this target to exactly that pair.
19
+ * Checked at code-generation time because xbintsc builds for its own host.
21
20
  */
22
21
  export function requiresSetjmpex(platform: string, arch: string): boolean {
23
22
  return platform === "win32" && arch === "arm64";
@@ -77,6 +77,19 @@ export function compileString(source: string, fileName = "input.ts", extensions?
77
77
  return { ir, diagnostics: diagnostics.diagnostics };
78
78
  }
79
79
 
80
+ /**
81
+ * Every module specifier the registry can resolve at code generation time (the
82
+ * registered platform modules plus the hints for known-but-disabled
83
+ * extensions). Bare imports outside this set are looked up in `node_modules`
84
+ * and bundled as source.
85
+ */
86
+ function externalModuleSpecifiers(registry: ExtensionRegistry): Set<string> {
87
+ return new Set([
88
+ ...Object.keys(registry.modules()),
89
+ ...Object.keys(registry.moduleHints()),
90
+ ]);
91
+ }
92
+
80
93
  /**
81
94
  * Compile a file to LLVM IR, bundling every reachable relative module first so
82
95
  * that imports from other TypeScript sources resolve exactly like `build`.
@@ -88,6 +101,7 @@ export function compileEntry(entryPath: string, extensions?: ExtensionRegistry):
88
101
  const diagnostics = new DiagnosticBag();
89
102
  const parser = new Parser(file, diagnostics);
90
103
  let sourceFile = parser.parseSourceFile();
104
+ const registry = extensions ?? createDefaultRegistry();
91
105
  const isModule = sourceFile.statements.some(
92
106
  (statement) =>
93
107
  statement.kind === SyntaxKind.ImportDeclaration ||
@@ -95,10 +109,9 @@ export function compileEntry(entryPath: string, extensions?: ExtensionRegistry):
95
109
  statement.kind === SyntaxKind.ExportAssignment,
96
110
  );
97
111
  if (isModule) {
98
- const bundled = bundleModules(absoluteEntry, diagnostics);
112
+ const bundled = bundleModules(absoluteEntry, diagnostics, externalModuleSpecifiers(registry));
99
113
  if (bundled) sourceFile = bundled.sourceFile;
100
114
  }
101
- const registry = extensions ?? createDefaultRegistry();
102
115
  const { ir } = generate(sourceFile, diagnostics, {
103
116
  builtins: registry.builtins(),
104
117
  modules: registry.modules(),
@@ -172,7 +185,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
172
185
  );
173
186
  let cacheText = sourceText;
174
187
  if (isModule) {
175
- const bundled = bundleModules(absoluteEntry, diagnostics);
188
+ const bundled = bundleModules(absoluteEntry, diagnostics, externalModuleSpecifiers(registry));
176
189
  if (bundled) {
177
190
  sourceFile = bundled.sourceFile;
178
191
  cacheText = bundled.text;
@@ -1,14 +1,6 @@
1
1
  export { build, compileString, COMPILER_VERSION, type BuildOptions, type BuildResult, type EmitKind } from "./compiler.js";
2
2
  export { BuildCache, hashParts, hashString } from "./cache.js";
3
- export { findRuntimeDir, findPackageRoot, findVendorDir, vendorRootDir, platformSlug, releaseArchiveBase, releaseArchiveFileName, releaseArchiveExtension } from "./paths.js";
4
- export {
5
- toolchainDownload,
6
- toolchainSupportLibraries,
7
- LLVM_VERSION,
8
- LLVM_MINGW_VERSION,
9
- type ToolchainDownload,
10
- type SupportLibrary,
11
- } from "./toolchain-download.js";
3
+ export { findRuntimeDir, platformSlug, releaseArchiveBase, releaseArchiveFileName, releaseArchiveExtension } from "./paths.js";
12
4
  export { findRuntimeLibrary, runtimeLibDir } from "./runtime-lib.js";
13
5
  export {
14
6
  findClang,