typenative 0.0.20 → 0.0.21

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.
@@ -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
- # Requires an NPM_TOKEN secret set in your GitHub repository settings.
5
- # Setup: npmjs.com → Access Tokens → Generate New Token (Automation)
6
- # GitHub repo → Settings → Secrets → Actions → New secret: NPM_TOKEN
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
 
@@ -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,38 @@ 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.0.21] - 2026-09-06
9
+
10
+ Self-hosting milestone: the compiler no longer depends on `execa`, `fs-extra`, or `nanoid`, and the
11
+ TypeScript parser is decoupled behind an injectable, normalized JSON AST — produced either by the
12
+ `typescript` npm package (dev CLI) or by the new Go-native `tsparser` tool (self-hosted binary).
13
+ Verified: all 33 test specs transpile identically through both parser paths, and the compiler now
14
+ transpiles its own source all the way to `go build`.
15
+
16
+ ### Added
17
+
18
+ - **`node:fs` mappings**: `readFileSync`, `writeFileSync`, `appendFileSync`, `existsSync`, `mkdirSync`, `readdirSync`, `copyFileSync`, `rmSync` → Go helpers (`TnReadFile`, `TnWriteFile`, …)
19
+ - **`node:url` mappings**: `fileURLToPath`, `pathToFileURL`
20
+ - **`node:os` mappings**: `platform` (returns `win32`/`darwin`/linux like Node), `homedir`, `tmpdir`
21
+ - **`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`)
22
+ - **`node:readline` mapping**: synchronous `question(prompt)` for interactive native prompts (`TnQuestion`, single buffered stdin reader)
23
+ - **`process.env.X`** → `TnGetenv("X")`
24
+ - **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)
25
+ - **`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
26
+ - **`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
27
+ - **`src/parse-native.ts`**: self-hosted parser client (spawns the `tsparser` binary, `TYPENATIVE_TSPARSER` env override)
28
+ - **Injectable parser**: `transpileToNative(code, { parse, readFile })` — the transpiler no longer imports `typescript`
29
+ - **Unbraced `if`/`else if`/`else` statements**: single-statement branches now emit valid Go blocks
30
+ - **`%` and `%=` on numbers** → `math.Mod`
31
+ - **`./x.js` → `x.ts` import resolution** for ES-style relative specifiers
32
+ - **Tests 31–32**: node-stdlib end-to-end spec (fs round-trip, os platform, shell exec) and subprocess/control-flow spec
33
+
34
+ ### Changed
35
+
36
+ - **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
37
+ - **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
38
+ - Dropped runtime dependencies: `execa`, `fs-extra`, `nanoid` (replaced by a local `goSafeId`)
39
+
8
40
  ## [0.0.20] - 2026-03-16
9
41
 
10
42
  ### Added
