typenative 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -32,7 +32,7 @@ npx typenative --source hello.ts --script
32
32
  ## Requirements
33
33
 
34
34
  - [Node.js v24](https://nodejs.org/en) or newer.
35
- - [Go 1.26](https://go.dev/doc/install) or newer.
35
+ - [Go 1.26](https://go.dev/doc/install) or newer
36
36
 
37
37
  ## Install
38
38
 
@@ -105,12 +105,15 @@ Scaffolded `tsconfig.json` targets the TypeNative type surface:
105
105
  {
106
106
  "compilerOptions": {
107
107
  "target": "es2020",
108
- "strict": true,
109
- "types": ["typenative", "typenative/go", "typenative/npm"]
108
+ "lib": [],
109
+ "types": ["typenative", "typenative/go", "typenative/npm"],
110
+ "strict": true
110
111
  }
111
112
  }
112
113
  ```
113
114
 
115
+ `lib: []` disables the standard TypeScript libraries, so `typenative`'s own declarations are the entire type surface — the compiler only accepts what actually transpiles.
116
+
114
117
  ## CLI reference
115
118
 
116
119
  | Flag | Mode | Description |
@@ -128,24 +131,30 @@ TypeNative supports a growing subset of TypeScript, not the whole language. The
128
131
 
129
132
  **Language highlights that work today:**
130
133
 
131
- - `let`/`const`, functions, arrow functions, closures, recursion, rest/spread, default and optional params, destructured params.
132
- - Classes (inheritance, static members, getters, parameter properties, abstract, `implements`), interfaces, type aliases, generics, enums (numeric + string).
133
- - `if`/`switch` (incl. fallthrough), `for`/`for...of`/`while`/`do...while`, `try`/`catch`/`finally` (`throw` → `panic`, catch via `defer`/`recover`), ternaries, optional chaining, nullish coalescing, `in`, `typeof`, `instanceof`.
134
- - `async`/`await`, generators, custom iterators, tagged templates.
134
+ - `let`/`const`, functions, arrow functions, closures, recursion, rest/spread, default and optional params, destructured params, `arguments`.
135
+ - Classes (inheritance, static members/blocks, getters/setters, parameter properties, abstract, `implements`, decorators on classes and methods), interfaces, type aliases, generics (constraints, defaults), namespaces, enums (numeric, string, computed, `const`), tuples.
136
+ - `if`/`switch` (incl. fallthrough), `for`/`for...of`/`for...in`/`while`/`do...while`, labeled loops, `try`/`catch`/`finally` (`throw` → `panic`, catch via `defer`/`recover`), ternaries, optional chaining, nullish coalescing (`??`/`??=`), `in`, `typeof`, `instanceof`, `delete`, logical assignment.
137
+ - `async`/`await`, generators and async generators (`for await`), custom iterators (`*[Symbol.iterator]()`), tagged templates (`String.raw`), `using` (`[Symbol.dispose]`), `new.target`, `import.meta.url`, `import = require` / `export =`, dynamic `import()`.
138
+ - Type system: literal and template-literal types, union/intersection types, conditional types with `infer`, `keyof`/mapped/indexed-access types, utility types (`Partial`, `Readonly`, `Pick`, `Omit`, `Required`, `NonNullable`, `Record`), `as const`, `satisfies`, type guards (`p is T`), assertion functions.
139
+ - Runtime values: `bigint` literals (`2n ** 64n`) and `symbol` keys.
140
+ - JSX: `.tsx` files render to HTML strings.
135
141
 
136
142
  **Standard library that works today:**
137
143
 
138
- | Area | What to use |
139
- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
- | Console | `console.log`, `assert` (→ `panic` on failure) |
141
- | `Array` | `push`/`pop`, `slice`/`splice`, `map`/`filter`/`reduce`, `find`/`some`/`every`, `sort` with comparator, `flat`/`flatMap`, `at`, `entries`, `join`, `reverse`, `shift`/`unshift` |
142
- | `Object` | `assign`, `keys`/`values`/`entries`, spread of records → Go maps |
143
- | `String` | `split`/`slice`/`replace`, `padStart`/`padEnd`, `repeat`, search/compare, char codes, unicode |
144
- | `Number`/`Math` | `parseInt`/`parseFloat`, `toFixed`/`toString(radix)`, `isInteger`/`isNaN`, `min`/`max`/`random` |
145
- | `Map`/`Set` | Full `set`/`get`/`has`/`delete`/`size` plus `for...of` iteration (→ Go maps) |
146
- | `JSON` | `parse` / `stringify` |
147
- | `RegExp` | Literals + `new RegExp()`, `test()`, `exec()` with groups, `i`/`m` flags (backreferences, named groups, stateful `/g` loops and lookbehind are not supported — Go `regexp` limitation) |
148
- | `process` | `argv` (→ `os.Args`), `platform` (→ `runtime.GOOS`), `env` |
144
+ | Area | What to use |
145
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
146
+ | Console | `console.log`/`error`/`warn`, `console.time`/`timeEnd`, `assert` (→ `panic` on failure) |
147
+ | `Array` | `push`/`pop`, `shift`/`unshift`, `slice`/`splice`/`concat`, `map`/`filter`/`reduce`/`reduceRight`, `find`/`findLast`/`findIndex`/`findLastIndex`, `some`/`every`/`forEach`/`includes`/`indexOf`/`lastIndexOf`, `sort` with comparator, `flat`/`flatMap`, `fill`, `at`, `entries`, `join`, `reverse`, change-by-copy (`toSorted`/`toReversed`/`toSpliced`/`with`/`copyWithin`), `Array.from`, `Array.isArray`, `new Array<T>(n)` |
148
+ | `Object` | `assign`, `keys`/`values`/`entries`, `fromEntries`, `hasOwn`, `defineProperty`, `getOwnPropertyNames`, integrity (`freeze`/`seal`/`preventExtensions` + `isFrozen`/`isSealed`/`isExtensible`), spread of records → Go maps |
149
+ | `String` | `split`/`slice`/`replace`/`replaceAll`, `padStart`/`padEnd`, `repeat`, `match`/`matchAll`/`search`, `includes`/`startsWith`/`endsWith`, `indexOf`/`lastIndexOf`, char codes (`charAt`/`charCodeAt`/`fromCharCode`), `at`, `trim`/`trimStart`/`trimEnd`, `normalize`, `localeCompare`, `String.raw` |
150
+ | `Number`/`Math` | `parseInt`/`parseFloat`, `toFixed`/`toPrecision`/`toString(radix)`, `Number.isInteger`/`isNaN`/`isFinite` + `MAX_SAFE_INTEGER` & friends, full `Math` (trig, hyperbolic, `log*`, `cbrt`/`hypot`, `min`/`max` incl. spread, constants) |
151
+ | `Map`/`Set` | Full `set`/`get`/`has`/`delete`/`clear`/`size`, `forEach`, `keys`/`values`, `for...of` iteration (→ ordered Go maps), Set operations (`union`/`intersection`/`difference`/`symmetricDifference`/`isSubsetOf`/`isSupersetOf`/`isDisjointFrom`), `WeakMap`/`WeakSet` |
152
+ | `Promise` | `new Promise` (resolve + reject), `then`/`catch`/`finally`, `resolve`/`reject`, `all`/`race`/`allSettled`/`any` (→ goroutines over channels) |
153
+ | `Date` | `new Date()` / `new Date(ms)`, `getTime`/`valueOf`, local + UTC getters, `toISOString`/`toDateString`/`toTimeString`, `Date.now()`, `Date.UTC()` |
154
+ | `JSON` | `parse` / `stringify` |
155
+ | `RegExp` | Literals + `new RegExp()`, `test()`, `exec()` with named groups (`.groups`), flags `i`/`m`/`s`/`g`/`y`/`u`, stateful `lastIndex` for `/g` loops, `source`/`flags`/`global`/... properties. Backreferences, lookahead and lookbehind run on a small backtracking engine (RE2 does not support them) |
156
+ | Globals | `setTimeout`/`clearTimeout`, `setInterval`/`clearInterval`, `queueMicrotask`, `structuredClone`, `globalThis`, `encodeURIComponent`/`decodeURIComponent`, `isNaN`/`isFinite`, `NaN`/`Infinity` |
157
+ | `process` | `argv` (→ `os.Args`), `platform` (→ Node-style names), `env`, `cwd`, `exit` |
149
158
 
150
159
  **Node built-ins (import with the `node:` prefix, mapped to Go helpers):**
151
160
 
@@ -160,7 +169,7 @@ fs.readFileSync('file.txt', 'utf-8');
160
169
  childProcess.spawnSync('go', ['version'], { encoding: 'utf-8' });
161
170
  ```
162
171
 
163
- Supported: `node:path` (`join`, `dirname`, `basename`, `extname`, `resolve`), `node:fs` (`read/write/append/exists/mkdir/readdir/copy/rm`), `node:os` (`platform`, `homedir`, `tmpdir`), `node:child_process` (sync `exec`/`execSync`/`spawnSync`/`spawnInherit` shape). `node:readline.question()` exists as a TypeNative sync extension for self-hosted prompts.
172
+ Supported: `node:path` (`join`, `dirname`, `basename`, `extname`, `resolve`), `node:fs` (`read`/`write`/`append`/`exists`/`mkdir`/`readdir`/`copy`/`rm`/`unlink`), `node:url` (`fileURLToPath`, `pathToFileURL`), `node:os` (`platform`, `homedir`, `tmpdir`), `node:child_process` (sync `exec`, `execSync`, and `spawnSync` shaped like Node's `{ stdout, stderr, status }`; pass `{ stdio: 'inherit' }` to run with the parent's stdio or `{ input }` to pipe stdin). `node:readline.question()` exists as a TypeNative sync extension for self-hosted prompts.
164
173
 
165
174
  **Direct Go access (import with the `go:` prefix):**
166
175
 
@@ -171,7 +180,7 @@ import { ToUpper } from 'go:strings';
171
180
  Println(Sprintf('hi %s', ToUpper('bob')));
172
181
  ```
173
182
 
174
- Supported: `go:fmt` (`Println`, `Sprintf`), `go:strings` (`ToUpper`, `ToLower`), `go:strconv` (`FormatBool`).
183
+ Any Go package can be imported this way (`import { X } from 'go:pkg'` → `pkg.X`); the typed subset declared for typechecking is `go:fmt` (`Println`, `Sprintf`), `go:strings` (`ToUpper`, `ToLower`), `go:strconv` (`FormatBool`).
175
184
 
176
185
  **npm packages:**
177
186
 
package/bin/index.js CHANGED
@@ -12,241 +12,279 @@ import * as childProcess from 'node:child_process';
12
12
  import { transpileToNative } from './transpiler.js';
13
13
  // Run a command and capture its output. Uses Node's spawnSync under Node and a
14
14
  // Go helper (os/exec) once transpiled — both synchronous, same result shape.
15
+ // dist/go.mod + dist/tnvendor/x-text: golang.org/x/text's transform and unicode/norm
16
+ // packages, copied from GOROOT/src/vendor (no network needed)
17
+ function vendorXText() {
18
+ const goroot = runCommand('go', ['env', 'GOROOT']).stdout.trim();
19
+ const source = path.join(goroot, 'src', 'vendor', 'golang.org', 'x', 'text');
20
+ for (const pkg of ['transform', path.join('unicode', 'norm')]) {
21
+ const target = path.join('dist', 'tnvendor', 'x-text', pkg);
22
+ fs.mkdirSync(target, { recursive: true });
23
+ for (const file of fs.readdirSync(path.join(source, pkg))) {
24
+ if (file.endsWith('.go') && !file.endsWith('_test.go')) {
25
+ fs.writeFileSync(
26
+ path.join(target, file),
27
+ fs.readFileSync(path.join(source, pkg, file), 'utf-8')
28
+ );
29
+ }
30
+ }
31
+ }
32
+ fs.writeFileSync(
33
+ path.join('dist', 'tnvendor', 'x-text', 'go.mod'),
34
+ 'module golang.org/x/text\n\ngo 1.23\n'
35
+ );
36
+ fs.writeFileSync(
37
+ path.join('dist', 'go.mod'),
38
+ 'module tnapp\n\ngo 1.23\n\nrequire golang.org/x/text v0.0.0\n\nreplace golang.org/x/text => ./tnvendor/x-text\n'
39
+ );
40
+ }
15
41
  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 };
42
+ const result = childProcess.spawnSync(name, args, { encoding: 'utf-8' });
43
+ return { stdout: result.stdout, stderr: result.stderr, status: result.status };
18
44
  }
19
45
  // Run a command with the parent's stdio, returns the exit code.
20
46
  export function runInherit(name, args = []) {
21
- const result = childProcess.spawnSync(name, args, { stdio: 'inherit' });
22
- return result.status;
47
+ const result = childProcess.spawnSync(name, args, { stdio: 'inherit' });
48
+ return result.status;
23
49
  }
24
50
  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);
42
- }
43
- console.log('Dependencies installed successfully!');
51
+ fs.mkdirSync(projectName, { recursive: true });
52
+ fs.writeFileSync(
53
+ path.join(projectName, 'main.ts'),
54
+ `// Write your TypeScript code here\nconsole.log('Hello, World!');\n`
55
+ );
56
+ fs.writeFileSync(path.join(projectName, 'tsconfig.json'), getTsConfig());
57
+ fs.writeFileSync(path.join(projectName, 'package.json'), getPackageJson(projectName));
58
+ fs.writeFileSync(path.join(projectName, '.gitignore'), getGitIgnore());
59
+ fs.writeFileSync(path.join(projectName, 'README.md'), getReadMe(projectName));
60
+ console.log(`Project "${projectName}" created successfully!`);
61
+ if (installDependencies) {
62
+ console.log('Installing dependencies...');
63
+ // npm --prefix avoids needing cwd support in the subprocess binding
64
+ const result = runCommand('npm', ['install', '--prefix', projectName]);
65
+ if (result.stdout) console.log(result.stdout.trim());
66
+ if (result.status !== 0) {
67
+ if (result.stderr) console.error(result.stderr.trim());
68
+ process.exit(result.status);
44
69
  }
70
+ console.log('Dependencies installed successfully!');
71
+ }
45
72
  }
46
73
  export function run(opts) {
47
- const sourcePath = opts.tsCode ? null : opts.source;
48
- const tsCode = opts.tsCode ? opts.tsCode : fs.readFileSync(sourcePath, 'utf-8');
49
- const sourceDir = sourcePath ? path.dirname(path.resolve(sourcePath)) : null;
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 */
70
- }
71
- }
72
- return null;
73
- }
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';
93
- const exePath = `dist/${exeName}`;
94
- fs.mkdirSync('dist', { recursive: true });
95
- // Clean up stale Go files from previous runs before writing new ones
96
- for (const existing of fs.readdirSync('dist')) {
97
- if (existing.endsWith('.go'))
98
- fs.rmSync(`dist/${existing}`);
99
- }
100
- fs.writeFileSync('dist/code.go', transpileResult.main);
101
- const goFiles = ['dist/code.go'];
102
- for (const [filename, content] of transpileResult.files) {
103
- fs.writeFileSync(`dist/${filename}`, content);
104
- goFiles.push(`dist/${filename}`);
105
- }
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);
112
- }
113
- if (opts.scriptMode) {
114
- process.exit(runInherit(exePath));
115
- }
116
- else if (opts.output) {
117
- fs.copyFileSync(exePath, opts.output);
118
- console.log(`Created native executable at: ${opts.output}`);
119
- }
74
+ const sourcePath = opts.tsCode ? null : opts.source;
75
+ const tsCode = opts.tsCode ? opts.tsCode : fs.readFileSync(sourcePath, 'utf-8');
76
+ const sourceDir = sourcePath ? path.dirname(path.resolve(sourcePath)) : null;
77
+ const transpileOptions = { parse: opts.parse, tsx: !!sourcePath && sourcePath.endsWith('.tsx') };
78
+ if (sourceDir) {
79
+ transpileOptions.readFile = (specifier, fromDir) => {
80
+ const baseDir = fromDir ?? sourceDir;
81
+ // Relative or absolute path → resolve from baseDir
82
+ if (specifier.startsWith('.') || specifier.startsWith('/')) {
83
+ // ES convention: ./x.js may refer to x.ts
84
+ const tsSpecifier = specifier.endsWith('.js')
85
+ ? specifier.replace(/\.js$/, '.ts')
86
+ : specifier;
87
+ const tsxSpecifier = specifier.endsWith('.js')
88
+ ? specifier.replace(/\.js$/, '.tsx')
89
+ : specifier + '.tsx';
90
+ for (const candidate of [tsSpecifier + '.ts', tsxSpecifier, tsSpecifier, specifier]) {
91
+ try {
92
+ const fullPath = path.resolve(baseDir, candidate);
93
+ return {
94
+ content: fs.readFileSync(fullPath, 'utf-8'),
95
+ dir: path.dirname(fullPath),
96
+ tsx: fullPath.endsWith('.tsx')
97
+ };
98
+ } catch {
99
+ /* not found */
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+ // npm package — walk up from baseDir looking for node_modules/<name>
105
+ const resolved = resolveNpmPackage(baseDir, specifier);
106
+ if (!resolved) return null;
107
+ let { content, dir } = resolved;
108
+ // Normalize CommonJS to ES module syntax
109
+ if (
110
+ !content.includes('export ') &&
111
+ (content.includes('module.exports') || content.includes('exports.'))
112
+ ) {
113
+ content = normalizeCjsContent(content);
114
+ }
115
+ // Inject types from a local ambient .d.ts if available
116
+ const typed = tryInjectDtsTypes(content, specifier, sourceDir);
117
+ if (typed) content = typed;
118
+ return { content, dir };
119
+ };
120
+ }
121
+ const transpileResult = transpileToNative(tsCode, transpileOptions);
122
+ const exeName = platform() === 'win32' ? 'native.exe' : 'native';
123
+ const exePath = `dist/${exeName}`;
124
+ fs.mkdirSync('dist', { recursive: true });
125
+ // Clean up stale Go files from previous runs before writing new ones
126
+ for (const existing of fs.readdirSync('dist')) {
127
+ if (existing.endsWith('.go') || existing === 'go.mod' || existing === 'go.sum')
128
+ fs.rmSync(`dist/${existing}`);
129
+ }
130
+ fs.writeFileSync('dist/code.go', transpileResult.main);
131
+ const goFiles = ['dist/code.go'];
132
+ for (const [filename, content] of transpileResult.files) {
133
+ fs.writeFileSync(`dist/${filename}`, content);
134
+ goFiles.push(`dist/${filename}`);
135
+ }
136
+ // golang.org/x/text (String.normalize) comes from the copy vendored in GOROOT
137
+ const xTextImport = /^import (\w+ )?"golang\.org\/x\/text\//m;
138
+ let usesXText = xTextImport.test(transpileResult.main);
139
+ for (const content of transpileResult.files.values()) {
140
+ if (xTextImport.test(content)) usesXText = true;
141
+ }
142
+ if (usesXText) vendorXText();
143
+ const buildResult = usesXText
144
+ ? runCommand('go', [
145
+ 'build',
146
+ '-C',
147
+ 'dist',
148
+ '-o',
149
+ exeName,
150
+ ...goFiles.map((f) => f.slice('dist/'.length))
151
+ ])
152
+ : runCommand('go', ['build', '-o', exePath, ...goFiles]);
153
+ if (buildResult.status !== 0) {
154
+ console.error('go build failed:');
155
+ if (buildResult.stderr) console.error(buildResult.stderr.trim());
156
+ process.exit(1);
157
+ }
158
+ if (opts.scriptMode) {
159
+ process.exit(runInherit(exePath));
160
+ } else if (opts.output) {
161
+ fs.copyFileSync(exePath, opts.output);
162
+ console.log(`Created native executable at: ${opts.output}`);
163
+ }
120
164
  }
121
165
  function normalizeCjsContent(code) {
122
- code = code.replace(/['"]use strict['"];?\n?/g, '');
123
- code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
124
- return code;
166
+ code = code.replace(/['"]use strict['"];?\n?/g, '');
167
+ code = code.replace(
168
+ /(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g,
169
+ 'export function $1('
170
+ );
171
+ return code;
125
172
  }
126
173
  function tryInjectDtsTypes(jsContent, packageName, searchDir) {
127
- if (!searchDir)
128
- return null;
129
- // Look for *.d.ts files in searchDir that declare the module
130
- let dtsBody = null;
131
- try {
132
- const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
133
- for (const file of fs.readdirSync(searchDir)) {
134
- if (!file.endsWith('.d.ts'))
135
- continue;
136
- const content = fs.readFileSync(path.join(searchDir, file), 'utf-8');
137
- const match = content.match(new RegExp(`declare module ['"]${escaped}['"][^{]*\\{([\\s\\S]*?)\\}`));
138
- if (match) {
139
- dtsBody = match[1];
140
- break;
141
- }
142
- }
174
+ if (!searchDir) return null;
175
+ // Look for *.d.ts files in searchDir that declare the module
176
+ let dtsBody = null;
177
+ try {
178
+ const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
179
+ for (const file of fs.readdirSync(searchDir)) {
180
+ if (!file.endsWith('.d.ts')) continue;
181
+ const content = fs.readFileSync(path.join(searchDir, file), 'utf-8');
182
+ const match = content.match(
183
+ new RegExp(`declare module ['"]${escaped}['"][^{]*\\{([\\s\\S]*?)\\}`)
184
+ );
185
+ if (match) {
186
+ dtsBody = match[1];
187
+ break;
188
+ }
143
189
  }
144
- catch {
145
- return null;
146
- }
147
- if (!dtsBody)
148
- return null;
149
- // Extract typed function signatures from the .d.ts module body
150
- const signatures = new Map();
151
- const sigRegex = /export function (\w+)\(([^)]*)\)\s*:\s*([^\n;]+)/g;
152
- for (const m of dtsBody.matchAll(sigRegex)) {
153
- signatures.set(m[1], { params: m[2].trim(), returnType: m[3].trim() });
154
- }
155
- if (signatures.size === 0)
156
- return null;
157
- // Replace untyped signatures in the normalized JS with typed ones from .d.ts
158
- return jsContent.replace(/export function (\w+)\s*\(([^)]*)\)/g, (match, name) => {
159
- const sig = signatures.get(name);
160
- if (!sig)
161
- return match;
162
- return `export function ${name}(${sig.params}): ${sig.returnType}`;
163
- });
190
+ } catch {
191
+ return null;
192
+ }
193
+ if (!dtsBody) return null;
194
+ // Extract typed function signatures from the .d.ts module body
195
+ const signatures = new Map();
196
+ const sigRegex = /export function (\w+)\(([^)]*)\)\s*:\s*([^\n;]+)/g;
197
+ for (const m of dtsBody.matchAll(sigRegex)) {
198
+ signatures.set(m[1], { params: m[2].trim(), returnType: m[3].trim() });
199
+ }
200
+ if (signatures.size === 0) return null;
201
+ // Replace untyped signatures in the normalized JS with typed ones from .d.ts
202
+ return jsContent.replace(/export function (\w+)\s*\(([^)]*)\)/g, (match, name) => {
203
+ const sig = signatures.get(name);
204
+ if (!sig) return match;
205
+ return `export function ${name}(${sig.params}): ${sig.returnType}`;
206
+ });
164
207
  }
165
208
  function resolveNpmPackage(fromDir, packageName) {
166
- // Walk up the directory tree looking for node_modules/<packageName>
167
- let searchDir = fromDir;
168
- while (true) {
169
- const pkgDir = path.join(searchDir, 'node_modules', packageName);
170
- const pkgJsonPath = path.join(pkgDir, 'package.json');
171
- try {
172
- const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
173
- // Build candidate entry points: TypeScript preferred, JavaScript as fallback
174
- const tsCandidates = [];
175
- const jsCandidates = [];
176
- for (const field of ['source', 'main', 'module']) {
177
- const entry = pkgJson[field];
178
- if (!entry)
179
- continue;
180
- if (entry.endsWith('.ts'))
181
- tsCandidates.push(entry);
182
- else if (entry.endsWith('.js')) {
183
- tsCandidates.push(entry.replace(/\.js$/, '.ts'));
184
- jsCandidates.push(entry);
185
- }
186
- }
187
- tsCandidates.push('index.ts', 'src/index.ts');
188
- jsCandidates.push('index.js', 'src/index.js');
189
- const candidates = [...tsCandidates, ...jsCandidates];
190
- for (const candidate of candidates) {
191
- const fullPath = path.resolve(pkgDir, candidate);
192
- try {
193
- return { content: fs.readFileSync(fullPath, 'utf-8'), dir: path.dirname(fullPath) };
194
- }
195
- catch {
196
- /* try next candidate */
197
- }
198
- }
209
+ // Walk up the directory tree looking for node_modules/<packageName>
210
+ let searchDir = fromDir;
211
+ while (true) {
212
+ const pkgDir = path.join(searchDir, 'node_modules', packageName);
213
+ const pkgJsonPath = path.join(pkgDir, 'package.json');
214
+ try {
215
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
216
+ // Build candidate entry points: TypeScript preferred, JavaScript as fallback
217
+ const tsCandidates = [];
218
+ const jsCandidates = [];
219
+ for (const field of ['source', 'main', 'module']) {
220
+ const entry = pkgJson[field];
221
+ if (!entry) continue;
222
+ if (entry.endsWith('.ts')) tsCandidates.push(entry);
223
+ else if (entry.endsWith('.js')) {
224
+ tsCandidates.push(entry.replace(/\.js$/, '.ts'));
225
+ jsCandidates.push(entry);
199
226
  }
200
- catch {
201
- /* no package.json here, keep walking up */
227
+ }
228
+ tsCandidates.push('index.ts', 'src/index.ts');
229
+ jsCandidates.push('index.js', 'src/index.js');
230
+ const candidates = [...tsCandidates, ...jsCandidates];
231
+ for (const candidate of candidates) {
232
+ const fullPath = path.resolve(pkgDir, candidate);
233
+ try {
234
+ return { content: fs.readFileSync(fullPath, 'utf-8'), dir: path.dirname(fullPath) };
235
+ } catch {
236
+ /* try next candidate */
202
237
  }
203
- const parent = path.dirname(searchDir);
204
- if (parent === searchDir)
205
- break; // filesystem root
206
- searchDir = parent;
238
+ }
239
+ } catch {
240
+ /* no package.json here, keep walking up */
207
241
  }
208
- return null;
242
+ const parent = path.dirname(searchDir);
243
+ if (parent === searchDir) break; // filesystem root
244
+ searchDir = parent;
245
+ }
246
+ return null;
209
247
  }
210
248
  function getPackageJson(projectName) {
211
- const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
212
- const pckg = {
213
- name: projectName,
214
- version: '1.0.0',
215
- scripts: {
216
- execute: 'npx typenative --source main.ts --script',
217
- build: `npx typenative --source main.ts --output bin/${exeName}`
218
- },
219
- devDependencies: {
220
- typenative: '^0.1.1'
221
- }
222
- };
223
- return JSON.stringify(pckg, null, 2);
249
+ const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
250
+ const pckg = {
251
+ name: projectName,
252
+ version: '1.0.0',
253
+ scripts: {
254
+ execute: 'npx typenative --source main.ts --script',
255
+ build: `npx typenative --source main.ts --output bin/${exeName}`
256
+ },
257
+ devDependencies: {
258
+ typenative: '^0.1.3'
259
+ }
260
+ };
261
+ return JSON.stringify(pckg, null, 2);
224
262
  }
225
263
  function getTsConfig() {
226
- const tsConfig = {
227
- include: ['**/*.ts'],
228
- compilerOptions: {
229
- target: 'es2020',
230
- lib: [],
231
- types: ['typenative', 'typenative/go', 'typenative/npm'],
232
- rootDir: '.',
233
- strict: true,
234
- noImplicitAny: true,
235
- allowSyntheticDefaultImports: true
236
- }
237
- };
238
- return JSON.stringify(tsConfig, null, 2);
264
+ const tsConfig = {
265
+ include: ['**/*.ts'],
266
+ compilerOptions: {
267
+ target: 'es2020',
268
+ lib: [],
269
+ types: ['typenative', 'typenative/go', 'typenative/npm'],
270
+ rootDir: '.',
271
+ strict: true,
272
+ noImplicitAny: true,
273
+ allowSyntheticDefaultImports: true
274
+ }
275
+ };
276
+ return JSON.stringify(tsConfig, null, 2);
239
277
  }
240
278
  function getGitIgnore() {
241
- return `# TypeNative generated files
279
+ return `# TypeNative generated files
242
280
  node_modules/
243
281
  dist/
244
282
  bin/
245
283
  `;
246
284
  }
247
285
  function getReadMe(projectName) {
248
- const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
249
- return `# ${projectName}
286
+ const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
287
+ return `# ${projectName}
250
288
 
251
289
  This project was created using TypeNative, a tool to transpile TypeScript code to native Go code.
252
290
 
@@ -4,9 +4,10 @@
4
4
  import * as path from 'node:path';
5
5
  import * as fs from 'node:fs';
6
6
  import * as childProcess from 'node:child_process';
7
- export function parseAstJsonNative(code) {
7
+ export function parseAstJsonNative(code, tsx) {
8
8
  const exe = findTsparser();
9
- const result = childProcess.spawnSync(exe, [], { input: code, encoding: 'utf-8' });
9
+ const args = tsx ? ['--tsx'] : [];
10
+ const result = childProcess.spawnSync(exe, args, { input: code, encoding: 'utf-8' });
10
11
  if (result.status !== 0) {
11
12
  throw new Error('tsparser failed: ' + result.stderr);
12
13
  }