typenative 0.1.2 → 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 +29 -20
- package/bin/index.js +242 -204
- package/bin/parse-native.js +3 -2
- package/bin/parse-node.js +18 -2
- package/bin/transpiler.js +891 -34
- package/conformance.md +17 -17
- package/package.json +1 -1
- package/tsparser/main.go +37 -3
- package/tsparser/tsparser.exe +2 -2
- package/types/typenative-npm.d.ts +7 -3
- package/types/typenative.d.ts +446 -53
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
|
-
"
|
|
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
|
|
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`, `
|
|
142
|
-
| `Object` | `assign`, `keys`/`values`/`entries`, spread of records → Go maps
|
|
143
|
-
| `String` | `split`/`slice`/`replace`, `padStart`/`padEnd`, `repeat`, search
|
|
144
|
-
| `Number`/`Math` | `parseInt`/`parseFloat`, `toFixed`/`toString(radix)`, `isInteger`/`isNaN`, `min`/`max
|
|
145
|
-
| `Map`/`Set` | Full `set`/`get`/`has`/`delete`/`size`
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
22
|
-
|
|
47
|
+
const result = childProcess.spawnSync(name, args, { stdio: 'inherit' });
|
|
48
|
+
return result.status;
|
|
23
49
|
}
|
|
24
50
|
export function createProject(projectName, installDependencies) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
searchDir = parent;
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
/* no package.json here, keep walking up */
|
|
207
241
|
}
|
|
208
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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
|
|
package/bin/parse-native.js
CHANGED
|
@@ -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
|
|
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
|
}
|