package/TODO.md ADDED
@@ -0,0 +1,34 @@
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
+ - [ ] Phase 4: dogfood src/transpiler.ts (2.6k lines): regex /g semantics,
30
+ String.replace with function replacer, JSON.parse result typing, object spread,
31
+ Array.includes on untyped values, default-import namespace stripping (ts.x → x)
32
+ - [ ] import.meta.url lowering
33
+ - [ ] Phase 5: stage-1/stage-2 bootstrap + run test suite through the native binary
34
+ - [ ] Phase 5: CI self-hosting job; README/CHANGELOG updates
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
- #!/usr/bin/env node
2
- import inquirer from 'inquirer';
3
- import fs from 'fs-extra';
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 { execa } from 'execa';
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
- import { fileURLToPath } from 'node:url';
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
- (async function main() {
11
- const scriptMode = process.argv.findIndex((a) => a === '--script') > -1;
12
- const newCommand = process.argv.findIndex((a) => a === '--new') > -1;
13
- const sourceIndex = process.argv.findIndex((a) => a === '--source');
14
- const source = sourceIndex > -1 ? process.argv[sourceIndex + 1] : null;
15
- const outputIndex = process.argv.findIndex((a) => a === '--output');
16
- const output = outputIndex > -1 ? process.argv[outputIndex + 1] : null;
17
- const answers = await inquirer.prompt([
18
- {
19
- type: 'input',
20
- name: 'projectName',
21
- message: 'Enter Project Name:',
22
- when: newCommand,
23
- validate: (input) => input.trim() !== ''
24
- },
25
- {
26
- type: 'confirm',
27
- name: 'installDependencies',
28
- message: 'Do you want to install dependencies?',
29
- when: newCommand
30
- },
31
- {
32
- type: 'input',
33
- name: 'path',
34
- message: 'Enter Path to typescript main file:',
35
- when: !newCommand && !scriptMode && !source,
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
- return;
43
+ console.log('Dependencies installed successfully!');
76
44
  }
77
- const sourcePath = answers.tsCode ? null : (source ?? answers.path ?? null);
78
- const tsCode = answers.tsCode
79
- ? answers.tsCode
80
- : await fs.readFile(sourcePath, { encoding: 'utf-8' });
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 transpileResult = transpileToNative(tsCode, sourceDir
83
- ? {
84
- readFile: (specifier, fromDir) => {
85
- const baseDir = fromDir ?? sourceDir;
86
- // Relative or absolute path → resolve from baseDir
87
- if (specifier.startsWith('.') || specifier.startsWith('/')) {
88
- for (const candidate of [specifier + '.ts', specifier]) {
89
- try {
90
- const fullPath = path.resolve(baseDir, candidate);
91
- return {
92
- content: fs.readFileSync(fullPath, 'utf-8'),
93
- dir: path.dirname(fullPath)
94
- };
95
- }
96
- catch {
97
- /* not found */
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
- // Inject types from a local ambient .d.ts if available
112
- const typed = tryInjectDtsTypes(content, specifier, sourceDir);
113
- if (typed)
114
- content = typed;
115
- return { content, dir };
72
+ return null;
116
73
  }
117
- }
118
- : undefined);
119
- const exeName = process.platform === 'win32' ? 'native.exe' : 'native';
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
- await fs.ensureDir('dist');
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 await fs.readdir('dist')) {
96
+ for (const existing of fs.readdirSync('dist')) {
124
97
  if (existing.endsWith('.go'))
125
- await fs.remove(`dist/${existing}`);
98
+ fs.rmSync(`dist/${existing}`);
126
99
  }
127
- await fs.writeFile('dist/code.go', transpileResult.main, { encoding: 'utf-8' });
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
- await fs.writeFile(`dist/${filename}`, content, { encoding: 'utf-8' });
103
+ fs.writeFileSync(`dist/${filename}`, content);
131
104
  goFiles.push(`dist/${filename}`);
132
105
  }
133
- await execa('go', ['build', '-o', exePath, ...goFiles], {
134
- stdio: 'inherit'
135
- });
136
- // await fs.remove('dist/code.go');
137
- if (scriptMode) {
138
- await execa(exePath, {
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
- else if (output || answers.output) {
144
- await fs.copy(exePath, output ?? answers.output, { overwrite: true });
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(');
@@ -237,7 +209,7 @@ function resolveNpmPackage(fromDir, packageName) {
237
209
  return null;
238
210
  }
239
211
  function getPackageJson(projectName) {
240
- const exeName = process.platform === 'win32' ? `${projectName}.exe` : projectName;
212
+ const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
241
213
  const pckg = {
242
214
  name: projectName,
243
215
  version: '1.0.0',
@@ -274,7 +246,7 @@ bin/
274
246
  `;
275
247
  }
276
248
  function getReadMe(projectName) {
277
- const exeName = process.platform === 'win32' ? `${projectName}.exe` : projectName;
249
+ const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
278
250
  return `# ${projectName}
279
251
 
280
252
  This project was created using TypeNative, a tool to transpile TypeScript code to native Go code.
package/bin/main.js ADDED
@@ -0,0 +1,31 @@
1
+ // Self-hosting entry point: this file is what TypeNative compiles to produce
2
+ // the native compiler binary. Interactive prompts use the transpilable
3
+ // prompt module (sync stdin) instead of inquirer.
4
+ import { run, createProject } from './index.js';
5
+ import { ask, askMultiline, confirm } from './prompt.js';
6
+ import { parseAstJsonNative } from './parse-native.js';
7
+ (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
+ let source = sourceIndex > -1 ? process.argv[sourceIndex + 1] : null;
12
+ const outputIndex = process.argv.findIndex((a) => a === '--output');
13
+ let output = outputIndex > -1 ? process.argv[outputIndex + 1] : null;
14
+ if (newCommand) {
15
+ const projectName = ask('Enter Project Name:');
16
+ const installDependencies = confirm('Do you want to install dependencies?');
17
+ createProject(projectName, installDependencies);
18
+ return;
19
+ }
20
+ let tsCode = null;
21
+ if (!newCommand && !scriptMode && !source) {
22
+ source = ask('Enter Path to typescript main file:');
23
+ }
24
+ if (!newCommand && !scriptMode && !output) {
25
+ output = ask('Enter Output Path:');
26
+ }
27
+ if (!newCommand && scriptMode && !source) {
28
+ tsCode = askMultiline('Write your typescript code here:');
29
+ }
30
+ run({ source, output, scriptMode, tsCode, parse: parseAstJsonNative });
31
+ })();
@@ -0,0 +1,37 @@
1
+ // Self-hosted parser: spawns the tsparser Go tool (tsparser/main.go) to parse
2
+ // TypeScript into the normalized JSON AST. Fully transpilable — used by the
3
+ // native compiler binary instead of the typescript npm package.
4
+ import * as path from 'node:path';
5
+ import * as fs from 'node:fs';
6
+ import * as childProcess from 'node:child_process';
7
+ export function parseAstJsonNative(code) {
8
+ const exe = findTsparser();
9
+ const result = childProcess.spawnSync(exe, [], { input: code, encoding: 'utf-8' });
10
+ if (result.status !== 0) {
11
+ throw new Error('tsparser failed: ' + result.stderr);
12
+ }
13
+ return JSON.parse(result.stdout);
14
+ }
15
+ // Locates the tsparser binary: TYPENATIVE_TSPARSER env var, next to this
16
+ // executable, or in a sibling tsparser/ directory (dev checkout layout).
17
+ function findTsparser() {
18
+ const exeName = platformIsWindows() ? 'tsparser.exe' : 'tsparser';
19
+ const envPath = process.env.TYPENATIVE_TSPARSER;
20
+ if (envPath && fs.existsSync(envPath))
21
+ return envPath;
22
+ const thisDir = path.dirname(path.resolve(process.argv[0]));
23
+ const candidates = [
24
+ path.join(thisDir, exeName),
25
+ path.join(thisDir, 'tsparser', exeName),
26
+ path.join(thisDir, '..', 'tsparser', exeName),
27
+ path.join(thisDir, '..', '..', 'tsparser', exeName)
28
+ ];
29
+ for (const candidate of candidates) {
30
+ if (fs.existsSync(candidate))
31
+ return candidate;
32
+ }
33
+ throw new Error('tsparser binary not found; set TYPENATIVE_TSPARSER');
34
+ }
35
+ function platformIsWindows() {
36
+ return process.platform === 'win32';
37
+ }
@@ -0,0 +1,110 @@
1
+ // Node-side AST adapter: converts a `typescript` npm package AST into the same
2
+ // normalized JSON shape that the `tsparser` Go tool emits
3
+ // (tsparser/main.go). The transpiler consumes this shape, so dev-CLI output and
4
+ // self-hosted (Go parser) output are field-compatible.
5
+ //
6
+ // Node-only module: imports the `typescript` package directly and is injected
7
+ // into the transpiler by the Node CLI wrapper (src/cli.ts). The self-hosted
8
+ // binary uses src/parse-native.ts instead.
9
+ import ts from 'typescript';
10
+ // Internal ts.Node bookkeeping properties that must not leak into the JSON
11
+ const SKIP_PROPS = new Set([
12
+ 'pos',
13
+ 'end',
14
+ 'flags',
15
+ 'modifierFlagsCache',
16
+ 'transformFlags',
17
+ 'parent',
18
+ 'original',
19
+ 'symbol',
20
+ 'id',
21
+ 'jsDoc',
22
+ 'flowNode',
23
+ 'contextFlags',
24
+ 'parserContextFlags',
25
+ 'excludeFlags',
26
+ 'modifierFlags',
27
+ 'flowId',
28
+ 'symbolCount',
29
+ 'identifierCount',
30
+ 'nodeArrayCache',
31
+ 'parsedByParser',
32
+ 'autoGenerateKind',
33
+ 'autoGenerateId',
34
+ 'emitNode',
35
+ 'localSymbol',
36
+ // 'text'/'escapedText' are only emitted for token-like kinds (TEXT_KINDS);
37
+ // SourceFile.text would otherwise leak the whole source into the AST
38
+ 'text',
39
+ 'escapedText',
40
+ 'endOfFileToken',
41
+ 'externalModuleIndicator',
42
+ 'commonJsModuleIndicator',
43
+ 'localJsxNamespace',
44
+ 'localJsxFactory',
45
+ 'localJsxFragmentFactory'
46
+ ]);
47
+ const TEXT_KINDS = new Set([
48
+ ts.SyntaxKind.Identifier,
49
+ ts.SyntaxKind.PrivateIdentifier,
50
+ ts.SyntaxKind.StringLiteral,
51
+ ts.SyntaxKind.NumericLiteral,
52
+ ts.SyntaxKind.BigIntLiteral,
53
+ ts.SyntaxKind.NoSubstitutionTemplateLiteral,
54
+ ts.SyntaxKind.RegularExpressionLiteral,
55
+ ts.SyntaxKind.TemplateHead,
56
+ ts.SyntaxKind.TemplateMiddle,
57
+ ts.SyntaxKind.TemplateTail,
58
+ ts.SyntaxKind.JsxText
59
+ ]);
60
+ // Reverse enum map preferring canonical names over aliases
61
+ // (ts.SyntaxKind[8] is "FirstLiteralToken", the alias of NumericLiteral)
62
+ const KIND_NAMES = (() => {
63
+ const names = {};
64
+ for (const key of Object.keys(ts.SyntaxKind)) {
65
+ const val = ts.SyntaxKind[key];
66
+ if (typeof val === 'number' && names[val] === undefined)
67
+ names[val] = key;
68
+ }
69
+ return names;
70
+ })();
71
+ function isNode(v) {
72
+ return typeof v === 'object' && v !== null && typeof v.kind === 'number' && typeof v.pos === 'number';
73
+ }
74
+ function convertNode(node) {
75
+ const obj = { kind: KIND_NAMES[node.kind] ?? String(node.kind) };
76
+ if (TEXT_KINDS.has(node.kind)) {
77
+ obj.text = node.text ?? node.escapedText;
78
+ }
79
+ for (const key of Object.keys(node)) {
80
+ if (SKIP_PROPS.has(key))
81
+ continue;
82
+ const v = node[key];
83
+ if (typeof v === 'function')
84
+ continue;
85
+ if (v === null || v === undefined)
86
+ continue;
87
+ if (isNode(v)) {
88
+ obj[key] = convertNode(v);
89
+ }
90
+ else if (Array.isArray(v)) {
91
+ const arr = v.filter((e) => isNode(e)).map((e) => convertNode(e));
92
+ if (arr.length > 0)
93
+ obj[key] = arr;
94
+ }
95
+ else if (typeof v === 'number' && key === 'operator') {
96
+ // unary operator: emit the kind name (matches the Go producer)
97
+ obj[key] = KIND_NAMES[v] ?? String(v);
98
+ }
99
+ else if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
100
+ if (key !== 'kind')
101
+ obj[key] = v;
102
+ }
103
+ }
104
+ return obj;
105
+ }
106
+ // Parse TypeScript source into the normalized JSON AST shape.
107
+ export function parseAstJson(code) {
108
+ const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
109
+ return convertNode(sourceFile);
110
+ }