@ttsc/wasm 0.30.4 → 0.31.1

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 (174) hide show
  1. package/README.md +3 -3
  2. package/build/build-wasm.cjs +5 -4
  3. package/build/go-build-cache.cjs +347 -0
  4. package/build/pack-prepare.cjs +3 -2
  5. package/cmd/ttsc-wasm/main.go +22 -10
  6. package/cmd/ttsc-wasm/main_wasm.go +2 -2
  7. package/dist/.ttsc-wasm-build.json +138 -58
  8. package/dist/go.mod +7 -5
  9. package/dist/ttsc.wasm +2 -2
  10. package/go.mod +7 -5
  11. package/go.sum +10 -10
  12. package/host/api.go +34 -41
  13. package/host/doc.go +7 -6
  14. package/host/expose.go +42 -0
  15. package/host/fountain.go +192 -22
  16. package/host/host.go +10 -25
  17. package/host/host_native.go +3 -5
  18. package/host/invoke.go +133 -0
  19. package/host/plugin.go +37 -72
  20. package/host/result.go +108 -0
  21. package/lib/src/BootTtscWorkerTerminationError.d.ts +20 -0
  22. package/lib/src/BootTtscWorkerTerminationError.js +20 -0
  23. package/lib/src/BootTtscWorkerTerminationError.js.map +1 -1
  24. package/lib/src/MemFSError.d.ts +21 -2
  25. package/lib/src/MemFSError.js +25 -2
  26. package/lib/src/MemFSError.js.map +1 -1
  27. package/lib/src/bootTtsc.d.ts +49 -2
  28. package/lib/src/bootTtsc.js +89 -23
  29. package/lib/src/bootTtsc.js.map +1 -1
  30. package/lib/src/createMemFS.d.ts +45 -0
  31. package/lib/src/createMemFS.js +269 -66
  32. package/lib/src/createMemFS.js.map +1 -1
  33. package/lib/src/parseResult.d.ts +19 -0
  34. package/lib/src/parseResult.js +19 -0
  35. package/lib/src/parseResult.js.map +1 -1
  36. package/lib/src/structures/IBootResult.d.ts +20 -2
  37. package/lib/src/structures/IBootTtscOptions.d.ts +28 -6
  38. package/lib/src/structures/IFileStats.d.ts +51 -0
  39. package/lib/src/structures/IMemFSHost.d.ts +89 -0
  40. package/lib/src/structures/ITtscApi.d.ts +154 -9
  41. package/lib/src/structures/ITtscBuildOpts.d.ts +18 -1
  42. package/lib/src/structures/ITtscCompileResult.d.ts +20 -2
  43. package/lib/src/structures/ITtscDiagnostic.d.ts +25 -1
  44. package/lib/src/structures/ITtscFileQuery.d.ts +19 -1
  45. package/lib/src/structures/ITtscFountainDiagnosticsResult.d.ts +20 -1
  46. package/lib/src/structures/ITtscNodeAtPositionResult.d.ts +18 -1
  47. package/lib/src/structures/ITtscNodeInfo.d.ts +23 -2
  48. package/lib/src/structures/ITtscPluginOpts.d.ts +24 -3
  49. package/lib/src/structures/ITtscPositionQuery.d.ts +21 -4
  50. package/lib/src/structures/ITtscReleaseSnapshotResult.d.ts +22 -2
  51. package/lib/src/structures/ITtscResult.d.ts +31 -5
  52. package/lib/src/structures/ITtscSnapshotHandle.d.ts +19 -1
  53. package/lib/src/structures/ITtscSnapshotResult.d.ts +22 -1
  54. package/lib/src/structures/ITtscSnapshotsResult.d.ts +19 -1
  55. package/lib/src/structures/ITtscSourceFileTextResult.d.ts +19 -1
  56. package/lib/src/structures/ITtscSourceFilesResult.d.ts +23 -2
  57. package/lib/src/structures/ITtscSymbolAtPositionResult.d.ts +18 -1
  58. package/lib/src/structures/ITtscSymbolDeclaration.d.ts +25 -2
  59. package/lib/src/structures/ITtscSymbolInfo.d.ts +19 -1
  60. package/lib/src/structures/ITtscTransformResult.d.ts +21 -3
  61. package/lib/src/structures/ITtscTypeAtPositionResult.d.ts +18 -1
  62. package/lib/src/structures/ITtscTypeInfo.d.ts +19 -1
  63. package/lib/src/structures/ITtscVersion.d.ts +28 -1
  64. package/lib/src/structures/IWasmExecFS.d.ts +310 -0
  65. package/package.json +4 -2
  66. package/shim-vendor/shim/ast/enums_gen.go +4 -2
  67. package/shim-vendor/shim/ast/go.mod +5 -5
  68. package/shim-vendor/shim/ast/go.sum +10 -10
  69. package/shim-vendor/shim/ast/lint.go +1088 -20
  70. package/shim-vendor/shim/ast/parent.go +38 -22
  71. package/shim-vendor/shim/ast/safe_text.go +48 -26
  72. package/shim-vendor/shim/ast/shim.go +1307 -20
  73. package/shim-vendor/shim/ast/test/next_jsdoc_comment_location_reaches_variable_statement_test.go +51 -0
  74. package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +24 -9
  75. package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +9 -5
  76. package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +13 -5
  77. package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +12 -5
  78. package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +35 -5
  79. package/shim-vendor/shim/astnav/go.mod +5 -5
  80. package/shim-vendor/shim/astnav/go.sum +12 -10
  81. package/shim-vendor/shim/astnav/shim.go +9 -0
  82. package/shim-vendor/shim/bundled/go.mod +7 -1
  83. package/shim-vendor/shim/bundled/go.sum +12 -0
  84. package/shim-vendor/shim/checker/enums_gen.go +4 -2
  85. package/shim-vendor/shim/checker/extra-shim.json +6 -0
  86. package/shim-vendor/shim/checker/go.mod +5 -5
  87. package/shim-vendor/shim/checker/go.sum +10 -10
  88. package/shim-vendor/shim/checker/node_builder_fields_gen.go +19 -0
  89. package/shim-vendor/shim/checker/shim.go +827 -114
  90. package/shim-vendor/shim/compiler/emit_sourcemap.go +68 -29
  91. package/shim-vendor/shim/compiler/emitassemble.go +36 -2
  92. package/shim-vendor/shim/compiler/emithook.go +13 -3
  93. package/shim-vendor/shim/compiler/go.mod +5 -5
  94. package/shim-vendor/shim/compiler/go.sum +10 -10
  95. package/shim-vendor/shim/compiler/incremental.go +56 -20
  96. package/shim-vendor/shim/compiler/resolution.go +92 -9
  97. package/shim-vendor/shim/core/enums_gen.go +2 -2
  98. package/shim-vendor/shim/core/go.mod +6 -1
  99. package/shim-vendor/shim/core/go.sum +10 -0
  100. package/shim-vendor/shim/core/modulekind.go +9 -0
  101. package/shim-vendor/shim/core/shim.go +157 -6
  102. package/shim-vendor/shim/diagnosticwriter/go.mod +10 -1
  103. package/shim-vendor/shim/diagnosticwriter/go.sum +20 -0
  104. package/shim-vendor/shim/diagnosticwriter/lint.go +188 -10
  105. package/shim-vendor/shim/diagnosticwriter/shim.go +14 -0
  106. package/shim-vendor/shim/lsp/go.mod +5 -5
  107. package/shim-vendor/shim/lsp/go.sum +10 -10
  108. package/shim-vendor/shim/lsp/shim.go +78 -2
  109. package/shim-vendor/shim/parser/go.mod +10 -1
  110. package/shim-vendor/shim/parser/go.sum +22 -0
  111. package/shim-vendor/shim/printer/emithost.go +46 -1
  112. package/shim-vendor/shim/printer/go.mod +5 -5
  113. package/shim-vendor/shim/printer/go.sum +10 -10
  114. package/shim-vendor/shim/printer/shim.go +114 -7
  115. package/shim-vendor/shim/printer/sourcemap.go +55 -0
  116. package/shim-vendor/shim/scanner/go.mod +10 -1
  117. package/shim-vendor/shim/scanner/go.sum +20 -0
  118. package/shim-vendor/shim/scanner/regexp.go +19 -4
  119. package/shim-vendor/shim/scanner/shim.go +3 -0
  120. package/shim-vendor/shim/stringutil/extra-shim.json +1 -0
  121. package/shim-vendor/shim/stringutil/go.mod +5 -0
  122. package/shim-vendor/shim/stringutil/go.sum +2 -0
  123. package/shim-vendor/shim/stringutil/shim.go +119 -0
  124. package/shim-vendor/shim/transformers/go.mod +5 -5
  125. package/shim-vendor/shim/transformers/go.sum +12 -10
  126. package/shim-vendor/shim/transformers/shim.go +12 -3
  127. package/shim-vendor/shim/tsoptions/command_line_raw.go +44 -0
  128. package/shim-vendor/shim/tsoptions/go.mod +10 -1
  129. package/shim-vendor/shim/tsoptions/go.sum +22 -0
  130. package/shim-vendor/shim/tsoptions/shim.go +1 -1
  131. package/shim-vendor/shim/tspath/go.mod +1 -1
  132. package/shim-vendor/shim/tspath/go.sum +6 -0
  133. package/shim-vendor/shim/vfs/cachedvfs/go.mod +3 -1
  134. package/shim-vendor/shim/vfs/cachedvfs/go.sum +12 -0
  135. package/shim-vendor/shim/vfs/decode.go +26 -0
  136. package/shim-vendor/shim/vfs/decode_test.go +42 -0
  137. package/shim-vendor/shim/vfs/go.mod +7 -1
  138. package/shim-vendor/shim/vfs/go.sum +12 -0
  139. package/shim-vendor/shim/vfs/osvfs/go.mod +7 -1
  140. package/shim-vendor/shim/vfs/osvfs/go.sum +12 -0
  141. package/src/BootTtscWorkerTerminationError.ts +22 -0
  142. package/src/MemFSError.ts +29 -2
  143. package/src/bootTtsc.ts +104 -28
  144. package/src/createMemFS.ts +291 -64
  145. package/src/parseResult.ts +19 -0
  146. package/src/structures/IBootResult.ts +21 -2
  147. package/src/structures/IBootTtscOptions.ts +32 -6
  148. package/src/structures/IFileStats.ts +65 -0
  149. package/src/structures/IMemFSHost.ts +97 -0
  150. package/src/structures/ITtscApi.ts +154 -9
  151. package/src/structures/ITtscBuildOpts.ts +19 -1
  152. package/src/structures/ITtscCompileResult.ts +21 -2
  153. package/src/structures/ITtscDiagnostic.ts +32 -1
  154. package/src/structures/ITtscFileQuery.ts +19 -1
  155. package/src/structures/ITtscFountainDiagnosticsResult.ts +20 -1
  156. package/src/structures/ITtscNodeAtPositionResult.ts +18 -1
  157. package/src/structures/ITtscNodeInfo.ts +27 -2
  158. package/src/structures/ITtscPluginOpts.ts +28 -3
  159. package/src/structures/ITtscPositionQuery.ts +21 -4
  160. package/src/structures/ITtscReleaseSnapshotResult.ts +22 -2
  161. package/src/structures/ITtscResult.ts +34 -5
  162. package/src/structures/ITtscSnapshotHandle.ts +19 -1
  163. package/src/structures/ITtscSnapshotResult.ts +22 -1
  164. package/src/structures/ITtscSnapshotsResult.ts +19 -1
  165. package/src/structures/ITtscSourceFileTextResult.ts +19 -1
  166. package/src/structures/ITtscSourceFilesResult.ts +23 -2
  167. package/src/structures/ITtscSymbolAtPositionResult.ts +18 -1
  168. package/src/structures/ITtscSymbolDeclaration.ts +27 -2
  169. package/src/structures/ITtscSymbolInfo.ts +23 -1
  170. package/src/structures/ITtscTransformResult.ts +22 -3
  171. package/src/structures/ITtscTypeAtPositionResult.ts +18 -1
  172. package/src/structures/ITtscTypeInfo.ts +20 -1
  173. package/src/structures/ITtscVersion.ts +33 -1
  174. package/src/structures/IWasmExecFS.ts +336 -0
