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
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import { existsSync, readFileSync, statSync } from "node:fs";
16
- import { dirname, isAbsolute, resolve } from "node:path";
16
+ import { dirname, isAbsolute, join, resolve } from "node:path";
17
17
  import {
18
18
  ModifierKind,
19
19
  SyntaxKind,
@@ -55,27 +55,50 @@ export interface BundleResult {
55
55
  readonly moduleCount: number;
56
56
  }
57
57
 
58
- const RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".mts", ".cts", "/index.ts", "/index.tsx"];
59
-
60
58
  /**
61
- * Bare module specifiers (`fs`, `node:fs`, `@scope/pkg`) are not files to
62
- * bundle: they are resolved at code generation time against the registered
63
- * extension modules.
59
+ * Source extensions probed for an extension-less specifier. TypeScript suffixes
60
+ * are tried before their JavaScript counterparts so a project that ships both
61
+ * `foo.ts` and a compiled `foo.js` prefers the source it is compiled from.
64
62
  */
65
- function isExternalSpecifier(specifier: string): boolean {
66
- return !specifier.startsWith(".") && !isAbsolute(specifier);
63
+ const RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
64
+ /** File extensions a directory `index` entry may use. */
65
+ const INDEX_SUFFIXES = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
66
+ /** Conditions consulted, in order, when reading a package `exports` map. */
67
+ const EXPORT_CONDITIONS = ["import", "module", "default", "node", "require"];
68
+
69
+ /** A specifier is a file path (`./x`, `../x`, `/x`) rather than a package. */
70
+ function isRelativeSpecifier(specifier: string): boolean {
71
+ return specifier.startsWith(".") || isAbsolute(specifier);
67
72
  }
68
73
 
69
- function resolveModule(fromDir: string, specifier: string): string | undefined {
70
- const base = resolve(fromDir, specifier);
71
- // TypeScript sources are imported using their emitted `.js` extension
72
- // (`import ... from "./foo.js"`), so map the extension back to the source
73
- // file before probing the usual suffixes.
74
+ /**
75
+ * Resolve a path on disk, tolerating TypeScript's `.js` import convention
76
+ * (`import "./foo.js"` pointing at `foo.ts`) and Node's directory/`index`
77
+ * conventions.
78
+ */
79
+ function resolvePath(base: string): string | undefined {
80
+ // A JavaScript specifier may refer to a TypeScript source that will emit it
81
+ // (`import "./foo.js"` pointing at `foo.ts`). Probe the matching TypeScript
82
+ // extensions after the literal path: `.js`/`.jsx` -> `.ts`/`.tsx`,
83
+ // `.mjs` -> `.mts`, `.cjs` -> `.cts`.
74
84
  const candidates = [base];
75
- const jsExtension = /\.(?:m|c)?jsx?$/.exec(base);
85
+ const jsExtension = /\.((?:m|c)?jsx?)$/.exec(base);
76
86
  if (jsExtension) {
77
87
  const stem = base.slice(0, jsExtension.index);
78
- candidates.push(`${stem}.ts`, `${stem}.tsx`, `${stem}.mts`, `${stem}.cts`);
88
+ switch (jsExtension[1]) {
89
+ case "jsx":
90
+ candidates.push(`${stem}.tsx`, `${stem}.ts`);
91
+ break;
92
+ case "mjs":
93
+ candidates.push(`${stem}.mts`);
94
+ break;
95
+ case "cjs":
96
+ candidates.push(`${stem}.cts`);
97
+ break;
98
+ default:
99
+ candidates.push(`${stem}.ts`, `${stem}.tsx`);
100
+ break;
101
+ }
79
102
  }
80
103
  for (const candidate of candidates) {
81
104
  if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
@@ -84,9 +107,197 @@ function resolveModule(fromDir: string, specifier: string): string | undefined {
84
107
  const candidate = base + suffix;
85
108
  if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
86
109
  }
110
+ if (existsSync(base) && statSync(base).isDirectory()) return resolveDirectory(base);
111
+ return undefined;
112
+ }
113
+
114
+ /** Resolve a directory to its package entry or an `index` file. */
115
+ function resolveDirectory(directory: string): string | undefined {
116
+ const pkg = readPackageJson(directory);
117
+ if (pkg && typeof pkg.main === "string") {
118
+ const main = resolvePath(resolve(directory, pkg.main));
119
+ if (main) return main;
120
+ }
121
+ for (const suffix of INDEX_SUFFIXES) {
122
+ const candidate = join(directory, `index${suffix}`);
123
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
124
+ }
125
+ return undefined;
126
+ }
127
+
128
+ function resolveModule(fromDir: string, specifier: string): string | undefined {
129
+ return resolvePath(resolve(fromDir, specifier));
130
+ }
131
+
132
+ interface PackageJson {
133
+ readonly main?: string;
134
+ readonly module?: string;
135
+ readonly exports?: unknown;
136
+ }
137
+
138
+ function readPackageJson(directory: string): PackageJson | undefined {
139
+ const file = join(directory, "package.json");
140
+ if (!existsSync(file) || !statSync(file).isFile()) return undefined;
141
+ try {
142
+ const parsed = JSON.parse(readFileSync(file, "utf8")) as unknown;
143
+ return parsed && typeof parsed === "object" ? (parsed as PackageJson) : undefined;
144
+ } catch {
145
+ return undefined;
146
+ }
147
+ }
148
+
149
+ /** Split `pkg/sub/path` or `@scope/pkg/sub/path` into package name and subpath. */
150
+ function splitPackageSpecifier(specifier: string): { packageName: string; subpath: string } {
151
+ const parts = specifier.split("/");
152
+ if (specifier.startsWith("@")) {
153
+ return { packageName: parts.slice(0, 2).join("/"), subpath: parts.slice(2).join("/") };
154
+ }
155
+ return { packageName: parts[0] ?? specifier, subpath: parts.slice(1).join("/") };
156
+ }
157
+
158
+ /**
159
+ * Resolve a bare specifier against the `node_modules` directories above
160
+ * `fromDir`, following a package's `exports` map and falling back to
161
+ * `module`/`main`/`index`. Packages that only ship CommonJS resolve here too;
162
+ * their `require` calls are then rejected during code generation.
163
+ */
164
+ function resolveNodePackage(fromDir: string, specifier: string): string | undefined {
165
+ const { packageName, subpath } = splitPackageSpecifier(specifier);
166
+ if (!packageName || packageName.startsWith(".")) return undefined;
167
+ let directory = fromDir;
168
+ for (;;) {
169
+ const packageDir = join(directory, "node_modules", packageName);
170
+ if (existsSync(packageDir) && statSync(packageDir).isDirectory()) {
171
+ const resolved = subpath
172
+ ? resolvePackageSubpath(packageDir, subpath)
173
+ : resolvePackageEntry(packageDir);
174
+ if (resolved) return resolved;
175
+ }
176
+ const parent = dirname(directory);
177
+ if (parent === directory) return undefined;
178
+ directory = parent;
179
+ }
180
+ }
181
+
182
+ function resolvePackageEntry(packageDir: string): string | undefined {
183
+ const pkg = readPackageJson(packageDir);
184
+ if (pkg) {
185
+ const exported = resolveExportTarget(subpathValue(pkg.exports, "."));
186
+ if (exported) {
187
+ const resolved = resolvePath(resolve(packageDir, exported));
188
+ if (resolved) return resolved;
189
+ }
190
+ if (typeof pkg.module === "string") {
191
+ const resolved = resolvePath(resolve(packageDir, pkg.module));
192
+ if (resolved) return resolved;
193
+ }
194
+ if (typeof pkg.main === "string") {
195
+ const resolved = resolvePath(resolve(packageDir, pkg.main));
196
+ if (resolved) return resolved;
197
+ }
198
+ }
199
+ return resolveDirectory(packageDir);
200
+ }
201
+
202
+ function resolvePackageSubpath(packageDir: string, subpath: string): string | undefined {
203
+ const pkg = readPackageJson(packageDir);
204
+ const exported = resolveExportTarget(subpathValue(pkg?.exports, `./${subpath}`));
205
+ if (exported) {
206
+ const resolved = resolvePath(resolve(packageDir, exported));
207
+ if (resolved) return resolved;
208
+ }
209
+ return resolvePath(join(packageDir, subpath));
210
+ }
211
+
212
+ /** Pick the export target for a subpath (`"."` or `"./sub"`) from an exports map. */
213
+ function subpathValue(exportsField: unknown, subpath: string): unknown {
214
+ if (exportsField === undefined) return undefined;
215
+ if (typeof exportsField === "string" || Array.isArray(exportsField)) {
216
+ return subpath === "." ? exportsField : undefined;
217
+ }
218
+ if (!exportsField || typeof exportsField !== "object") return undefined;
219
+ const record = exportsField as Record<string, unknown>;
220
+ const keys = Object.keys(record);
221
+ // A map without `.`-prefixed keys is a condition map for the root export.
222
+ if (!keys.some((key) => key.startsWith("."))) {
223
+ return subpath === "." ? exportsField : undefined;
224
+ }
225
+ if (subpath in record) return record[subpath];
226
+ // Wildcard patterns such as `"./*": "./dist/*.js"`.
227
+ for (const key of keys) {
228
+ const star = key.indexOf("*");
229
+ if (star === -1) continue;
230
+ const prefix = key.slice(0, star);
231
+ const suffix = key.slice(star + 1);
232
+ if (subpath.startsWith(prefix) && subpath.endsWith(suffix)) {
233
+ const matched = subpath.slice(prefix.length, subpath.length - suffix.length);
234
+ return substituteStar(record[key], matched);
235
+ }
236
+ }
237
+ return undefined;
238
+ }
239
+
240
+ function substituteStar(target: unknown, matched: string): unknown {
241
+ if (typeof target === "string") return target.split("*").join(matched);
242
+ if (Array.isArray(target)) return target.map((entry) => substituteStar(entry, matched));
243
+ if (target && typeof target === "object") {
244
+ const record: Record<string, unknown> = {};
245
+ for (const [key, value] of Object.entries(target)) record[key] = substituteStar(value, matched);
246
+ return record;
247
+ }
248
+ return target;
249
+ }
250
+
251
+ /** Pick a runtime string from a (possibly conditional) exports target. */
252
+ function resolveExportTarget(target: unknown): string | undefined {
253
+ if (typeof target === "string") return target;
254
+ if (Array.isArray(target)) {
255
+ for (const entry of target) {
256
+ const resolved = resolveExportTarget(entry);
257
+ if (resolved) return resolved;
258
+ }
259
+ return undefined;
260
+ }
261
+ if (target && typeof target === "object") {
262
+ const record = target as Record<string, unknown>;
263
+ for (const condition of EXPORT_CONDITIONS) {
264
+ if (condition in record) {
265
+ const resolved = resolveExportTarget(record[condition]);
266
+ if (resolved) return resolved;
267
+ }
268
+ }
269
+ }
87
270
  return undefined;
88
271
  }
89
272
 
273
+ type DependencyResolution =
274
+ | { readonly kind: "external" }
275
+ | { readonly kind: "file"; readonly path: string }
276
+ | { readonly kind: "missing" };
277
+
278
+ /**
279
+ * Classify an import specifier. Known platform modules (extension modules and
280
+ * `node:` builtins) are left for code generation; relative paths and
281
+ * `node_modules` packages are resolved to a source file to bundle; a relative
282
+ * path that does not exist is an error.
283
+ */
284
+ function classifyDependency(
285
+ fromDir: string,
286
+ specifier: string,
287
+ externalSpecifiers: ReadonlySet<string>,
288
+ ): DependencyResolution {
289
+ if (specifier.startsWith("node:") || externalSpecifiers.has(specifier)) {
290
+ return { kind: "external" };
291
+ }
292
+ const resolved = isRelativeSpecifier(specifier)
293
+ ? resolveModule(fromDir, specifier)
294
+ : resolveNodePackage(fromDir, specifier);
295
+ if (resolved) return { kind: "file", path: resolved };
296
+ // A bare specifier that is not a file may still be a platform module the
297
+ // generator knows about (e.g. when the caller passes no extension registry).
298
+ return isRelativeSpecifier(specifier) ? { kind: "missing" } : { kind: "external" };
299
+ }
300
+
90
301
  function hasModifier(node: Node, kind: ModifierKind): boolean {
91
302
  const modifiers = (node as { modifiers?: { modifierKind: ModifierKind }[] }).modifiers;
92
303
  return !!modifiers && modifiers.some((modifier) => modifier.modifierKind === kind);
@@ -111,7 +322,11 @@ function declarationName(node: Node): Identifier | undefined {
111
322
  }
112
323
 
113
324
  /** Load, parse and bind the entry module and every module it imports. */
114
- function loadGraph(entryPath: string, diagnostics: DiagnosticBag): ModuleRecord[] | undefined {
325
+ function loadGraph(
326
+ entryPath: string,
327
+ diagnostics: DiagnosticBag,
328
+ externalSpecifiers: ReadonlySet<string>,
329
+ ): ModuleRecord[] | undefined {
115
330
  const records = new Map<string, ModuleRecord>();
116
331
  const order: ModuleRecord[] = [];
117
332
  const visiting = new Set<string>();
@@ -152,9 +367,9 @@ function loadGraph(entryPath: string, diagnostics: DiagnosticBag): ModuleRecord[
152
367
  for (const statement of sourceFile.statements) {
153
368
  const specifier = moduleSpecifierOf(statement);
154
369
  if (!specifier) continue;
155
- if (isExternalSpecifier(specifier)) continue;
156
- const dependency = resolveModule(dirname(path), specifier);
157
- if (!dependency) {
370
+ const dependency = classifyDependency(dirname(path), specifier, externalSpecifiers);
371
+ if (dependency.kind === "external") continue;
372
+ if (dependency.kind === "missing") {
158
373
  diagnostics.error(
159
374
  DiagnosticCode.CodegenError,
160
375
  `Cannot resolve module '${specifier}' from '${path}'`,
@@ -164,7 +379,7 @@ function loadGraph(entryPath: string, diagnostics: DiagnosticBag): ModuleRecord[
164
379
  failed = true;
165
380
  continue;
166
381
  }
167
- load(dependency);
382
+ load(dependency.path);
168
383
  }
169
384
  visiting.delete(path);
170
385
  order.push(record);
@@ -215,14 +430,18 @@ function topLevelSymbols(record: ModuleRecord): SymbolInfo[] {
215
430
  * Merge every module's statements into a single file. Dependencies come first
216
431
  * (post-order over the import graph), the entry module last.
217
432
  */
218
- export function bundleModules(entryPath: string, diagnostics: DiagnosticBag): BundleResult | undefined {
219
- const records = loadGraph(entryPath, diagnostics);
433
+ export function bundleModules(
434
+ entryPath: string,
435
+ diagnostics: DiagnosticBag,
436
+ externalSpecifiers: ReadonlySet<string> = new Set(),
437
+ ): BundleResult | undefined {
438
+ const records = loadGraph(entryPath, diagnostics, externalSpecifiers);
220
439
  if (!records) return undefined;
221
440
 
222
441
  const byPath = new Map(records.map((record) => [record.path, record]));
223
442
  const dependencyOf = (record: ModuleRecord, specifier: string): ModuleRecord | undefined => {
224
- const resolved = resolveModule(dirname(record.path), specifier);
225
- return resolved ? byPath.get(resolved) : undefined;
443
+ const resolution = classifyDependency(dirname(record.path), specifier, externalSpecifiers);
444
+ return resolution.kind === "file" ? byPath.get(resolution.path) : undefined;
226
445
  };
227
446
 
228
447
  // Phase 1: record every top-level binding and pick a unique final name.
@@ -328,8 +547,14 @@ export function bundleModules(entryPath: string, diagnostics: DiagnosticBag): Bu
328
547
  for (const record of records) {
329
548
  for (const statement of record.sourceFile.statements) {
330
549
  if (statement.kind === SyntaxKind.ImportDeclaration) {
331
- // External (extension) imports stay in place for code generation.
332
- if (isExternalSpecifier((statement as ImportDeclaration).moduleSpecifier.value)) {
550
+ // External (extension / built-in) imports stay in place for codegen.
551
+ const declaration = statement as ImportDeclaration;
552
+ const resolution = classifyDependency(
553
+ dirname(record.path),
554
+ declaration.moduleSpecifier.value,
555
+ externalSpecifiers,
556
+ );
557
+ if (resolution.kind === "external") {
333
558
  merged.push(statement);
334
559
  } else {
335
560
  const synthetic = namespaceStatements.get(statement);
@@ -22,15 +22,6 @@ export function findRuntimeDir(): string {
22
22
  throw new Error("Unable to locate the xbintsc runtime directory (missing runtime/rt.h)");
23
23
  }
24
24
 
25
- export function findPackageRoot(): string {
26
- return resolve(findRuntimeDir(), "..");
27
- }
28
-
29
- /** `<package>/vendor` — where a bundled toolchain is assembled (may not exist). */
30
- export function vendorRootDir(): string {
31
- return resolve(findPackageRoot(), "vendor");
32
- }
33
-
34
25
  /** Platform-architecture slug, e.g. `darwin-arm64`, `linux-x64`, `win32-x64`. */
35
26
  export function platformSlug(): string {
36
27
  return `${process.platform}-${process.arch}`;
@@ -68,25 +59,3 @@ export function releaseArchiveFileName(
68
59
  export function releaseArchiveExtension(useZstd: boolean): string {
69
60
  return useZstd ? ".tar.zst" : ".tar.gz";
70
61
  }
71
-
72
- /**
73
- * Directory that may hold a toolchain shipped next to the package:
74
- * `<package>/vendor/<os>-<arch>` (or a generic `vendor/`). Undefined when none
75
- * is present, in which case the driver falls back to the system PATH.
76
- */
77
- export function findVendorDir(): string | undefined {
78
- const roots: string[] = [];
79
- try {
80
- roots.push(findPackageRoot());
81
- } catch {
82
- // `runtime/` may be absent (e.g. a bare `emit` install); vendor is optional.
83
- }
84
- roots.push(resolve(here, "../../.."), resolve(here, "../.."), process.cwd());
85
- for (const root of roots) {
86
- const specific = join(root, "vendor", platformSlug());
87
- if (existsSync(specific)) return specific;
88
- const generic = join(root, "vendor");
89
- if (existsSync(generic)) return generic;
90
- }
91
- return undefined;
92
- }
@@ -3,23 +3,23 @@
3
3
  *
4
4
  * xbintsc shells out to a clang-compatible driver for three jobs: lowering LLVM
5
5
  * IR to an object, compiling the C runtime, and linking. This module decides
6
- * *which* driver to use, in a fixed priority order, so a bundled toolchain can
7
- * shadow the system one without touching the driver code:
6
+ * *which* driver to use, in a fixed priority order:
8
7
  *
9
8
  * 1. an explicit `xbintsc_CLANG` / `xbintsc_TOOLCHAIN`
10
- * 2. a toolchain shipped next to the package (`vendor/<os>-<arch>/bin`)
11
- * 3. the system `PATH` (`findClang`)
9
+ * 2. the system `PATH` (`findClang`)
10
+ *
11
+ * xbintsc does not ship a compiler. Every host must provide a clang-compatible
12
+ * toolchain (see `doc/requirements.md`): the Xcode Command Line Tools on macOS,
13
+ * the distribution's clang/lld on Linux, and LLVM plus the Visual Studio C++
14
+ * build tools (the MSVC ABI) on Windows.
12
15
  *
13
16
  * Extra linker arguments (for example `-fuse-ld=lld`) can be supplied through
14
17
  * `xbintsc_LINKER_ARGS`.
15
18
  */
16
19
 
17
- import { existsSync } from "node:fs";
18
- import { join } from "node:path";
19
- import { findVendorDir } from "./paths.js";
20
20
  import { findClang, realRunner, type Runner } from "./toolchain.js";
21
21
 
22
- export type ToolchainSource = "env" | "vendor" | "system";
22
+ export type ToolchainSource = "env" | "system";
23
23
 
24
24
  export interface ResolvedToolchain {
25
25
  /** Path or name of the clang-compatible driver to invoke. */
@@ -28,17 +28,14 @@ export interface ResolvedToolchain {
28
28
  readonly source: ToolchainSource;
29
29
  /** Extra arguments to pass to the link step. */
30
30
  readonly linkerArgs: readonly string[];
31
- /**
32
- * Extra environment for the toolchain subprocesses, merged over the current
33
- * environment. Used to point `LD_LIBRARY_PATH` at bundled shared libraries
34
- * (for example the legacy `libtinfo.so.5` the official Linux build needs).
35
- */
31
+ /** Extra environment for the toolchain subprocesses, merged over the current
32
+ * environment. */
36
33
  readonly env: Record<string, string>;
37
34
  }
38
35
 
39
- function works(runner: Runner, command: string, env?: Record<string, string>): boolean {
36
+ function works(runner: Runner, command: string): boolean {
40
37
  try {
41
- return runner.run(command, ["--version"], env ? { env } : undefined).status === 0;
38
+ return runner.run(command, ["--version"]).status === 0;
42
39
  } catch {
43
40
  return false;
44
41
  }
@@ -50,18 +47,6 @@ function linkerArgs(fallback: readonly string[]): string[] {
50
47
  return [...fallback];
51
48
  }
52
49
 
53
- /**
54
- * Environment overrides for a bundled toolchain. The official Linux build links
55
- * the removed `libtinfo.so.5` soname, so when we ship it under `vendor/lib/` we
56
- * prepend that directory to the dynamic linker search path.
57
- */
58
- function vendorEnv(vendor: string): Record<string, string> {
59
- const libDir = join(vendor, "lib");
60
- if (process.platform !== "linux" || !existsSync(libDir)) return {};
61
- const existing = process.env.LD_LIBRARY_PATH;
62
- return { LD_LIBRARY_PATH: existing ? libDir + ":" + existing : libDir };
63
- }
64
-
65
50
  /** Resolve the toolchain that `build` should use. See the module docblock. */
66
51
  export function resolveToolchain(runner: Runner = realRunner): ResolvedToolchain {
67
52
  const explicit = process.env.xbintsc_CLANG ?? process.env.xbintsc_TOOLCHAIN;
@@ -69,23 +54,5 @@ export function resolveToolchain(runner: Runner = realRunner): ResolvedToolchain
69
54
  return { clang: explicit, source: "env", linkerArgs: linkerArgs([]), env: {} };
70
55
  }
71
56
 
72
- const vendor = findVendorDir();
73
- if (vendor) {
74
- const env = vendorEnv(vendor);
75
- const names = process.platform === "win32" ? ["clang.exe", "clang"] : ["clang"];
76
- for (const name of names) {
77
- const candidate = join(vendor, "bin", name);
78
- if (existsSync(candidate) && works(runner, candidate, env)) {
79
- // A bundled toolchain ships `ld.lld`, so prefer lld over any system linker.
80
- return {
81
- clang: candidate,
82
- source: "vendor",
83
- linkerArgs: linkerArgs(["-fuse-ld=lld"]),
84
- env,
85
- };
86
- }
87
- }
88
- }
89
-
90
57
  return { clang: findClang(runner), source: "system", linkerArgs: linkerArgs([]), env: {} };
91
58
  }
@@ -64,8 +64,13 @@ export function findClang(runner: Runner = realRunner): string {
64
64
  "cc",
65
65
  ].filter((candidate): candidate is string => Boolean(candidate));
66
66
  for (const candidate of candidates) {
67
- const result = runner.run(candidate, ["--version"]);
68
- if (result.status === 0) return candidate;
67
+ let status: number;
68
+ try {
69
+ status = runner.run(candidate, ["--version"]).status;
70
+ } catch {
71
+ continue;
72
+ }
73
+ if (status === 0) return candidate;
69
74
  }
70
75
  throw new ToolchainError("clang --version", 1, "No C compiler found. Set xbintsc_CLANG to a clang binary.");
71
76
  }
@@ -18,9 +18,8 @@
18
18
  * {
19
19
  * "name": "mathx",
20
20
  * "description": "C++ math helpers",
21
- * "objects": ["build/libmathx.a"],
22
- * "linkerFlags": ["-lm"],
23
- * "linkerFlagsByPlatform": { "linux": ["-lstdc++"], "darwin": ["-lc++"] },
21
+ * "objects": ["build/mathx.o"],
22
+ * "linkerFlagsByPlatform": { "linux": ["-lstdc++", "-lm"], "darwin": ["-lc++"], "win32": ["-lmsvcprt"] },
24
23
  * "builtins": { "fastAdd": { "symbol": "mathx_add" } },
25
24
  * "modules": {
26
25
  * "mathx": {
@@ -1,20 +1,62 @@
1
1
  /**
2
- * Builtins for the filesystem operations in Node's `fs` module
3
- * (`existsSync`, directory handling, rename/copy, `statSync`, ...).
2
+ * Builtins for the filesystem operations in Node's `fs` module.
3
+ *
4
+ * Every runtime symbol uses the uniform `xt_value fn(int32_t, xt_value *)`
5
+ * ABI. xbintsc has no event loop, so only the synchronous API is exposed
6
+ * (`*Sync`), plus the watcher factories, whose objects never fire events.
4
7
  */
5
8
 
6
9
  import type { BuiltinFunction } from "../../registry.js";
7
10
 
8
11
  export const fsOpsBuiltins: Readonly<Record<string, BuiltinFunction>> = {
12
+ // Path / metadata -------------------------------------------------------
9
13
  existsSync: { symbol: "xt_node_exists" },
14
+ accessSync: { symbol: "xt_node_access" },
15
+ statSync: { symbol: "xt_node_stat" },
16
+ lstatSync: { symbol: "xt_node_lstat" },
17
+ statfsSync: { symbol: "xt_node_statfs" },
18
+ realpathSync: { symbol: "xt_node_realpath" },
19
+ chmodSync: { symbol: "xt_node_chmod" },
20
+ lchmodSync: { symbol: "xt_node_lchmod" },
21
+ chownSync: { symbol: "xt_node_chown" },
22
+ lchownSync: { symbol: "xt_node_lchown" },
23
+ truncateSync: { symbol: "xt_node_truncate" },
24
+ utimesSync: { symbol: "xt_node_utimes" },
25
+ lutimesSync: { symbol: "xt_node_lutimes" },
26
+
27
+ // Directories / entries -------------------------------------------------
10
28
  readdirSync: { symbol: "xt_node_read_dir" },
11
29
  mkdirSync: { symbol: "xt_node_mkdir" },
30
+ mkdtempSync: { symbol: "xt_node_mkdtemp" },
31
+ rmdirSync: { symbol: "xt_node_rmdir" },
12
32
  rmSync: { symbol: "xt_node_rm" },
13
33
  unlinkSync: { symbol: "xt_node_unlink" },
14
- rmdirSync: { symbol: "xt_node_rmdir" },
15
34
  renameSync: { symbol: "xt_node_rename" },
16
35
  copyFileSync: { symbol: "xt_node_copy_file" },
17
- realpathSync: { symbol: "xt_node_realpath" },
18
- statSync: { symbol: "xt_node_stat" },
19
- lstatSync: { symbol: "xt_node_lstat" },
36
+ cpSync: { symbol: "xt_node_cp" },
37
+ linkSync: { symbol: "xt_node_link" },
38
+ symlinkSync: { symbol: "xt_node_symlink" },
39
+ readlinkSync: { symbol: "xt_node_readlink" },
40
+ opendirSync: { symbol: "xt_node_opendir" },
41
+ globSync: { symbol: "xt_node_glob" },
42
+
43
+ // File descriptors ------------------------------------------------------
44
+ openSync: { symbol: "xt_node_open" },
45
+ closeSync: { symbol: "xt_node_close" },
46
+ readSync: { symbol: "xt_node_read" },
47
+ writeSync: { symbol: "xt_node_write" },
48
+ readvSync: { symbol: "xt_node_readv" },
49
+ writevSync: { symbol: "xt_node_writev" },
50
+ fstatSync: { symbol: "xt_node_fstat" },
51
+ fsyncSync: { symbol: "xt_node_fsync" },
52
+ fdatasyncSync: { symbol: "xt_node_fdatasync" },
53
+ ftruncateSync: { symbol: "xt_node_ftruncate" },
54
+ fchmodSync: { symbol: "xt_node_fchmod" },
55
+ fchownSync: { symbol: "xt_node_fchown" },
56
+ futimesSync: { symbol: "xt_node_futimes" },
57
+
58
+ // Watchers (API-shaped, no events) --------------------------------------
59
+ watch: { symbol: "xt_node_watch" },
60
+ watchFile: { symbol: "xt_node_watch_file" },
61
+ unwatchFile: { symbol: "xt_node_unwatch_file" },
20
62
  };
@@ -1,24 +1,46 @@
1
1
  /**
2
2
  * Node's `fs` module: synchronous file and directory operations backed by
3
3
  * `runtime/ext_node/fs`. Asynchronous callbacks are not supported (xbintsc has
4
- * no event loop), so only the `*Sync` API is exposed. Import the functions, e.g.
5
- * `import { readFileSync } from "fs"`.
4
+ * no event loop), so the API is the `*Sync` surface plus `watch`/`watchFile`
5
+ * (which return API-shaped watchers that never emit).
6
+ *
7
+ * `fs.constants` and `fs.promises` are exposed as value getters, so both
8
+ * `import { constants } from "fs"` and the `fs.constants.F_OK` property form
9
+ * work.
6
10
  */
7
11
 
8
12
  import type { NodeModule } from "../module.js";
9
13
  import { resolveFrom } from "../module.js";
14
+ import type { ModuleExports } from "../../registry.js";
10
15
  import { readFileBuiltins } from "./read-file.js";
11
16
  import { writeFileBuiltins } from "./write-file.js";
12
17
  import { fsOpsBuiltins } from "./fs-ops.js";
13
18
  import { fsStreamBuiltins } from "./streams.js";
14
19
 
20
+ const builtins = { ...readFileBuiltins, ...writeFileBuiltins, ...fsOpsBuiltins, ...fsStreamBuiltins };
21
+
22
+ const exports: ModuleExports = {
23
+ ...builtins,
24
+ constants: { valueSymbol: "xt_fs_constants" },
25
+ promises: { valueSymbol: "xt_fs_promises" },
26
+ };
27
+
15
28
  export const fsModule: NodeModule = {
16
29
  name: "fs",
17
30
  runtimeSources: () => [
18
31
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/read_file.c"),
19
32
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/write_file.c"),
20
33
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/fs_ops.c"),
34
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/fd_ops.c"),
35
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/meta_ops.c"),
36
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/link_ops.c"),
37
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/copy_ops.c"),
38
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/dir.c"),
39
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/glob.c"),
40
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/watch.c"),
41
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/constants.c"),
21
42
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/streams.c"),
22
43
  ],
23
- builtins: () => ({ ...readFileBuiltins, ...writeFileBuiltins, ...fsOpsBuiltins, ...fsStreamBuiltins }),
44
+ builtins: () => builtins,
45
+ exports: () => exports,
24
46
  };