typenative 0.2.0 → 0.3.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.
@@ -5,7 +5,39 @@ on:
5
5
  pull_request:
6
6
 
7
7
  jobs:
8
+ # Every probe through the Node CLI, on each OS a generated binary can target
8
9
  conformance:
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ os: [ubuntu-latest, windows-latest, macos-latest]
14
+ runs-on: ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 24
21
+ cache: npm
22
+
23
+ - uses: actions/setup-go@v5
24
+ with:
25
+ go-version: '1.26'
26
+ cache-dependency-path: tsparser/go.sum
27
+
28
+ - run: npm ci
29
+
30
+ - run: npm run build
31
+
32
+ - run: npm ci
33
+ working-directory: conformance
34
+
35
+ - run: npm run check:go-types
36
+
37
+ - run: npm run conformance:parallel
38
+
39
+ # The self-hosted compiler: same probes, and it must reproduce its own Go
40
+ native:
9
41
  runs-on: ubuntu-latest
10
42
  steps:
11
43
  - uses: actions/checkout@v4
@@ -18,6 +50,7 @@ jobs:
18
50
  - uses: actions/setup-go@v5
19
51
  with:
20
52
  go-version: '1.26'
53
+ cache-dependency-path: tsparser/go.sum
21
54
 
22
55
  - run: npm ci
23
56
 
@@ -26,4 +59,8 @@ jobs:
26
59
  - run: npm ci
27
60
  working-directory: conformance
28
61
 
29
- - run: npm run conformance
62
+ - run: npm run build:native
63
+
64
+ - run: npm run bootstrap:check
65
+
66
+ - run: node scripts/conformance-parallel.mjs --native
@@ -29,9 +29,11 @@ jobs:
29
29
  with:
30
30
  go-version: '1.26'
31
31
  - run: npm ci
32
+ # Test what gets published: bin/ rebuilt from src/, not the committed copy
33
+ - run: npm run build
32
34
  - run: npm ci
33
35
  working-directory: conformance
34
- - run: npm run conformance
36
+ - run: npm run conformance:parallel
35
37
 
36
38
  release:
37
39
  needs: test
@@ -78,6 +80,9 @@ jobs:
78
80
 
79
81
  - run: npm ci
80
82
 
83
+ # Publish bin/ built from this commit (prebuild also stamps the tag's version)
84
+ - run: npm run build
85
+
81
86
  # Authenticates via OIDC (id-token: write) — no NODE_AUTH_TOKEN needed.
82
87
  # Provenance is included automatically; the flag is kept for explicitness.
83
88
  - run: npm publish --provenance --access public