package/README.md CHANGED
@@ -118,7 +118,7 @@ There is no boot deadline. How long a fetch and instantiation take belongs to th
118
118
 
119
119
  Once `go.run` starts, JavaScript cannot terminate the Go wasm runtime. A boot failure from that point throws `BootTtscWorkerTerminationError` with code `TTSC_WASM_WORKER_TERMINATION_REQUIRED`, and later boots for the same `apiName` reject without installing another readiness bridge. Terminate and replace that Worker before retrying.
120
120
 
121
- Booting two wasms with the same `apiName` overwrites the previous global binding; pick a unique `apiName` per binary. `bootTtsc` also installs shared `fs` and `process` globals in its Worker, so use separate Workers when binaries need independent filesystems.
121
+ Use one Go runtime per Worker. A different API name does not isolate the shared `Go`, `fs` and `process` globals, so another binary needs another Worker. Calls joining the same API/binary boot reuse its actual filesystem; a different explicit host or `wasmExecUrl` is rejected rather than silently ignored. The default runtime-script URL is the binary's sibling `wasm_exec.js`, without its query or fragment.
122
122
 
123
123
  ## Fountain API (snapshot, AST, type checker)
124
124
 
@@ -171,7 +171,7 @@ Verbs and payload types:
171
171
 
172
172
  `getNodeAtPosition` returns the syntax token touching `position`, including punctuation. `getTypeAtPosition` resolves that token to its enclosing semantic AST node, while `getSymbolAtPosition` queries the token directly. Both return `null` when there is no semantic answer. Whitespace and comments return `null` for all three verbs.
