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.
- package/.github/workflows/ci.yml +38 -1
- package/.github/workflows/npm-publish-github-packages.yml +6 -1
- package/CHANGELOG.md +60 -0
- package/CONTRIBUTION.md +7 -5
- package/README.md +11 -7
- package/bin/cli.js +4 -0
- package/bin/index.js +53 -6
- package/bin/main.js +8 -4
- package/bin/node/system_extra.js +54 -1
- package/bin/node/timers.js +14 -1
- package/bin/node/tls.js +3 -1
- package/bin/parse-native.js +10 -0
- package/bin/parse-node.js +10 -0
- package/bin/transpiler.js +243 -51
- package/bin/typecheck-node.js +78 -0
- package/bin/version.js +1 -1
- package/conformance.md +2 -1
- package/package.json +2 -1
- package/tsparser/check.go +157 -0
- package/tsparser/main.go +30 -1
- package/types/typenative-npm.d.ts +7 -4
package/.github/workflows/ci.yml
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
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/
|
|
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
|
-
|
|
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
|
|
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
|
-
- **
|
|
242
|
-
- **
|
|
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
|
|
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
|
|
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
|
|
33
|
-
|
|
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
|
})();
|
package/bin/node/system_extra.js
CHANGED
|
@@ -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, '
|
|
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']) {
|
package/bin/node/timers.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/bin/parse-native.js
CHANGED
|
@@ -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
|
}
|