package/CHANGELOG.md CHANGED
@@ -7,6 +7,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.1] - 2026-10-18
11
+
12
+ ### Fixed
13
+
14
+ - `node:tls` on macOS: an untrusted certificate chain reports Node's codes
15
+ (`DEPTH_ZERO_SELF_SIGNED_CERT`, `SELF_SIGNED_CERT_IN_CHAIN`,
16
+ `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`) like on other platforms; the platform verifier returns
17
+ a plain error, not `UnknownAuthorityError`
18
+ - `go build` runs with `-trimpath`, so vendored packages (the ~5 MB sqlite driver) are cached
19
+ once instead of recompiled for every project directory, and builds are reproducible
20
+ - `conformance-parallel.mjs` builds one sqlite probe first so a cold cache doesn't time out
21
+ every sqlite probe at once on small runners; the `node:sqlite` `location()` probe compares
22
+ canonical paths (symlinks, Windows 8.3 short names)
23
+
24
+ ## [0.3.0] - 2026-10-18
25
+
26
+ Errors now point at your TypeScript. Programs are type-checked before they're transpiled, code
27
+ TypeNative can't compile stops the build with its position, and Go build errors and panics
28
+ report `.ts` lines. CI runs the suite on Linux, Windows and macOS, and all 398 probes pass on
29
+ Windows (four failed there before), through both the Node CLI and the self-hosted compiler.
30
+
31
+ ### Added
32
+
33
+ - **Type-checking**: the CLI type-checks the entry file and its imports before transpiling,
34
+ using the nearest `tsconfig.json` (or `lib: []` plus the bundled `typenative` declarations),
35
+ and stops on errors (`main.ts:4:7: error TS2322: …`); `--no-typecheck` skips it. The
36
+ self-hosted compiler runs the same check on typescript-go (`tsparser --check`)
37
+ - **Line directives**: generated Go carries `/*line file.ts:line:col*/` directives, so
38
+ `go build` errors and panic stack traces report TypeScript files and lines; runtime helpers
39
+ keep their own `dist/code.go` positions. Both parsers emit `line` / `column` on AST nodes
40
+ - `TranspileResult.diagnostics`, and `fileName` / `goFile` / `goos` transpile options
41
+ - `npm run bootstrap:check`: the self-hosted compiler must emit byte-identical Go for
42
+ `src/main.ts` to the Node CLI's; `conformance-parallel.mjs --native`
43
+ - CI on Ubuntu, Windows and macOS, `check:go-types` in CI, and a self-hosted compiler job
44
+ (bootstrap fixpoint + the suite through it)
45
+
46
+ ### Changed
47
+
48
+ - Unsupported syntax and unsupported `node:` modules are errors with a position
49
+ (`main.ts:2:1: error: Unsupported syntax: WithStatement`) instead of warnings followed by
50
+ broken Go; `;` and `debugger` statements compile to nothing
51
+ - Generated Go is deterministic: temp and renamed identifiers come from a generator seeded per
52
+ transpilation instead of `Math.random`, so the same source always gives the same Go
53
+ - Code typed into `--script`'s prompt is saved as `dist/script.ts` (type-checked, and named in
54
+ errors)
55
+ - Scaffolded `tsconfig.json` sets `skipLibCheck`
56
+ - `node:sqlite` `function()` / `aggregate()` callbacks are typed `(...args: any[])`: arguments
57
+ convert to the types the callback declares
58
+ - The release workflow builds `bin/` before testing and publishing; `conformance-parallel.mjs`
59
+ sizes its worker pool to the machine
60
+ - Probes that depend on the OS (`lib-node-dns-resolver`, `lib-node-util-parse-args`) hold on
61
+ Windows, and the conformance probes type-check (`conformance/tsconfig.json` sets
62
+ `skipLibCheck`)
63
+
64
+ ### Fixed
65
+
66
+ - Windows: `os.totalmem()` / `os.freemem()` / `os.uptime()` read the OS (they returned the
67
+ process's own memory, `0` and `0`); `performance.now()` keeps increasing between calls, so
68
+ marks taken in a row keep their order (Go's Windows clock repeats readings)
69
+
10
70
  ## [0.2.0] - 2026-10-18
11
71
 
12
72
  The Go standard-library bridge (`go:*` imports) goes from 73 to 154 probed packages, with 16
package/CONTRIBUTION.md CHANGED
@@ -44,7 +44,7 @@ Probe conventions:
44
44
 
45
45
  - Self-contained — no imports except the module under test, no external files.
46
46
  - Exercise the feature with `console.log` **and** verify with `assert(condition, 'message')`.
47
- - A passing probe transpiles, compiles with `go build`, runs, and exits `0`. Anything else is a failure.
47
+ - A passing probe type-checks (against `conformance/tsconfig.json`), transpiles, compiles with `go build`, runs, and exits `0`. Anything else is a failure.
48
48
 
49
49
  ```ts
50
50
  // conformance/lib-map.ts
@@ -73,7 +73,8 @@ No signature → user code won't type-check → the feature doesn't exist.
73
73
  ### 3. Implement in `src/transpiler.ts`
74
74
 
75
75
  - Match on `node.kind === 'Xxx'` strings (never the `typescript` enum) so the same code consumes Node- and Go-produced ASTs.
76
- - Emit a warning, not a crash, for anything still unsupported: `console.warn('[TypeNative] Unsupported syntax: …')`.
76
+ - Report anything still unsupported with `reportError(node, 'Unsupported …')`, not a crash or a guess: it becomes a `file.ts:line:col: error:` diagnostic and the CLI stops before `go build`.
77
+ - New statement-like output goes through `withLineMarker(node, code)` (statement lists already do), so Go errors and stack traces point at the `.ts` line.
77
78
  - Keep the transpiler free of Node-only dependencies (see below).
78
79
 
79
80
  ### 4. Document in `conformance.md`
@@ -85,7 +86,7 @@ Add or flip the matrix row and link your probe. Keep ✅/❌ honest — known ga
85
86
  This is the one rule that breaks the build if ignored: **`src/index.ts`, `src/transpiler.ts`, `src/main.ts`, `src/prompt.ts`, and `src/parse-native.ts` must transpile with TypeNative itself.** That means:
86
87
 
87
88
  - ❌ No `inquirer`, no async Node-only APIs, no unmapped modules in those files.
88
- - ✅ Node-only code lives in `src/cli.ts` (prompts) and `src/parse-node.ts` (`typescript` package) and is injected at the boundary — e.g. `run({ ..., parse: parseAstJson })`.
89
+ - ✅ Node-only code lives in `src/cli.ts` (prompts), `src/parse-node.ts` and `src/typecheck-node.ts` (`typescript` package) and is injected at the boundary — e.g. `run({ ..., parse: parseAstJson, typeCheck })`. The self-hosted binary injects `tsparser`-backed versions (`src/parse-native.ts`).
89
90
  - ✅ Prompts in transpilable code go through `src/prompt.ts` (`node:readline` → Go helper).
90
91
 
91
92
  If you need a new capability in transpilable code, add the mapping (transpiler + `types/` shim) first, then use it.
@@ -109,10 +110,11 @@ If you touched anything transpilable, also verify self-hosting:
109
110
 
110
111
  ```sh
111
112
  npm run build:native # builds native/tsparser + native/typenative
112
- node scripts/conformance.mjs --native # full suite through your new binary
113
+ npm run bootstrap:check # native/typenative must emit the same Go for src/main.ts
114
+ node scripts/conformance-parallel.mjs --native # full suite through your new binary
113
115
  ```
114
116
 
115
- CI ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) runs `npm ci` → `npm run build` → `npm run conformance` on Ubuntu (Node 24 + Go). Match it locally before pushing.
117
+ CI ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) runs `npm run build`, `npm run check:go-types` and `npm run conformance:parallel` on Ubuntu, Windows and macOS (Node 24 + Go 1.26), plus a Linux job that builds the native compiler, runs `npm run bootstrap:check` and the suite through it. Probes that depend on the OS should hold on all three (branch on `process.platform` where Node itself differs).
116
118
 
117
119
  ## Pull requests
118
120
 
package/README.md CHANGED
@@ -122,8 +122,11 @@ Scaffolded `tsconfig.json` targets the TypeNative type surface:
122
122
  | `--output <path>` | build | Where to copy the compiled binary. If omitted with `--script`, the temp binary in `dist/` is executed and kept there. |
123
123
  | `--script` | run | Build to `dist/native(.exe)` and execute immediately with inherited stdio. Combine with `--source`, or omit `--source` to type code into the prompt. |
124
124
  | `--new` | scaffold | Create a new project directory (`main.ts`, `package.json`, `tsconfig.json`, `README.md`, `.gitignore`). |
125
+ | `--no-typecheck` | build / script | Skip the type-check that runs before transpiling. |
125
126
 
126
- Exit codes from `--script` are the exit code of your program. A non-zero `go build` prints `go build failed:` plus the compiler output and exits `1`.
127
+ Exit codes from `--script` are the exit code of your program. Before anything is built, the entry file and its imports are type-checked with the nearest `tsconfig.json` (or TypeNative's defaults: `lib: []` plus the `typenative` declarations); type errors print as `main.ts:4:7: error TS2322: …` and exit `1`. TypeScript TypeNative can't compile yet is an error too (`main.ts:2:1: error: Unsupported syntax: WithStatement`), not a guess. A non-zero `go build` prints `go build failed:` plus the compiler output and exits `1`.
128
+
129
+ Errors point at your TypeScript, not the generated Go: `dist/code.go` carries Go line directives, so `go build` errors and panic stack traces report `.ts` files and lines (`main.ts:12`).
127
130
 
128
131
  ## Writing TypeNative-compatible TypeScript
129
132
 
@@ -235,18 +238,19 @@ console.log(`count=${c.count} seen=${seen.get('a')}`);
235
238
  ## How it works
236
239
 
237
240
  ```text
238
- main.ts --(parse)--> normalized JSON AST --(transpileToNative)--> dist/code.go --(go build)--> dist/native(.exe) --run/copy--> your binary
241
+ main.ts --(type-check)--(parse)--> normalized JSON AST --(transpileToNative)--> dist/code.go --(go build)--> dist/native(.exe) --run/copy--> your binary
239
242
  ```
240
243
 
241
- - **Parse:** Node CLI uses the `typescript` npm package (`src/parse-node.ts`); the self-hosted binary spawns the Go `tsparser` tool (`src/parse-native.ts`). Both emit the same `{ kind: "Xxx", ... }` JSON shape so one transpiler consumes either.
242
- - **Transpile:** `transpileToNative(code, { parse, readFile })` in `src/transpiler.ts` lowers the AST to Go, resolving relative and npm imports into additional Go files (`TranspileResult { main, files }`).
244
+ - **Type-check:** Node CLI builds a program with the `typescript` package (`src/typecheck-node.ts`); the self-hosted binary runs `tsparser --check`, the same check on typescript-go (`tsparser/check.go`).
245
+ - **Parse:** Node CLI uses the `typescript` npm package (`src/parse-node.ts`); the self-hosted binary spawns the Go `tsparser` tool (`src/parse-native.ts`). Both emit the same `{ kind: "Xxx", line, column, ... }` JSON shape so one transpiler consumes either.
246
+ - **Transpile:** `transpileToNative(code, { parse, readFile })` in `src/transpiler.ts` lowers the AST to Go, resolving relative and npm imports into additional Go files (`TranspileResult { main, files, diagnostics }`). Generated statements carry `/*line file.ts:line:col*/` directives; `diagnostics` lists source the transpiler can't compile.
243
247
  - **Build:** `run()` in `src/index.ts` writes `dist/code.go` (+ one Go file per module), cleans stale `dist/*.go`, shells out to `go build -o dist/native(.exe)`, then either executes it (`--script`, stdio inherited) or copies it to `--output`. Third-party Go modules a program needs (`node:sqlite`'s driver, shipped in `govendor/`; `golang.org/x/text`, copied from GOROOT) are copied under `dist/tnvendor` and wired in with `replace` directives in `dist/go.mod`, so builds stay offline.
244
248
 
245
249
  ## Testing and conformance
246
250
 
247
- `npm test` is `npm run conformance`: every `conformance/*.ts` probe is transpiled, built with `go`, executed, and must exit `0`. Results print per category (`syntax` / `semantics` / `lib`) plus a total pass rate; any failure exits non-zero and lists the first diagnostic line (`*.go:line`, `panic:`, `Unsupported syntax`, `Error`).
251
+ `npm test` is `npm run conformance`: every `conformance/*.ts` probe is type-checked, transpiled, built with `go`, executed, and must exit `0` (`npm run conformance:parallel` runs them concurrently). Results print per category (`syntax` / `semantics` / `lib`) plus a total pass rate; any failure exits non-zero and lists the first diagnostic line (`*.go:line`, `panic:`, `Unsupported syntax`, `Error`).
248
252
 
249
- CI (`.github/workflows/ci.yml`) runs `npm ci`, `npm run build`, and `npm run conformance` on Ubuntu with Node 24 and Go. Releases are cut from `TypeNative_*` tags via trusted publishing.
253
+ CI (`.github/workflows/ci.yml`) runs `npm run build`, `npm run check:go-types` and the conformance suite on Ubuntu, Windows and macOS with Node 24 and Go 1.26, and a Linux job that builds the self-hosted compiler, checks the bootstrap fixpoint (`npm run bootstrap:check`) and runs the suite through it. Releases are cut from `TypeNative_*` tags via trusted publishing, from a fresh `npm run build`.
250
254
 
251
255
  When adding a feature: add a probe, link it from [conformance.md](conformance.md), and keep the ✅/❌ matrix honest — gaps are documented, not hidden.
252
256
 
@@ -260,7 +264,7 @@ npm run build:native # builds native/tsparser + native/typenative side by side
260
264
  native/typenative --source app.ts --script
261
265
  ```
262
266
 
263
- `TYPENATIVE_TSPARSER` overrides parser discovery if you move the binaries apart. Verify parity with `node scripts/conformance.mjs --native`.
267
+ `TYPENATIVE_TSPARSER` overrides parser discovery if you move the binaries apart. Verify parity with `node scripts/conformance.mjs --native`, and that the binary reproduces its own Go with `npm run bootstrap:check`.
264
268
 
265
269
  ## License
266
270
 
package/bin/cli.js CHANGED
@@ -5,9 +5,11 @@ import inquirer from 'inquirer';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { run, createProject } from './index.js';
7
7
  import { parseAstJson } from './parse-node.js';
8
+ import { typeCheckNode } from './typecheck-node.js';
8
9
  (async function main() {
9
10
  const scriptMode = process.argv.findIndex((a) => a === '--script') > -1;
10
11
  const newCommand = process.argv.findIndex((a) => a === '--new') > -1;
12
+ const noTypeCheck = process.argv.includes('--no-typecheck');
11
13
  const sourceIndex = process.argv.findIndex((a) => a === '--source');
12
14
  const source = sourceIndex > -1 ? process.argv[sourceIndex + 1] : null;
13
15
  const outputIndex = process.argv.findIndex((a) => a === '--output');
@@ -53,12 +55,14 @@ import { parseAstJson } from './parse-node.js';
53
55
  return;
54
56
  }
55
57
  const sourcePath = answers.tsCode ? null : (source ?? answers.path ?? null);
58
+ const typesDir = fileURLToPath(new URL('../types', import.meta.url));
56
59
  run({
57
60
  source: sourcePath,
58
61
  output: output ?? answers.output ?? null,
59
62
  scriptMode,
60
63
  tsCode: answers.tsCode ?? null,
61
64
  parse: parseAstJson,
65
+ typeCheck: noTypeCheck ? null : (fileName) => typeCheckNode(fileName, typesDir),
62
66
  vendorDir: fileURLToPath(new URL('../govendor', import.meta.url))
63
67
  });
64
68
  })();
package/bin/index.js CHANGED
@@ -11,6 +11,22 @@ import { platform } from 'node:os';
11
11
  import * as childProcess from 'node:child_process';
12
12
  import { transpileToNative } from './transpiler.js';
13
13
  import { VERSION } from './version.js';
14
+ // A source path as diagnostics and Go line directives show it: relative to
15
+ // the working directory when inside it, else absolute; forward slashes
16
+ function displayPath(file) {
17
+ const absolute = path.resolve(file);
18
+ const relative = path.relative(process.cwd(), absolute);
19
+ const inside = relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
20
+ return (inside ? relative : absolute).split('\\').join('/');
21
+ }
22
+ // file:line:column: <label>: message (the position Go's own errors use)
23
+ function printDiagnostic(file, line, column, label, message) {
24
+ const position = line > 0 ? `${displayPath(file)}:${line}:${column}` : displayPath(file);
25
+ console.error(`${file ? position + ': ' : ''}${label}: ${message.split('\n').join('\n ')}`);
26
+ }
27
+ function plural(count, noun) {
28
+ return `${count} ${noun}${count === 1 ? '' : 's'}`;
29
+ }
14
30
  // golang.org/x/text's transform and unicode/norm packages (String.normalize),
15
31
  // copied from GOROOT/src/vendor
16
32
  function vendorXText() {
@@ -104,10 +120,30 @@ export function createProject(projectName, installDependencies) {
104
120
  export function run(opts) {
105
121
  const sourcePath = opts.tsCode ? null : opts.source;
106
122
  const tsCode = opts.tsCode ? opts.tsCode : fs.readFileSync(sourcePath, 'utf-8');
123
+ fs.mkdirSync('dist', { recursive: true });
124
+ // Code typed into the prompt is saved as dist/script.ts, so it is
125
+ // type-checked and errors point at a real file
126
+ const entryPath = sourcePath ?? 'dist/script.ts';
127
+ if (!sourcePath)
128
+ fs.writeFileSync(entryPath, tsCode);
129
+ if (opts.typeCheck) {
130
+ const typeErrors = opts.typeCheck(entryPath);
131
+ if (typeErrors.length > 0) {
132
+ for (const d of typeErrors)
133
+ printDiagnostic(d.file, d.line, d.column, `error TS${d.code}`, d.message);
134
+ console.error(`\n${plural(typeErrors.length, 'type error')} (--no-typecheck skips the check)`);
135
+ process.exit(1);
136
+ }
137
+ }
107
138
  const sourceDir = sourcePath ? path.dirname(path.resolve(sourcePath)) : null;
139
+ // Node's platform names are GOOS names, except win32
140
+ const goos = platform() === 'win32' ? 'windows' : platform();
108
141
  const transpileOptions = {
109
142
  parse: opts.parse,
110
- tsx: !!sourcePath && sourcePath.endsWith('.tsx')
143
+ tsx: !!sourcePath && sourcePath.endsWith('.tsx'),
144
+ fileName: displayPath(entryPath),
145
+ goFile: 'dist/code.go',
146
+ goos: goos
111
147
  };
112
148
  if (sourceDir) {
113
149
  transpileOptions.readFile = (specifier, fromDir) => {
@@ -127,7 +163,8 @@ export function run(opts) {
127
163
  return {
128
164
  content: fs.readFileSync(fullPath, 'utf-8'),
129
165
  dir: path.dirname(fullPath),
130
- tsx: fullPath.endsWith('.tsx')
166
+ tsx: fullPath.endsWith('.tsx'),
167
+ path: displayPath(fullPath)
131
168
  };
132
169
  }
133
170
  catch {
@@ -150,13 +187,18 @@ export function run(opts) {
150
187
  const typed = tryInjectDtsTypes(content, specifier, sourceDir);
151
188
  if (typed)
152
189
  content = typed;
153
- return { content, dir };
190
+ return { content, dir, path: displayPath(resolved.path) };
154
191
  };
155
192
  }
156
193
  const transpileResult = transpileToNative(tsCode, transpileOptions);
194
+ if (transpileResult.diagnostics.length > 0) {
195
+ for (const d of transpileResult.diagnostics)
196
+ printDiagnostic(d.file, d.line, d.column, 'error', d.message);
197
+ console.error(`\n${plural(transpileResult.diagnostics.length, 'error')}: TypeNative can't compile this yet`);
198
+ process.exit(1);
199
+ }
157
200
  const exeName = platform() === 'win32' ? 'native.exe' : 'native';
158
201
  const exePath = `dist/${exeName}`;
159
- fs.mkdirSync('dist', { recursive: true });
160
202
  // Clean up stale Go files from previous runs before writing new ones
161
203
  for (const existing of fs.readdirSync('dist')) {
162
204
  if (existing.endsWith('.go') || existing === 'go.mod' || existing === 'go.sum')
@@ -191,12 +233,16 @@ export function run(opts) {
191
233
  'build',
192
234
  '-C',
193
235
  'dist',
236
+ // Path-independent cache keys: the vendored packages (sqlite is ~5 MB
237
+ // of Go) compile once, not again for every project directory
238
+ '-trimpath',
194
239
  '-o',
195
240
  exeName,
196
241
  ...goFiles.map((f) => f.slice('dist/'.length))
197
242
  ])
198
243
  : runCommand('go', ['build', '-o', exePath, ...goFiles]);
199
244
  if (buildResult.status !== 0) {
245
+ // Positions in .ts files come from line directives in the generated Go
200
246
  console.error('go build failed:');
201
247
  if (buildResult.stderr)
202
248
  console.error(buildResult.stderr.trim());
@@ -282,7 +328,7 @@ function resolveNpmPackage(fromDir, packageName) {
282
328
  for (const candidate of candidates) {
283
329
  const fullPath = path.resolve(pkgDir, candidate);
284
330
  try {
285
- return { content: fs.readFileSync(fullPath, 'utf-8'), dir: path.dirname(fullPath) };
331
+ return { content: fs.readFileSync(fullPath, 'utf-8'), dir: path.dirname(fullPath), path: fullPath };
286
332
  }
287
333
  catch {
288
334
  /* try next candidate */
@@ -324,7 +370,8 @@ function getTsConfig() {
324
370
  rootDir: '.',
325
371
  strict: true,
326
372
  noImplicitAny: true,
327
- allowSyntheticDefaultImports: true
373
+ allowSyntheticDefaultImports: true,
374
+ skipLibCheck: true
328
375
  }
329
376
  };
330
377
  return JSON.stringify(tsConfig, null, 2);
package/bin/main.js CHANGED
@@ -4,10 +4,11 @@
4
4
  import path from 'node:path';
5
5
  import { run, createProject } from './index.js';
6
6
  import { ask, askMultiline, confirm } from './prompt.js';
7
- import { parseAstJsonNative } from './parse-native.js';
7
+ import { parseAstJsonNative, typeCheckNative } from './parse-native.js';
8
8
  (function main() {
9
9
  const scriptMode = process.argv.findIndex((a) => a === '--script') > -1;
10
10
  const newCommand = process.argv.findIndex((a) => a === '--new') > -1;
11
+ const noTypeCheck = process.argv.includes('--no-typecheck');
11
12
  const sourceIndex = process.argv.findIndex((a) => a === '--source');
12
13
  let source = sourceIndex > -1 ? process.argv[sourceIndex + 1] : null;
13
14
  const outputIndex = process.argv.findIndex((a) => a === '--output');
@@ -28,7 +29,10 @@ import { parseAstJsonNative } from './parse-native.js';
28
29
  if (!newCommand && scriptMode && !source) {
29
30
  tsCode = askMultiline('Write your typescript code here:');
30
31
  }
31
- // The native compiler lives in native/, next to the package's govendor/
32
- const vendorDir = path.join(path.dirname(path.resolve(process.argv[0])), '..', 'govendor');
33
- run({ source, output, scriptMode, tsCode, parse: parseAstJsonNative, vendorDir });
32
+ // The native compiler lives in native/, next to the package's govendor/ and types/
33
+ const packageDir = path.join(path.dirname(path.resolve(process.argv[0])), '..');
34
+ const vendorDir = path.join(packageDir, 'govendor');
35
+ const typesDir = path.join(packageDir, 'types');
36
+ const typeCheck = noTypeCheck ? null : (fileName) => typeCheckNative(fileName, typesDir);
37
+ run({ source, output, scriptMode, tsCode, parse: parseAstJsonNative, typeCheck, vendorDir });
34
38
  })();
@@ -163,6 +163,9 @@ func tnSysctl(name string) string {
163
163
  }
164
164
 
165
165
  func TnOsTotalmem() float64 {
166
+ if total, _, ok := tnPlatformMemory(); ok {
167
+ return total
168
+ }
166
169
  if n := tnMemInfo("MemTotal"); n >= 0 {
167
170
  return n
168
171
  }
@@ -180,6 +183,9 @@ func TnOsTotalmem() float64 {
180
183
  }
181
184
 
182
185
  func TnOsFreemem() float64 {
186
+ if _, free, ok := tnPlatformMemory(); ok {
187
+ return free
188
+ }
183
189
  if n := tnMemInfo("MemAvailable"); n >= 0 {
184
190
  return n
185
191
  }
@@ -193,6 +199,9 @@ func TnOsFreemem() float64 {
193
199
 
194
200
  // Seconds since boot
195
201
  func TnOsUptime() float64 {
202
+ if n, ok := tnPlatformUptime(); ok {
203
+ return n
204
+ }
196
205
  if data, err := os.ReadFile("/proc/uptime"); err == nil {
197
206
  if f := strings.Fields(string(data)); len(f) > 0 {
198
207
  n, _ := strconv.ParseFloat(f[0], 64)
@@ -572,9 +581,53 @@ func TnEventLoopUtilization(previous ...*TnLoopUtilization) *TnLoopUtilization {
572
581
  }
573
582
  return u
574
583
  }`;
584
+ // Memory and uptime from the OS API where there's no /proc or sysctl
585
+ // (Windows); the helper is picked per target OS (nodeSystemOS@os)
586
+ const windowsSystemHelper = `// GlobalMemoryStatusEx's MEMORYSTATUSEX
587
+ type tnMemoryStatusEx struct {
588
+ length uint32
589
+ memoryLoad uint32
590
+ totalPhys uint64
591
+ availPhys uint64
592
+ totalPageFile uint64
593
+ availPageFile uint64
594
+ totalVirtual uint64
595
+ availVirtual uint64
596
+ availExtendedVirtual uint64
597
+ }
598
+
599
+ var tnKernel32 = syscall.NewLazyDLL("kernel32.dll")
600
+ var tnGlobalMemoryStatusEx = tnKernel32.NewProc("GlobalMemoryStatusEx")
601
+ var tnGetTickCount64 = tnKernel32.NewProc("GetTickCount64")
602
+
603
+ // Total and available physical memory in bytes
604
+ func tnPlatformMemory() (float64, float64, bool) {
605
+ status := tnMemoryStatusEx{}
606
+ status.length = uint32(unsafe.Sizeof(status))
607
+ if ok, _, _ := tnGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&status))); ok == 0 {
608
+ return 0, 0, false
609
+ }
610
+ return float64(status.totalPhys), float64(status.availPhys), true
611
+ }
612
+
613
+ // Seconds since boot
614
+ func tnPlatformUptime() (float64, bool) {
615
+ ms, _, _ := tnGetTickCount64.Call()
616
+ return float64(ms) / 1000, true
617
+ }`;
618
+ const otherSystemHelper = `// /proc or sysctl answer instead (TnOsTotalmem / TnOsFreemem / TnOsUptime)
619
+ func tnPlatformMemory() (float64, float64, bool) {
620
+ return 0, 0, false
621
+ }
622
+
623
+ func tnPlatformUptime() (float64, bool) {
624
+ return 0, false
625
+ }`;
575
626
  export function registerSystemExtra(r) {
576
627
  defineHelper(r, 'nodeUncaught', [], ['nodeEvents', 'nodeProcess', 'eventLoop', 'promise'], uncaughtHelper);
577
- defineHelper(r, 'nodeSystem', ['math', 'os', 'os/exec', 'runtime', 'runtime/metrics', 'strconv', 'strings', 'time'], [], systemHelper);
628
+ defineHelper(r, 'nodeSystemOS@windows', ['syscall', 'unsafe'], [], windowsSystemHelper);
629
+ defineHelper(r, 'nodeSystemOS@other', [], [], otherSystemHelper);
630
+ defineHelper(r, 'nodeSystem', ['math', 'os', 'os/exec', 'runtime', 'runtime/metrics', 'strconv', 'strings', 'time'], ['nodeSystemOS@os'], systemHelper);
578
631
  defineHelper(r, 'nodePerfExtra', ['math', 'math/big', 'reflect', 'sort', 'time'], ['nodePerf', 'nodeSystem', 'eventLoop', 'nodeInvoke', 'dynamic', 'orderedMap'], perfExtraHelper);
579
632
  // process events handled by the loop
580
633
  for (const name of ['process.on', 'process.once', 'process.addListener']) {
@@ -18,8 +18,21 @@ var tnPerfMu sync.Mutex
18
18
  // Set by PerformanceObserver: new entries reach the observers
19
19
  var tnPerfHook func(e *TnPerformanceEntry)
20
20
 
21
+ // Go's monotonic clock on Windows advances only at the timer interrupt, so
22
+ // back-to-back readings repeat; each reading is kept later than the last
23
+ // (by 1ns) so marks taken in a row stay in call order, as with Node's clock
24
+ var tnPerfLast float64
25
+ var tnPerfClockMu sync.Mutex
26
+
21
27
  func TnPerfNow() float64 {
22
- return float64(time.Since(tnPerfStart).Nanoseconds()) / 1e6
28
+ now := float64(time.Since(tnPerfStart).Nanoseconds()) / 1e6
29
+ tnPerfClockMu.Lock()
30
+ defer tnPerfClockMu.Unlock()
31
+ if now <= tnPerfLast {
32
+ now = tnPerfLast + 1e-6
33
+ }
34
+ tnPerfLast = now
35
+ return now
23
36
  }
24
37
 
25
38
  func TnPerfTimeOrigin() float64 {
package/bin/node/tls.js CHANGED
@@ -167,7 +167,9 @@ func tnVerifyPeer(certs []*x509.Certificate, roots *x509.CertPool, host string)
167
167
  return TnNodeError("Error", "CERT_NOT_YET_VALID", "certificate is not yet valid")
168
168
  }
169
169
  return TnNodeError("Error", "CERT_HAS_EXPIRED", "certificate has expired")
170
- case errors.As(err, &unknown):
170
+ // macOS's platform verifier (nil roots) reports an untrusted chain as a
171
+ // plain error, not UnknownAuthorityError
172
+ case errors.As(err, &unknown) || roots == nil:
171
173
  if len(certs) == 1 && bytes.Equal(leaf.RawIssuer, leaf.RawSubject) {
172
174
  return TnNodeError("Error", "DEPTH_ZERO_SELF_SIGNED_CERT", "self-signed certificate")
173
175
  }
@@ -13,6 +13,16 @@ export function parseAstJsonNative(code, tsx) {
13
13
  }
14
14
  return JSON.parse(result.stdout);
15
15
  }
16
+ // Type-checks the program rooted at fileName with typescript-go
17
+ // (tsparser --check); typesDir holds the bundled declarations
18
+ export function typeCheckNative(fileName, typesDir) {
19
+ const exe = findTsparser();
20
+ const result = childProcess.spawnSync(exe, ['--check', fileName, typesDir], { encoding: 'utf-8' });
21
+ if (result.status !== 0) {
22
+ throw new Error('tsparser --check failed: ' + result.stderr);
23
+ }
24
+ return JSON.parse(result.stdout);
25
+ }
16
26
  // Locates the tsparser binary: TYPENATIVE_TSPARSER env var, next to this
17
27
  // executable, or in a sibling tsparser/ directory (dev checkout layout).
18
28
  function findTsparser() {
package/bin/parse-node.js CHANGED
@@ -83,8 +83,17 @@ function declarationKind(flags) {
83
83
  function isNode(v) {
84
84
  return typeof v === 'object' && v !== null && typeof v.kind === 'number' && typeof v.pos === 'number';
85
85
  }
86
+ // The file being converted, for node positions
87
+ let currentSourceFile;
86
88
  function convertNode(node) {
87
89
  const obj = { kind: KIND_NAMES[node.kind] ?? String(node.kind) };
90
+ // 1-based line and column of the node's first token (columns count UTF-16
91
+ // units, as in the Go producer); the transpiler maps Go positions back to them
92
+ if (node.kind !== ts.SyntaxKind.SourceFile && node.pos >= 0) {
93
+ const start = currentSourceFile.getLineAndCharacterOfPosition(node.getStart(currentSourceFile));
94
+ obj.line = start.line + 1;
95
+ obj.column = start.character + 1;
96
+ }
88
97
  if (TEXT_KINDS.has(node.kind)) {
89
98
  obj.text = node.text ?? node.escapedText;
90
99
  }
@@ -122,5 +131,6 @@ export function parseAstJson(code, tsx) {
122
131
  const sourceFile = tsx
123
132
  ? ts.createSourceFile('main.tsx', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TSX)
124
133
  : ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
134
+ currentSourceFile = sourceFile;
125
135
  return convertNode(sourceFile);
126
136
  }