173
173
 
174
- `position` is a **byte offset** into the source text and must satisfy `0 <= position < UTF-8 byte length of sourceText`. The same coordinate TypeScript-Go uses internally. JS callers that have a UTF-16 `(line, character)` pair (e.g. From Monaco) must convert it before calling.
174
+ `position` is a finite integer **byte offset** into the source text and must satisfy `0 <= position < UTF-8 byte length of sourceText`. This is the coordinate TypeScript-Go uses internally. Fractional and non-finite values are rejected before integer conversion. JS callers that have a UTF-16 `(line, character)` pair (e.g. From Monaco) must convert it before calling.
175
175
 
176
176
  **Lifecycle:** JS owns the handle. The wasm keeps the program (parsed AST, checker pool lease, every source file) alive until you call `releaseSnapshot`. Leaking handles leaks memory in the wasm linear heap.
177
177
 
@@ -195,7 +195,7 @@ Use `invocation.Go` for asynchronous work that belongs to the result. Register i
195
195
  The published `@ttsc/wasm` tarball ships:
196
196
 
197
197
  - A rewritten root `go.mod` whose `replace` directives point at `./shim-vendor/shim/*` (vendored at pack time from `packages/ttsc/shim/`).
198
- - The full `host/`, `cmd/`, `build/` Go source so consumers can `go build -tags '...'` their own wasm against your host helper.
198
+ - The full `host/` and `cmd/` Go source and the `build/` scripts so consumers can `go build -tags '...'` their own wasm against your host helper.
199
199
  - `dist/ttsc.wasm` + `dist/wasm_exec.js`: the no-plugin sanity binary and the Go runtime loader.
