typenative 0.0.20 → 0.1.0
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/npm-publish-github-packages.yml +14 -6
- package/CHANGELOG.md +70 -0
- package/README.md +41 -4
- package/TODO.md +38 -0
- package/bin/cli.js +62 -0
- package/bin/index.js +105 -134
- package/bin/main.js +31 -0
- package/bin/parse-native.js +37 -0
- package/bin/parse-node.js +110 -0
- package/bin/prompt.js +29 -0
- package/bin/transpiler.js +4277 -629
- package/package.json +56 -37
- package/tsparser/go.mod +14 -0
- package/tsparser/go.sum +22 -0
- package/tsparser/main.go +279 -0
- package/tsparser/tsparser.exe +4 -0
- package/types/typenative-npm.d.ts +44 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# Triggered by tags matching "TypeNative_{version}" (e.g. TypeNative_0.0.18)
|
|
2
2
|
# Runs tests, creates a GitHub release, and publishes to npm.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Uses npm Trusted Publishing (OIDC) — no NPM_TOKEN secret needed.
|
|
5
|
+
# One-time setup on npmjs.com:
|
|
6
|
+
# typenative package page → Settings → Publishing access → Trusted Publisher → Connect GitHub
|
|
7
|
+
# - Organization or user: danisss9
|
|
8
|
+
# - Repository: typenative
|
|
9
|
+
# - Workflow filename: npm-publish-github-packages.yml
|
|
10
|
+
# - Environment name: (leave blank)
|
|
11
|
+
# Requires npm >= 11.5.1 (ensured below) and the `id-token: write` permission.
|
|
7
12
|
|
|
8
13
|
name: Release & Publish
|
|
9
14
|
|
|
@@ -22,7 +27,7 @@ jobs:
|
|
|
22
27
|
node-version: 24
|
|
23
28
|
- uses: actions/setup-go@v5
|
|
24
29
|
with:
|
|
25
|
-
go-version: '1.
|
|
30
|
+
go-version: '1.23'
|
|
26
31
|
- run: npm ci
|
|
27
32
|
- run: npm ci
|
|
28
33
|
working-directory: test
|
|
@@ -61,6 +66,9 @@ jobs:
|
|
|
61
66
|
node-version: 24
|
|
62
67
|
registry-url: https://registry.npmjs.org/
|
|
63
68
|
|
|
69
|
+
# Trusted Publishing (OIDC) requires npm >= 11.5.1 — ensure we have it
|
|
70
|
+
- run: npm install -g npm@latest
|
|
71
|
+
|
|
64
72
|
- name: Extract version from tag
|
|
65
73
|
id: version
|
|
66
74
|
run: echo "VERSION=${GITHUB_REF_NAME#TypeNative_}" >> $GITHUB_OUTPUT
|
|
@@ -70,6 +78,6 @@ jobs:
|
|
|
70
78
|
|
|
71
79
|
- run: npm ci
|
|
72
80
|
|
|
81
|
+
# Authenticates via OIDC (id-token: write) — no NODE_AUTH_TOKEN needed.
|
|
82
|
+
# Provenance is included automatically; the flag is kept for explicitness.
|
|
73
83
|
- run: npm publish --provenance --access public
|
|
74
|
-
env:
|
|
75
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,76 @@ All notable changes to TypeNative will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.0] - 2026-10-09
|
|
9
|
+
|
|
10
|
+
TypeNative compiles itself: `src/main.ts` builds into a native `typenative` binary that passes the
|
|
11
|
+
whole test suite and regenerates its own Go source identically (stage-1/stage-2 bootstrap).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Native compiler**: `npm run build:native` builds `native/typenative` and `native/tsparser`
|
|
16
|
+
- **`any` / `unknown` at runtime**: property and index access, truthiness, equality (objects by
|
|
17
|
+
identity), method calls, spreads and conversions to typed values via a small Go runtime
|
|
18
|
+
(`TnGet`, `TnSet`, `TnAs`, …) — no guessed types
|
|
19
|
+
- **Language**: object spread, `??=`, `in`, `typeof`, `continue`, optional and destructured
|
|
20
|
+
parameters, constructor parameter properties, `return` inside `try`/`catch`, object type
|
|
21
|
+
aliases and inline object types → Go structs, `Record<K, V>` and dictionaries → Go maps
|
|
22
|
+
- **Typing**: contextual types for object/array/function literals, narrowing of `T | undefined`,
|
|
23
|
+
JS truthiness, `??` chains, nullable ternaries, optional chains across calls
|
|
24
|
+
- **Library**: `toFixed`, `Math.max(...arr)`, `sort` with comparators, `flat`, typed
|
|
25
|
+
`Object.keys`/`values`, regex `replace` (`/g`, `$1`/`$&`, function replacers), `match`/`split`
|
|
26
|
+
with regexes, `Array.isArray`, `console.warn`, `fs.unlinkSync`, `process.env['X']`
|
|
27
|
+
- Tests 33–50
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Go 1.23 or newer is required** (generated code uses `slices`/`maps` iterator helpers)
|
|
32
|
+
- `process.platform` reports Node's names (`win32`, `darwin`, `linux`)
|
|
33
|
+
- `JSON.stringify` no longer escapes `<`, `>`, `&` (like JS)
|
|
34
|
+
- Out-of-range array/string reads give zero values instead of panicking (JS `undefined`)
|
|
35
|
+
- Imports and helpers are emitted in sorted order
|
|
36
|
+
- Map, Set and Record keep insertion order (like JS); objects are references (Go pointers)
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- The test suite never ran (scripts called `bin/index.js`, which has no entry point); it now runs
|
|
41
|
+
through `bin/cli.js`
|
|
42
|
+
- Phase 3c regressions (19 tests): AST kind names, missing parent links, `extends` detection
|
|
43
|
+
- Main-file top-level variables were invisible to functions
|
|
44
|
+
- `spawnSync` with `stdio: 'inherit'` dropped its arguments
|
|
45
|
+
|
|
46
|
+
## [0.0.21] - 2026-09-06
|
|
47
|
+
|
|
48
|
+
Self-hosting milestone: the compiler no longer depends on `execa`, `fs-extra`, or `nanoid`, and the
|
|
49
|
+
TypeScript parser is decoupled behind an injectable, normalized JSON AST — produced either by the
|
|
50
|
+
`typescript` npm package (dev CLI) or by the new Go-native `tsparser` tool (self-hosted binary).
|
|
51
|
+
Verified: all 33 test specs transpile identically through both parser paths, and the compiler now
|
|
52
|
+
transpiles its own source all the way to `go build`.
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **`node:fs` mappings**: `readFileSync`, `writeFileSync`, `appendFileSync`, `existsSync`, `mkdirSync`, `readdirSync`, `copyFileSync`, `rmSync` → Go helpers (`TnReadFile`, `TnWriteFile`, …)
|
|
57
|
+
- **`node:url` mappings**: `fileURLToPath`, `pathToFileURL`
|
|
58
|
+
- **`node:os` mappings**: `platform` (returns `win32`/`darwin`/linux like Node), `homedir`, `tmpdir`
|
|
59
|
+
- **`node:child_process` mappings**: sync `exec`/`execSync`/`spawnSync` shaped like Node's `spawnSync` (`{ stdout, stderr, status }`); `{ stdio: 'inherit' }` runs with the parent's stdio (`TnRunInherit`); `{ input }` pipes stdin (`TnExecInput`)
|
|
60
|
+
- **`node:readline` mapping**: synchronous `question(prompt)` for interactive native prompts (`TnQuestion`, single buffered stdin reader)
|
|
61
|
+
- **`process.env.X`** → `TnGetenv("X")`
|
|
62
|
+
- **Go helper registry**: node-stdlib helpers are emitted once into the main output file with correct per-file import scoping (helpers register their Go packages lazily; import lists are computed after function bodies are visited)
|
|
63
|
+
- **`tsparser/` Go tool**: parses TypeScript via `github.com/buke/typescript-go-internal` (native Go port of the TypeScript parser — no embedded JS runtime) and emits a normalized JSON AST with TypeScript-API kind and field names
|
|
64
|
+
- **`src/parse-node.ts`**: Node adapter producing the identical JSON AST from the `typescript` npm package; verified byte-identical against `tsparser` on all test specs and all compiler sources
|
|
65
|
+
- **`src/parse-native.ts`**: self-hosted parser client (spawns the `tsparser` binary, `TYPENATIVE_TSPARSER` env override)
|
|
66
|
+
- **Injectable parser**: `transpileToNative(code, { parse, readFile })` — the transpiler no longer imports `typescript`
|
|
67
|
+
- **Unbraced `if`/`else if`/`else` statements**: single-statement branches now emit valid Go blocks
|
|
68
|
+
- **`%` and `%=` on numbers** → `math.Mod`
|
|
69
|
+
- **`./x.js` → `x.ts` import resolution** for ES-style relative specifiers
|
|
70
|
+
- **Tests 31–32**: node-stdlib end-to-end spec (fs round-trip, os platform, shell exec) and subprocess/control-flow spec
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- **CLI entry split**: `bin/cli.js` (npm `bin`) keeps inquirer for interactive Node use; the transpilable core lives in `src/index.ts` (`run`/`createProject`), with `src/main.ts` as the self-hosting entry and `src/prompt.ts` (transpilable sync prompts) replacing inquirer in native builds
|
|
75
|
+
- **Transpiler internals**: the `ts` typechecker is replaced by syntactic `typeNodeToText`; node predicates became plain `isX()` kind-string functions; operator tokens map through a kind-name table
|
|
76
|
+
- Dropped runtime dependencies: `execa`, `fs-extra`, `nanoid` (replaced by a local `goSafeId`)
|
|
77
|
+
|
|
8
78
|
## [0.0.20] - 2026-03-16
|
|
9
79
|
|
|
10
80
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Build native applications using Typescript.
|
|
|
5
5
|
## PreRequisites
|
|
6
6
|
|
|
7
7
|
- [Nodejs v24](https://nodejs.org/en) or newer.
|
|
8
|
-
- [Go 1.
|
|
8
|
+
- [Go 1.23](https://go.dev/doc/install) or newer.
|
|
9
9
|
|
|
10
10
|
## Get Started
|
|
11
11
|
|
|
@@ -24,7 +24,7 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
24
24
|
| boolean | ✅ | Transpiled to `bool` |
|
|
25
25
|
| string | ✅ | |
|
|
26
26
|
| null | ✅ | |
|
|
27
|
-
| any
|
|
27
|
+
| any / unknown | ✅ | Dynamic values handled at runtime (JSON-like objects, arrays, primitives) |
|
|
28
28
|
| Nullable types | ✅ | `T \| null` / `T \| undefined` transpiled to Go pointer types |
|
|
29
29
|
|
|
30
30
|
**Variables & Objects**
|
|
@@ -36,6 +36,9 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
36
36
|
| Object shorthand | ✅ | `{ name }` → `{ name: name }` |
|
|
37
37
|
| Array destructuring | ✅ | `const [a, b] = arr` |
|
|
38
38
|
| Object destructuring | ✅ | `const { x, y } = obj` |
|
|
39
|
+
| Object spread | ✅ | `{ ...base, x: 1 }` |
|
|
40
|
+
| Records / dictionaries | ✅ | `Record<K, V>` and objects indexed by key → Go maps |
|
|
41
|
+
| Object type aliases | ✅ | `type P = { x: number }` → Go struct |
|
|
39
42
|
|
|
40
43
|
**Operators**
|
|
41
44
|
|
|
@@ -50,6 +53,9 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
50
53
|
| Ternary expressions | ✅ | `condition ? a : b` |
|
|
51
54
|
| Nullish coalescing | ✅ | `??` operator |
|
|
52
55
|
| Optional chaining | ✅ | `obj?.prop`, `arr?.[i]` |
|
|
56
|
+
| Nullish assignment | ✅ | `x ??= value` |
|
|
57
|
+
| `in` / `typeof` | ✅ | `"key" in obj`, `typeof x === "string"` |
|
|
58
|
+
| JS truthiness | ✅ | `if (str)`, `!count`, `a \|\| fallback` on any type |
|
|
53
59
|
|
|
54
60
|
**Control Flow**
|
|
55
61
|
|
|
@@ -58,11 +64,13 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
58
64
|
| If/Else statements | ✅ | Fully supported |
|
|
59
65
|
| Switch statements | ✅ | Case and default statements |
|
|
60
66
|
| For loops | ✅ | Standard `for` loops |
|
|
67
|
+
| Continue | ✅ | `continue` in all loop kinds |
|
|
61
68
|
| For...of loops | ✅ | Arrays, Maps, Sets; `Object.entries()` unwrapping |
|
|
62
69
|
| For...in loops | ✅ | Iterates keys via Go `range` |
|
|
63
70
|
| While loops | ✅ | Transpiled to Go's `for` loops |
|
|
64
71
|
| Do...while loops | ✅ | Implemented with conditional break |
|
|
65
72
|
| Try/Catch/Finally | ✅ | `throw` → `panic`; catch/finally via `defer`/`recover` |
|
|
73
|
+
| Narrowing | ✅ | `T \| undefined` narrowed after `if (x)`, `x !== undefined`, early returns |
|
|
66
74
|
|
|
67
75
|
**Data Structures & Array Methods**
|
|
68
76
|
|
|
@@ -74,6 +82,8 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
74
82
|
| Method chaining | ✅ | e.g. `.map(...).filter(...).join(...)` |
|
|
75
83
|
| Map | ✅ | `Map<K, V>` → Go `map[K]V`; `.set()`, `.get()`, `.has()`, `.delete()`, `.clear()`, `.size` |
|
|
76
84
|
| Set | ✅ | `Set<T>` → Go `map[T]struct{}`; `.add()`, `.has()`, `.delete()`, `.clear()`, `.size` |
|
|
85
|
+
| Object.keys / values | ✅ | Typed results for maps and records |
|
|
86
|
+
| Array sort / flat | ✅ | `sort((a, b) => a - b)` in place, `flat()` one level |
|
|
77
87
|
|
|
78
88
|
**Functions**
|
|
79
89
|
|
|
@@ -87,12 +97,15 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
87
97
|
| Generics (functions/classes) | ✅ | Type parameters via Go generics |
|
|
88
98
|
| Default parameter values | ✅ | `function(x = defaultValue)` |
|
|
89
99
|
| Rest parameters | ✅ | `function(...args: T[])` → Go variadic |
|
|
100
|
+
| Optional parameters | ✅ | `function f(x?: string)` |
|
|
101
|
+
| Destructured parameters | ✅ | `function f({ a, b }: T)`, `([x, y]: number[])` |
|
|
90
102
|
|
|
91
103
|
**Classes & Interfaces**
|
|
92
104
|
|
|
93
105
|
| Feature | Supported | Notes |
|
|
94
106
|
| ------------------- | :-------: | -------------------------------------------------------------- |
|
|
95
107
|
| Classes | ✅ | Transpiled to Go structs with constructor and receiver methods |
|
|
108
|
+
| Parameter properties | ✅ | `constructor(private x: number)` |
|
|
96
109
|
| Class inheritance | ✅ | `extends` via embedded structs, `super()` supported |
|
|
97
110
|
| Static members | ✅ | `static method()` / `static prop` → package-level declarations |
|
|
98
111
|
| Getters / Setters | ✅ | `get prop()` → `Get_prop()`, `set prop(v)` → `Set_prop(v)` |
|
|
@@ -131,7 +144,8 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
131
144
|
| Math.random | ✅ | Mapped to `rand.Float64()` |
|
|
132
145
|
| Math.floor / ceil / round | ✅ | Mapped to `math.Floor`, `math.Ceil`, `math.Round` |
|
|
133
146
|
| Math.abs / sqrt / pow | ✅ | Mapped to corresponding `math` functions |
|
|
134
|
-
| Math.min / max | ✅ |
|
|
147
|
+
| Math.min / max | ✅ | Go builtins `min` / `max` |
|
|
148
|
+
| Math.max(...arr) | ✅ | Spread and any number of arguments via `slices.Max` / `max` |
|
|
135
149
|
| Math.log / log2 / log10 | ✅ | Mapped to `math.Log`, `math.Log2`, `math.Log10` |
|
|
136
150
|
| Math.sin / cos / tan | ✅ | Mapped to `math.Sin`, `math.Cos`, `math.Tan` |
|
|
137
151
|
| Math.trunc / sign | ✅ | Mapped to `math.Trunc` and inline sign check |
|
|
@@ -146,6 +160,7 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
146
160
|
| split / includes / indexOf | ✅ | Via `strings` package |
|
|
147
161
|
| startsWith / endsWith | ✅ | Via `strings` package |
|
|
148
162
|
| replace / replaceAll | ✅ | Via `strings` package |
|
|
163
|
+
| replace with RegExp | ✅ | `/g` vs first match, `$1`/`$&` patterns, function replacers |
|
|
149
164
|
| charAt / substring / slice | ✅ | Direct Go string indexing/slicing |
|
|
150
165
|
| concat / repeat | ✅ | String concatenation and `strings.Repeat` |
|
|
151
166
|
| padStart / padEnd | ✅ | Via `strings.Repeat` |
|
|
@@ -157,6 +172,7 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
157
172
|
| Feature | Supported | Notes |
|
|
158
173
|
| -------- | :-------: | ----------------------------------------------------- |
|
|
159
174
|
| toString | ✅ | Universal `toString()` via `fmt.Sprintf` for any type |
|
|
175
|
+
| toFixed | ✅ | `n.toFixed(2)` via `strconv.FormatFloat` |
|
|
160
176
|
|
|
161
177
|
**RegExp**
|
|
162
178
|
|
|
@@ -180,4 +196,25 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
|
|
|
180
196
|
| npm package imports | ✅ | `import { x } from 'pkg'` mapped to Go module imports |
|
|
181
197
|
| Named exports | ✅ | `export function` / `export const` declarations |
|
|
182
198
|
|
|
183
|
-
|
|
199
|
+
## Native (self-hosted) compiler
|
|
200
|
+
|
|
201
|
+
TypeNative compiles itself: `src/main.ts` transpiles to Go and builds into a native `typenative`
|
|
202
|
+
binary that needs no Node.js at runtime and produces the same Go output as the npm package.
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
npm run build # compile the TypeScript sources
|
|
206
|
+
npm run build:native # build native/typenative and native/tsparser
|
|
207
|
+
native/typenative --source app.ts --script
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Building the native compiler requires Go 1.26 (for the TypeScript parser, `tsparser/`). The
|
|
211
|
+
`typenative` binary finds `tsparser` next to itself, or via the `TYPENATIVE_TSPARSER`
|
|
212
|
+
environment variable.
|
|
213
|
+
|
|
214
|
+
### Known limitations
|
|
215
|
+
|
|
216
|
+
- Stateful `/g` regex loops (`while ((m = re.exec(s)))`) are not supported — use `matchAll`
|
|
217
|
+
- `import.meta.url` is not lowered
|
|
218
|
+
- Object types compile to Go structs, which are copied by value (JS objects are references)
|
|
219
|
+
|
|
220
|
+
TypeNative is in early development and new features are being added regularly.
|
package/TODO.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Self-Hosting Roadmap
|
|
2
|
+
|
|
3
|
+
Goal: TypeNative transpiles itself into a native Go binary (1.0 milestone).
|
|
4
|
+
|
|
5
|
+
## Done
|
|
6
|
+
- [x] node:fs mappings (read/write/append/exists/mkdir/readdir/copy/rm) via Go helpers
|
|
7
|
+
- [x] node:url (fileURLToPath/pathToFileURL), node:os (platform/homedir/tmpdir)
|
|
8
|
+
- [x] node:child_process (spawnSync-shaped sync API, stdio-inherit runs)
|
|
9
|
+
- [x] node:readline sync `question` (stdin prompts in native binaries)
|
|
10
|
+
- [x] Go helper-function registry emitted into the main output file
|
|
11
|
+
- [x] Per-file import scoping (imports registered after function-body visits)
|
|
12
|
+
- [x] Unbraced if/else-if/else statements, `%`/`%=` → math.Mod
|
|
13
|
+
- [x] nanoid/fs-extra/execa removed from src; inquirer isolated in Node-only cli.ts
|
|
14
|
+
- [x] Transpilable core: src/index.ts (run/createProject), src/prompt.ts, src/main.ts (self-host entry)
|
|
15
|
+
|
|
16
|
+
## Done (phase 3a/3b)
|
|
17
|
+
- [x] tsparser/ Go tool: parses TS via buke/typescript-go-internal (no JS runtime),
|
|
18
|
+
emits normalized JSON AST (kind + TS-spec field names)
|
|
19
|
+
- [x] src/parse-node.ts: Node adapter (typescript npm pkg → same JSON shape)
|
|
20
|
+
- [x] Verified byte-identical AST signatures across all 32 test specs AND all
|
|
21
|
+
compiler sources (incl. transpiler.ts, 521k-char signature)
|
|
22
|
+
|
|
23
|
+
- [x] ts shim converted to plain isX() functions + string kind literals
|
|
24
|
+
(object-literal shim was itself untranspilable)
|
|
25
|
+
- [x] Phase 3c: transpiler.ts no longer imports `typescript` — consumes the JSON AST
|
|
26
|
+
via an injected parse function (ts shim keeps predicate call sites); operator
|
|
27
|
+
tokens, process.env, stdin-exec mappings added; verified identical Go output
|
|
28
|
+
via npm parser and Go-parser paths on all 33 test specs
|
|
29
|
+
- [x] Phase 4: src/main.ts (incl. transpiler.ts) compiles to a native binary;
|
|
30
|
+
`any` values use a small Go runtime (TnGet/TnSet/TnAs/...) instead of guessed types
|
|
31
|
+
- [x] Phase 5: stage-1/stage-2 bootstrap — the native compiler passes all test specs
|
|
32
|
+
and reproduces its own Go source identically (fixpoint)
|
|
33
|
+
|
|
34
|
+
## Next
|
|
35
|
+
- [ ] CI self-hosting job (stage 1 → stage 2 → compare); README/CHANGELOG updates
|
|
36
|
+
- [ ] Stateful RegExp /g (`lastIndex`, `while ((m = re.exec(s)))` loops)
|
|
37
|
+
- [ ] import.meta.url lowering
|
|
38
|
+
- [ ] Gradually type the AST in transpiler.ts (fewer dynamic lookups, faster binary)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Node CLI entry point (npm `bin`). Interactive prompts use inquirer here;
|
|
3
|
+
// the actual compiler logic lives in the transpilable src/index.ts.
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { run, createProject } from './index.js';
|
|
6
|
+
import { parseAstJson } from './parse-node.js';
|
|
7
|
+
(async function main() {
|
|
8
|
+
const scriptMode = process.argv.findIndex((a) => a === '--script') > -1;
|
|
9
|
+
const newCommand = process.argv.findIndex((a) => a === '--new') > -1;
|
|
10
|
+
const sourceIndex = process.argv.findIndex((a) => a === '--source');
|
|
11
|
+
const source = sourceIndex > -1 ? process.argv[sourceIndex + 1] : null;
|
|
12
|
+
const outputIndex = process.argv.findIndex((a) => a === '--output');
|
|
13
|
+
const output = outputIndex > -1 ? process.argv[outputIndex + 1] : null;
|
|
14
|
+
const answers = await inquirer.prompt([
|
|
15
|
+
{
|
|
16
|
+
type: 'input',
|
|
17
|
+
name: 'projectName',
|
|
18
|
+
message: 'Enter Project Name:',
|
|
19
|
+
when: newCommand,
|
|
20
|
+
validate: (input) => input.trim() !== ''
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'confirm',
|
|
24
|
+
name: 'installDependencies',
|
|
25
|
+
message: 'Do you want to install dependencies?',
|
|
26
|
+
when: newCommand
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'input',
|
|
30
|
+
name: 'path',
|
|
31
|
+
message: 'Enter Path to typescript main file:',
|
|
32
|
+
when: !newCommand && !scriptMode && !source,
|
|
33
|
+
validate: (input) => input.trim() !== ''
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'input',
|
|
37
|
+
name: 'output',
|
|
38
|
+
message: 'Enter Output Path:',
|
|
39
|
+
when: !newCommand && !scriptMode && !output,
|
|
40
|
+
validate: (input) => input.trim() !== ''
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'editor',
|
|
44
|
+
name: 'tsCode',
|
|
45
|
+
message: 'Write your typescript code here:',
|
|
46
|
+
when: !newCommand && scriptMode && !source,
|
|
47
|
+
default: `console.log('Hello, World!');`
|
|
48
|
+
}
|
|
49
|
+
]);
|
|
50
|
+
if (newCommand) {
|
|
51
|
+
createProject(answers.projectName.trim(), answers.installDependencies);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const sourcePath = answers.tsCode ? null : (source ?? answers.path ?? null);
|
|
55
|
+
run({
|
|
56
|
+
source: sourcePath,
|
|
57
|
+
output: output ?? answers.output ?? null,
|
|
58
|
+
scriptMode,
|
|
59
|
+
tsCode: answers.tsCode ?? null,
|
|
60
|
+
parse: parseAstJson
|
|
61
|
+
});
|
|
62
|
+
})();
|
package/bin/index.js
CHANGED
|
@@ -1,151 +1,123 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Core compiler logic — kept free of Node-only dependencies (inquirer, execa,
|
|
2
|
+
// fs-extra) so this module transpiles with TypeNative itself.
|
|
3
|
+
// Node-only interactive prompting lives in src/cli.ts; the self-hosted entry
|
|
4
|
+
// point is src/main.ts.
|
|
4
5
|
import path from 'node:path';
|
|
5
|
-
import
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import { platform } from 'node:os';
|
|
8
|
+
// TypeNative maps node:child_process to a synchronous Go API whose shape differs
|
|
9
|
+
// from Node's; under Node these functions are never called (the CLI wrapper does
|
|
10
|
+
// not reach them), so they're accessed through `any` casts for type-checking.
|
|
11
|
+
import * as childProcess from 'node:child_process';
|
|
6
12
|
import { transpileToNative } from './transpiler.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
validate: (input) => input.trim() !== ''
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
type: 'input',
|
|
40
|
-
name: 'output',
|
|
41
|
-
message: 'Enter Output Path:',
|
|
42
|
-
when: !newCommand && !scriptMode && !output,
|
|
43
|
-
validate: (input) => input.trim() !== ''
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
type: 'editor',
|
|
47
|
-
name: 'tsCode',
|
|
48
|
-
message: 'Write your typescript code here:',
|
|
49
|
-
when: !newCommand && scriptMode && !source,
|
|
50
|
-
default: `console.log('Hello, World!');`
|
|
51
|
-
}
|
|
52
|
-
]);
|
|
53
|
-
if (newCommand) {
|
|
54
|
-
const projectName = answers.projectName.trim();
|
|
55
|
-
await fs.ensureDir(projectName);
|
|
56
|
-
await fs.writeFile(path.join(projectName, 'main.ts'), `// Write your TypeScript code here\nconsole.log('Hello, World!');\n`, { encoding: 'utf-8' });
|
|
57
|
-
await fs.writeFile(path.join(projectName, 'tsconfig.json'), getTsConfig(), {
|
|
58
|
-
encoding: 'utf-8'
|
|
59
|
-
});
|
|
60
|
-
await fs.writeFile(path.join(projectName, 'package.json'), getPackageJson(projectName), {
|
|
61
|
-
encoding: 'utf-8'
|
|
62
|
-
});
|
|
63
|
-
await fs.writeFile(path.join(projectName, '.gitignore'), getGitIgnore(), {
|
|
64
|
-
encoding: 'utf-8'
|
|
65
|
-
});
|
|
66
|
-
await fs.writeFile(path.join(projectName, 'README.md'), getReadMe(projectName), {
|
|
67
|
-
encoding: 'utf-8'
|
|
68
|
-
});
|
|
69
|
-
console.log(`Project "${projectName}" created successfully!`);
|
|
70
|
-
if (answers.installDependencies) {
|
|
71
|
-
console.log('Installing dependencies...');
|
|
72
|
-
await execa('npm', ['install'], { cwd: projectName, stdio: 'inherit' });
|
|
73
|
-
console.log('Dependencies installed successfully!');
|
|
13
|
+
// Run a command and capture its output. Uses Node's spawnSync under Node and a
|
|
14
|
+
// Go helper (os/exec) once transpiled — both synchronous, same result shape.
|
|
15
|
+
export function runCommand(name, args) {
|
|
16
|
+
const result = childProcess.spawnSync(name, args, { encoding: 'utf-8' });
|
|
17
|
+
return { stdout: result.stdout, stderr: result.stderr, status: result.status };
|
|
18
|
+
}
|
|
19
|
+
// Run a command with the parent's stdio, returns the exit code.
|
|
20
|
+
export function runInherit(name, args = []) {
|
|
21
|
+
const result = childProcess.spawnSync(name, args, { stdio: 'inherit' });
|
|
22
|
+
return result.status;
|
|
23
|
+
}
|
|
24
|
+
export function createProject(projectName, installDependencies) {
|
|
25
|
+
fs.mkdirSync(projectName, { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(projectName, 'main.ts'), `// Write your TypeScript code here\nconsole.log('Hello, World!');\n`);
|
|
27
|
+
fs.writeFileSync(path.join(projectName, 'tsconfig.json'), getTsConfig());
|
|
28
|
+
fs.writeFileSync(path.join(projectName, 'package.json'), getPackageJson(projectName));
|
|
29
|
+
fs.writeFileSync(path.join(projectName, '.gitignore'), getGitIgnore());
|
|
30
|
+
fs.writeFileSync(path.join(projectName, 'README.md'), getReadMe(projectName));
|
|
31
|
+
console.log(`Project "${projectName}" created successfully!`);
|
|
32
|
+
if (installDependencies) {
|
|
33
|
+
console.log('Installing dependencies...');
|
|
34
|
+
// npm --prefix avoids needing cwd support in the subprocess binding
|
|
35
|
+
const result = runCommand('npm', ['install', '--prefix', projectName]);
|
|
36
|
+
if (result.stdout)
|
|
37
|
+
console.log(result.stdout.trim());
|
|
38
|
+
if (result.status !== 0) {
|
|
39
|
+
if (result.stderr)
|
|
40
|
+
console.error(result.stderr.trim());
|
|
41
|
+
process.exit(result.status);
|
|
74
42
|
}
|
|
75
|
-
|
|
43
|
+
console.log('Dependencies installed successfully!');
|
|
76
44
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
45
|
+
}
|
|
46
|
+
export function run(opts) {
|
|
47
|
+
const sourcePath = opts.tsCode ? null : opts.source;
|
|
48
|
+
const tsCode = opts.tsCode ? opts.tsCode : fs.readFileSync(sourcePath, 'utf-8');
|
|
81
49
|
const sourceDir = sourcePath ? path.dirname(path.resolve(sourcePath)) : null;
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
50
|
+
const transpileOptions = { parse: opts.parse };
|
|
51
|
+
if (sourceDir) {
|
|
52
|
+
transpileOptions.readFile = (specifier, fromDir) => {
|
|
53
|
+
const baseDir = fromDir ?? sourceDir;
|
|
54
|
+
// Relative or absolute path → resolve from baseDir
|
|
55
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
56
|
+
// ES convention: ./x.js may refer to x.ts
|
|
57
|
+
const tsSpecifier = specifier.endsWith('.js')
|
|
58
|
+
? specifier.replace(/\.js$/, '.ts')
|
|
59
|
+
: specifier;
|
|
60
|
+
for (const candidate of [tsSpecifier + '.ts', tsSpecifier, specifier]) {
|
|
61
|
+
try {
|
|
62
|
+
const fullPath = path.resolve(baseDir, candidate);
|
|
63
|
+
return {
|
|
64
|
+
content: fs.readFileSync(fullPath, 'utf-8'),
|
|
65
|
+
dir: path.dirname(fullPath)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
/* not found */
|
|
99
70
|
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
// npm package — walk up from baseDir looking for node_modules/<name>
|
|
103
|
-
const resolved = resolveNpmPackage(baseDir, specifier);
|
|
104
|
-
if (!resolved)
|
|
105
|
-
return null;
|
|
106
|
-
let { content, dir } = resolved;
|
|
107
|
-
// Normalize CommonJS to ES module syntax
|
|
108
|
-
if (!content.includes('export ') && (content.includes('module.exports') || content.includes('exports.'))) {
|
|
109
|
-
content = normalizeCjsContent(content);
|
|
110
71
|
}
|
|
111
|
-
|
|
112
|
-
const typed = tryInjectDtsTypes(content, specifier, sourceDir);
|
|
113
|
-
if (typed)
|
|
114
|
-
content = typed;
|
|
115
|
-
return { content, dir };
|
|
72
|
+
return null;
|
|
116
73
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
74
|
+
// npm package — walk up from baseDir looking for node_modules/<name>
|
|
75
|
+
const resolved = resolveNpmPackage(baseDir, specifier);
|
|
76
|
+
if (!resolved)
|
|
77
|
+
return null;
|
|
78
|
+
let { content, dir } = resolved;
|
|
79
|
+
// Normalize CommonJS to ES module syntax
|
|
80
|
+
if (!content.includes('export ') &&
|
|
81
|
+
(content.includes('module.exports') || content.includes('exports.'))) {
|
|
82
|
+
content = normalizeCjsContent(content);
|
|
83
|
+
}
|
|
84
|
+
// Inject types from a local ambient .d.ts if available
|
|
85
|
+
const typed = tryInjectDtsTypes(content, specifier, sourceDir);
|
|
86
|
+
if (typed)
|
|
87
|
+
content = typed;
|
|
88
|
+
return { content, dir };
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const transpileResult = transpileToNative(tsCode, transpileOptions);
|
|
92
|
+
const exeName = platform() === 'win32' ? 'native.exe' : 'native';
|
|
120
93
|
const exePath = `dist/${exeName}`;
|
|
121
|
-
|
|
94
|
+
fs.mkdirSync('dist', { recursive: true });
|
|
122
95
|
// Clean up stale Go files from previous runs before writing new ones
|
|
123
|
-
for (const existing of
|
|
96
|
+
for (const existing of fs.readdirSync('dist')) {
|
|
124
97
|
if (existing.endsWith('.go'))
|
|
125
|
-
|
|
98
|
+
fs.rmSync(`dist/${existing}`);
|
|
126
99
|
}
|
|
127
|
-
|
|
100
|
+
fs.writeFileSync('dist/code.go', transpileResult.main);
|
|
128
101
|
const goFiles = ['dist/code.go'];
|
|
129
102
|
for (const [filename, content] of transpileResult.files) {
|
|
130
|
-
|
|
103
|
+
fs.writeFileSync(`dist/${filename}`, content);
|
|
131
104
|
goFiles.push(`dist/${filename}`);
|
|
132
105
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
stdio: 'inherit'
|
|
140
|
-
});
|
|
141
|
-
//await fs.remove(exePath);
|
|
106
|
+
const buildResult = runCommand('go', ['build', '-o', exePath, ...goFiles]);
|
|
107
|
+
if (buildResult.status !== 0) {
|
|
108
|
+
console.error('go build failed:');
|
|
109
|
+
if (buildResult.stderr)
|
|
110
|
+
console.error(buildResult.stderr.trim());
|
|
111
|
+
process.exit(1);
|
|
142
112
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
//await fs.remove(exePath);
|
|
146
|
-
console.log(`Created native executable at: ${output ?? answers.output}`);
|
|
113
|
+
if (opts.scriptMode) {
|
|
114
|
+
process.exit(runInherit(exePath));
|
|
147
115
|
}
|
|
148
|
-
|
|
116
|
+
else if (opts.output) {
|
|
117
|
+
fs.copyFileSync(exePath, opts.output);
|
|
118
|
+
console.log(`Created native executable at: ${opts.output}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
149
121
|
function normalizeCjsContent(code) {
|
|
150
122
|
code = code.replace(/['"]use strict['"];?\n?/g, '');
|
|
151
123
|
code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
|
|
@@ -177,8 +149,7 @@ function tryInjectDtsTypes(jsContent, packageName, searchDir) {
|
|
|
177
149
|
// Extract typed function signatures from the .d.ts module body
|
|
178
150
|
const signatures = new Map();
|
|
179
151
|
const sigRegex = /export function (\w+)\(([^)]*)\)\s*:\s*([^\n;]+)/g;
|
|
180
|
-
|
|
181
|
-
while ((m = sigRegex.exec(dtsBody)) !== null) {
|
|
152
|
+
for (const m of dtsBody.matchAll(sigRegex)) {
|
|
182
153
|
signatures.set(m[1], { params: m[2].trim(), returnType: m[3].trim() });
|
|
183
154
|
}
|
|
184
155
|
if (signatures.size === 0)
|
|
@@ -237,7 +208,7 @@ function resolveNpmPackage(fromDir, packageName) {
|
|
|
237
208
|
return null;
|
|
238
209
|
}
|
|
239
210
|
function getPackageJson(projectName) {
|
|
240
|
-
const exeName =
|
|
211
|
+
const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
|
|
241
212
|
const pckg = {
|
|
242
213
|
name: projectName,
|
|
243
214
|
version: '1.0.0',
|
|
@@ -274,7 +245,7 @@ bin/
|
|
|
274
245
|
`;
|
|
275
246
|
}
|
|
276
247
|
function getReadMe(projectName) {
|
|
277
|
-
const exeName =
|
|
248
|
+
const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
|
|
278
249
|
return `# ${projectName}
|
|
279
250
|
|
|
280
251
|
This project was created using TypeNative, a tool to transpile TypeScript code to native Go code.
|