200
200
 
201
201
  The tarball intentionally drops the `replace github.com/samchon/ttsc/packages/ttsc => ../ttsc` directive that the in-repo `go.mod` carries: consumers of the published module who want to rebuild the wasm must supply their own `replace` (or vendor `packages/ttsc` themselves). The published `dist/ttsc.wasm` is plug-and-play for runtime use; the Go module is for **plugin authors extending the host**, not for vanilla consumers.
@@ -10,7 +10,7 @@ const cp = require("child_process");
10
10
  const fs = require("fs");
11
11
  const path = require("path");
12
12
 
13
- const { createGoBuildCache } = require("../../../scripts/go-build-cache.cjs");
13
+ const { createGoBuildCache } = require("./go-build-cache.cjs");
14
14
 
15
15
  const packageRoot = path.resolve(__dirname, "..");
16
16
  const repoRoot = path.resolve(packageRoot, "..", "..");
@@ -129,9 +129,10 @@ function buildWasm() {
129
129
  // Mirror packages/ttsc/shim/* into packages/wasm/shim-vendor/shim/* so the
130
130
  // published @ttsc/wasm tarball is self-contained: consumers extending the
131
131
  // wasm binary against the @ttsc/wasm Go module no longer need a sibling
132
- // `packages/ttsc/shim/` working tree. The vendored copy is checked in so
133
- // `pnpm pack --dry-run` agrees with what `go build` would see at install
134
- // time. The working-tree `go.mod` still points at `../ttsc/shim/*` for
132
+ // `packages/ttsc/shim/` working tree. The vendored copy is a build artifact:
133
+ // `shim-vendor/` is git-ignored and regenerated by every uncached build, and
134
+ // its files are snapshotted with the cache artifacts, so an interrupted or
135
+ // edited copy is rebuilt. The working-tree `go.mod` still points at `../ttsc/shim/*` for
135
136
  // local dev; the published `go.mod` is rewritten into `dist/go.mod` and
136
137
  // swapped in via prepack/postpack.
137
138
  function vendorShim() {
@@ -0,0 +1,347 @@
1
+ const cp = require("node:child_process");
2
+ const crypto = require("node:crypto");
3
+ const fs = require("node:fs");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+
7
+ const SOURCE_FIELDS = [
8
+ "GoFiles",
9
+ "CgoFiles",
10
+ "CFiles",
11
+ "CXXFiles",
12
+ "MFiles",
13
+ "HFiles",
14
+ "FFiles",
15
+ "SFiles",
16
+ "SysoFiles",
17
+ "EmbedFiles",
18
+ ];
19
+
20
+ function createGoBuildCache({
21
+ artifactPaths,
22
+ buildArguments,
23
+ cachePath,
24
+ cwd,
25
+ dependencyPackages,
26
+ environment,
27
+ extraFiles = [],
28
+ force = false,
29
+ inputDirectories = [],
30
+ execFileSync = cp.execFileSync,
31
+ }) {
32
+ const identity = createInputIdentity({
33
+ buildArguments,
34
+ cwd,
35
+ dependencyPackages,
36
+ environment,
37
+ extraFiles,
38
+ inputDirectories,
39
+ execFileSync,
40
+ });
41
+
42
+ return {
43
+ identity,
44
+ isCurrent() {
45
+ if (force) return false;
46
+ const record = readRecord(cachePath);
47
+ if (record?.identity !== identity.hash) return false;
48
+ try {
49
+ return (
50
+ stableJson(record.artifacts) ===
51
+ stableJson(snapshotPaths(artifactPaths))
52
+ );
53
+ } catch {
54
+ return false;
55
+ }
56
+ },
57
+ write() {
58
+ const record = {
59
+ schema: 1,
60
+ identity: identity.hash,
61
+ artifacts: snapshotPaths(artifactPaths),
62
+ };
63
+ writeAtomically(cachePath, `${JSON.stringify(record, null, 2)}\n`);
64
+ },
65
+ };
66
+ }
67
+
68
+ function createInputIdentity({
69
+ buildArguments,
70
+ cwd,
71
+ dependencyPackages,
72
+ environment,
73
+ extraFiles,
74
+ inputDirectories,
75
+ execFileSync,
76
+ }) {
77
+ // Dependency JSON grows with the entire selected package graph. Give stdout
78
+ // a file descriptor instead of execFileSync's bounded capture pipe; parse
79
+ // every byte only after Go succeeds, and reclaim the descriptor and file on
80
+ // both the success and failure paths. The temporary input is outside cwd so
81
+ // directory-shaped cache inputs cannot observe it.
82
+ const runGo = (args) => {
83
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-go-list-"));
84
+ const output = path.join(directory, "stdout.json");
85
+ let descriptor;
86
+ try {
87
+ descriptor = fs.openSync(output, "wx");
88
+ execFileSync("go", args, {
89
+ cwd,
90
+ env: { ...process.env, ...environment },
91
+ stdio: ["ignore", descriptor, "inherit"],
92
+ windowsHide: true,
93
+ });
94
+ return fs.readFileSync(output, "utf8");
95
+ } finally {
96
+ try {
97
+ if (descriptor !== undefined) fs.closeSync(descriptor);
98
+ } finally {
99
+ fs.rmSync(directory, { recursive: true, force: true });
100
+ }
101
+ }
102
+ };
103
+ const packages = parseJsonStream(
104
+ runGo(["list", "-deps", "-json", ...dependencyPackages]),
105
+ );
106
+ const modules = parseJsonStream(runGo(["list", "-m", "-json", "all"]));
107
+ const toolchain = JSON.parse(
108
+ runGo([
109
+ "env",
110
+ "-json",
111
+ "GOVERSION",
112
+ "GOROOT",
113
+ "GOOS",
114
+ "GOARCH",
115
+ "GOFLAGS",
116
+ "GOEXPERIMENT",
117
+ "GOTOOLCHAIN",
118
+ "GOTOOLDIR",
119
+ "GOWASM",
120
+ "CGO_ENABLED",
121
+ "GOWORK",
122
+ ]),
123
+ );
124
+ addToolchainBinaryIdentity(toolchain);
125
+ const files = new Set([path.resolve(__filename)]);
126
+
127
+ for (const pkg of packages) {
128
+ if (!pkg.Dir) continue;
129
+ for (const field of SOURCE_FIELDS) {
130
+ for (const file of pkg[field] ?? []) {
131
+ files.add(path.join(pkg.Dir, file));
132
+ }
133
+ }
134
+ if (pkg.Module?.GoMod) addIfFile(files, pkg.Module.GoMod);
135
+ }
136
+ for (const mod of modules) {
137
+ if (!mod.GoMod) continue;
138
+ addIfFile(files, mod.GoMod);
139
+ addIfFile(files, path.join(path.dirname(mod.GoMod), "go.sum"));
140
+ }
141
+ if (toolchain.GOWORK && toolchain.GOWORK !== "off") {
142
+ addIfFile(files, toolchain.GOWORK);
143
+ addIfFile(files, path.join(path.dirname(toolchain.GOWORK), "go.work.sum"));
144
+ }
145
+ for (const file of extraFiles) files.add(path.resolve(file));
146
+ for (const directory of inputDirectories) addDirectory(files, directory);
147
+
148
+ const inputs = [...files]
149
+ .sort((a, b) =>
150
+ normalizedPath(a, cwd).localeCompare(normalizedPath(b, cwd)),
151
+ )
152
+ .map((file) => ({
153
+ path: normalizedPath(file, cwd),
154
+ sha256: fileDigest(file),
155
+ }));
156
+ const payload = {
157
+ schema: 1,
158
+ buildArguments,
159
+ environment,
160
+ toolchain,
161
+ inputs,
162
+ };
163
+ return {
164
+ hash: digest(stableJson(payload)),
165
+ payload,
166
+ };
167
+ }
168
+
169
+ function addToolchainBinaryIdentity(toolchain) {
170
+ const goroot = toolchain.GOROOT;
171
+ const goToolDir = toolchain.GOTOOLDIR;
172
+ delete toolchain.GOROOT;
173
+ delete toolchain.GOTOOLDIR;
174
+ if (!goroot) return;
175
+ const executable = path.join(
176
+ goroot,
177
+ "bin",
178
+ process.platform === "win32" ? "go.exe" : "go",
179
+ );
180
+ try {
181
+ toolchain.goBinarySha256 = fileDigest(executable);
182
+ } catch {
183
+ // `go env` supplied a toolchain root, but the compiler binary vanished
184
+ // before its identity could be read. Keep the miss deterministic instead
185
+ // of reusing an artifact built by an unknown compiler.
186
+ toolchain.goBinarySha256 = null;
187
+ }
188
+ if (!goToolDir) return;
189
+ try {
190
+ toolchain.goToolDirectorySha256 = directoryDigest(goToolDir);
191
+ } catch {
192
+ // The compiler driver may still be present when an incomplete toolchain
193
+ // loses its compile/link programs. Treat that transition as a cache miss.
194
+ toolchain.goToolDirectorySha256 = null;
195
+ }
196
+ }
197
+
198
+ function directoryDigest(directory) {
199
+ const files = new Set();
200
+ addDirectory(files, directory);
201
+ return digest(
202
+ stableJson(
203
+ [...files]
204
+ .sort()
205
+ .map((file) => ({
206
+ path: path.relative(directory, file).replaceAll(path.sep, "/"),
207
+ sha256: fileDigest(file),
208
+ })),
209
+ ),
210
+ );
211
+ }
212
+
213
+ function parseJsonStream(source) {
214
+ const records = [];
215
+ let start = 0;
216
+ while (start < source.length) {
217
+ while (/\s/.test(source[start] ?? "")) start += 1;
218
+ if (start >= source.length) break;
219
+ if (source[start] !== "{") {
220
+ throw new Error("go command returned a non-JSON record");
221
+ }
222
+ let depth = 0;
223
+ let quoted = false;
224
+ let escaped = false;
225
+ let end = start;
226
+ for (; end < source.length; end += 1) {
227
+ const character = source[end];
228
+ if (quoted) {
229
+ if (escaped) escaped = false;
230
+ else if (character === "\\") escaped = true;
231
+ else if (character === '"') quoted = false;
232
+ continue;
233
+ }
234
+ if (character === '"') quoted = true;
235
+ else if (character === "{") depth += 1;
236
+ else if (character === "}") {
237
+ depth -= 1;
238
+ if (depth === 0) break;
239
+ }
240
+ }
241
+ if (depth !== 0) throw new Error("go command returned incomplete JSON");
242
+ records.push(JSON.parse(source.slice(start, end + 1)));
243
+ start = end + 1;
244
+ }
245
+ return records;
246
+ }
247
+
248
+ function snapshotPaths(paths) {
249
+ return paths.map((entry) => snapshotPath(entry));
250
+ }
251
+
252
+ function snapshotPath(entry) {
253
+ const target = path.resolve(entry);
254
+ let stat;
255
+ try {
256
+ stat = fs.statSync(target);
257
+ } catch {
258
+ throw new Error(`cache artifact is missing: ${target}`);
259
+ }
260
+ if (stat.isFile())
261
+ return { path: target, type: "file", sha256: fileDigest(target) };
262
+ if (!stat.isDirectory())
263
+ throw new Error(`cache artifact is not a file or directory: ${target}`);
264
+ const files = new Set();
265
+ addDirectory(files, target);
266
+ return {
267
+ path: target,
268
+ type: "directory",
269
+ files: [...files].sort().map((file) => ({
270
+ path: path.relative(target, file),
271
+ sha256: fileDigest(file),
272
+ })),
273
+ };
274
+ }
275
+
276
+ function addDirectory(files, directory) {
277
+ const root = path.resolve(directory);
278
+ let stat;
279
+ try {
280
+ stat = fs.statSync(root);
281
+ } catch {
282
+ throw new Error(`cache input directory is missing: ${root}`);
283
+ }
284
+ if (!stat.isDirectory())
285
+ throw new Error(`cache input is not a directory: ${root}`);
286
+ const stack = [root];
287
+ while (stack.length) {
288
+ const current = stack.pop();
289
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
290
+ const file = path.join(current, entry.name);
291
+ if (entry.isDirectory()) stack.push(file);
292
+ else if (entry.isFile()) files.add(file);
293
+ }
294
+ }
295
+ }
296
+
297
+ function addIfFile(files, file) {
298
+ try {
299
+ if (fs.statSync(file).isFile()) files.add(file);
300
+ } catch {
301
+ // A module can be in the graph without a colocated go.sum. Its go.mod is
302
+ // still tracked; absent optional sums must not make every cache lookup fail.
303
+ }
304
+ }
305
+
306
+ function readRecord(cachePath) {
307
+ try {
308
+ const record = JSON.parse(fs.readFileSync(cachePath, "utf8"));
309
+ return record?.schema === 1 ? record : null;
310
+ } catch {
311
+ return null;
312
+ }
313
+ }
314
+
315
+ function writeAtomically(destination, content) {
316
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
317
+ const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
318
+ fs.writeFileSync(temporary, content);
319
+ fs.rmSync(destination, { force: true });
320
+ fs.renameSync(temporary, destination);
321
+ }
322
+
323
+ function normalizedPath(file, cwd) {
324
+ const relative = path.relative(cwd, file);
325
+ return (relative.startsWith("..") ? path.resolve(file) : relative).replaceAll(
326
+ path.sep,
327
+ "/",
328
+ );
329
+ }
330
+
331
+ function fileDigest(file) {
332
+ return digest(fs.readFileSync(file));
333
+ }
334
+
335
+ function digest(value) {
336
+ return crypto.createHash("sha256").update(value).digest("hex");
337
+ }
338
+
339
+ function stableJson(value) {
340
+ return JSON.stringify(value);
341
+ }
342
+
343
+ module.exports = {
344
+ createGoBuildCache,
345
+ createInputIdentity,
346
+ parseJsonStream,
347
+ };
@@ -9,8 +9,9 @@
9
9
  // build-wasm.cjs emits a rewritten `dist/go.mod` that points at the
10
10
  // vendored `./shim-vendor/shim/*` tree. Pack-prepare swaps the working-tree
11
11
  // `go.mod` for that copy just before pack, then restores the original
12
- // afterwards. The original is stashed in `build/.go.mod.stash` so an
13
- // interrupted pack can be recovered by running `--restore`.
12
+ // afterwards. The original is stashed in
13
+ // `node_modules/.cache/ttsc-wasm/go.mod.stash` so an interrupted pack can be
14
+ // recovered by running `--restore`.
14
15
  //
15
16
  // Usage:
16
17
  // node build/pack-prepare.cjs --stash # prepack: stash + swap
@@ -2,8 +2,8 @@
2
2
 
3
3
  // Command ttsc-wasm is the non-wasm sanity entrypoint for `@ttsc/wasm`.
4
4
  //
5
- // The same binary, compiled with GOOS=js GOARCH=wasm, becomes the browser
6
- // wasm consumed by `bootTtsc`. For native targets we expose a minimal CLI so
5
+ // The package has a separate GOOS=js GOARCH=wasm browser entry consumed by
6
+ // `bootTtsc`. For native targets we expose a minimal CLI so
7
7
  // `go run ./cmd/ttsc-wasm` exercises the same host helpers without needing
8
8
  // the browser MemFS bridge.
9
9
  package main
@@ -55,39 +55,51 @@ func run(args []string) int {
55
55
  }
56
56
  }
57
57
 
58
- // runBuild invokes host.Build and writes the JSON result to stdout.
58
+ // runBuild invokes host.Build and writes the JSON result to stdout. A write
59
+ // failure returns code 3 even when compilation itself succeeded.
59
60
  func runBuild(args []string) int {
60
61
  cwd, tsconfig := parseProject(args)
61
62
  data, code, err := host.Build(cwd, tsconfig)
62
63
  if err != nil {
63
64
  fmt.Fprintf(os.Stderr, "ttsc-wasm build: %v\n", err)
64
- return 2
65
+ return code
66
+ }
67
+ if _, err := os.Stdout.Write(append(data, '\n')); err != nil {
68
+ fmt.Fprintf(os.Stderr, "ttsc-wasm build: write result: %v\n", err)
69
+ return 3
65
70
  }
66
- _, _ = os.Stdout.Write(append(data, '\n'))
67
71
  return code
68
72
  }
69
73
 
70
- // runCheck invokes host.Check and writes the JSON result to stdout.
74
+ // runCheck invokes host.Check and writes the JSON result to stdout. Code 3
75
+ // reports an output failure independently of the compiler diagnostic status.
71
76
  func runCheck(args []string) int {
72
77
  cwd, tsconfig := parseProject(args)
73
78
  data, code, err := host.Check(cwd, tsconfig)
74
79
  if err != nil {
75
80
  fmt.Fprintf(os.Stderr, "ttsc-wasm check: %v\n", err)
76
- return 2
81
+ return code
82
+ }
83
+ if _, err := os.Stdout.Write(append(data, '\n')); err != nil {
84
+ fmt.Fprintf(os.Stderr, "ttsc-wasm check: write result: %v\n", err)
85
+ return 3
77
86
  }
78
- _, _ = os.Stdout.Write(append(data, '\n'))
79
87
  return code
80
88
  }
81
89
 
82
90
  // runTransform invokes host.Transform and writes the JSON result to stdout.
91
+ // Code 3 reports that the source envelope could not be written.
83
92
  func runTransform(args []string) int {
84
93
  cwd, tsconfig := parseProject(args)
85
94
  data, code, err := host.Transform(cwd, tsconfig)
86
95
  if err != nil {
87
96
  fmt.Fprintf(os.Stderr, "ttsc-wasm transform: %v\n", err)
88
- return 2
97
+ return code
98
+ }
99
+ if _, err := os.Stdout.Write(append(data, '\n')); err != nil {
100
+ fmt.Fprintf(os.Stderr, "ttsc-wasm transform: write result: %v\n", err)
101
+ return 3
89
102
  }
90
- _, _ = os.Stdout.Write(append(data, '\n'))
91
103
  return code
92
104
  }
93
105
 
@@ -10,8 +10,8 @@ import (
10
10
  "github.com/samchon/ttsc/packages/wasm/host"
11
11
  )
12
12
 
13
- // main installs the base ttsc API on globalThis and blocks forever.
14
- // Expose never returns; the select{} inside it keeps the wasm runtime alive.
13
+ // main installs the base ttsc API on globalThis. A valid first registration
14
+ // keeps the wasm runtime alive until its Worker is terminated.
15
15
  func main() {
16
16
  host.Expose("ttsc", host.Config{
17
17
  Plugins: nil,