typenative 0.0.18 → 0.0.19

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.
@@ -24,6 +24,8 @@ jobs:
24
24
  with:
25
25
  go-version: '1.21'
26
26
  - run: npm ci
27
+ - run: npm ci
28
+ working-directory: test
27
29
  - run: npm test
28
30
 
29
31
  release:
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ 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.19] - 2026-03-07
9
+
10
+ ### Added
11
+
12
+ - Module/import support: `import { x } from './file'` transpiled to Go package imports for local TypeScript files
13
+ - Node.js built-in module imports: `import { join } from 'node:path'` and similar mapped to corresponding Go standard library packages
14
+ - npm package imports: `import { x } from 'pkg'` mapped to Go module imports via `typenative-npm.d.ts` type definitions
15
+ - Go package imports: `import { x } from 'go:package'` to import a Go library package
16
+ - Named exports: `export function` and `export const` declarations now transpiled correctly
17
+
8
18
  ## [0.0.18] - 2026-03-03
9
19
 
10
20
  ### Added
package/README.md CHANGED
@@ -49,37 +49,37 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
49
49
 
50
50
  **Control Flow**
51
51
 
52
- | Feature | Supported | Notes |
53
- | ------------------ | :-------: | ---------------------------------- |
54
- | If/Else statements | ✅ | Fully supported |
55
- | Switch statements | ✅ | Case and default statements |
56
- | For loops | ✅ | Standard `for` loops |
57
- | For...of loops | ✅ | Iteration over arrays |
58
- | While loops | ✅ | Transpiled to Go's `for` loops |
59
- | Do...while loops | ✅ | Implemented with conditional break |
52
+ | Feature | Supported | Notes |
53
+ | ------------------ | :-------: | ------------------------------------------------------ |
54
+ | If/Else statements | ✅ | Fully supported |
55
+ | Switch statements | ✅ | Case and default statements |
56
+ | For loops | ✅ | Standard `for` loops |
57
+ | For...of loops | ✅ | Iteration over arrays |
58
+ | While loops | ✅ | Transpiled to Go's `for` loops |
59
+ | Do...while loops | ✅ | Implemented with conditional break |
60
60
  | Try/Catch/Finally | ✅ | `throw` → `panic`; catch/finally via `defer`/`recover` |
61
61
 
62
62
  **Data Structures & Array Methods**
63
63
 
64
- | Feature | Supported | Notes |
65
- | -------------------------- | :-------: | ---------------------------------------------------------------- |
66
- | Arrays | ✅ | Basic array operations |
67
- | Array methods | ✅ | `push`, `join`, `slice` |
68
- | Higher-order array methods | ✅ | `.map()`, `.filter()`, `.some()`, `.find()` |
69
- | Method chaining | ✅ | Chaining array methods such as `.map(...).filter(...).join(...)` |
64
+ | Feature | Supported | Notes |
65
+ | -------------------------- | :-------: | ------------------------------------------------------------------------------------------ |
66
+ | Arrays | ✅ | Basic array operations |
67
+ | Array methods | ✅ | `push`, `join`, `slice` |
68
+ | Higher-order array methods | ✅ | `.map()`, `.filter()`, `.some()`, `.find()` |
69
+ | Method chaining | ✅ | Chaining array methods such as `.map(...).filter(...).join(...)` |
70
70
  | Map | ✅ | `Map<K, V>` → Go `map[K]V`; `.set()`, `.get()`, `.has()`, `.delete()`, `.clear()`, `.size` |
71
- | Set | ✅ | `Set<T>` → Go `map[T]struct{}`; `.add()`, `.has()`, `.delete()`, `.clear()`, `.size` |
71
+ | Set | ✅ | `Set<T>` → Go `map[T]struct{}`; `.add()`, `.has()`, `.delete()`, `.clear()`, `.size` |
72
72
 
73
73
  **Functions**
74
74
 
75
- | Feature | Supported | Notes |
76
- | ---------------------------- | :-------: | ------------------------------------------------------------ |
77
- | Function declarations | ✅ | Transpiled to Go functions |
78
- | Arrow functions | ✅ | Transpiled to anonymous functions |
79
- | Closures over mutable state | ✅ | Functions capturing and mutating outer variables |
80
- | Function types | ✅ | `() => number`, `(x: number) => string` as type annotations |
81
- | Generics (functions/classes) | ✅ | Type parameters via Go generics |
82
- | Default parameter values | ✅ | `function(x = defaultValue)` |
75
+ | Feature | Supported | Notes |
76
+ | ---------------------------- | :-------: | ----------------------------------------------------------- |
77
+ | Function declarations | ✅ | Transpiled to Go functions |
78
+ | Arrow functions | ✅ | Transpiled to anonymous functions |
79
+ | Closures over mutable state | ✅ | Functions capturing and mutating outer variables |
80
+ | Function types | ✅ | `() => number`, `(x: number) => string` as type annotations |
81
+ | Generics (functions/classes) | ✅ | Type parameters via Go generics |
82
+ | Default parameter values | ✅ | `function(x = defaultValue)` |
83
83
 
84
84
  **Classes & Interfaces**
85
85
 
@@ -145,10 +145,14 @@ TypeNative currently supports a focused subset of TypeScript syntax elements tha
145
145
  | test() | ✅ | Mapped to `regexp.MatchString` |
146
146
  | exec() | ✅ | Mapped to `regexp.FindStringSubmatch` |
147
147
 
148
- **Unsupported / Roadmap**
148
+ **Modules & Imports**
149
149
 
150
- | Feature | Supported | Notes |
151
- | --------------- | :-------: | -------------------------------- |
152
- | Modules/Imports | | `import` / `export` declarations |
150
+ | Feature | Supported | Notes |
151
+ | ------------------------ | :-------: | ------------------------------------------------------------ |
152
+ | Local file imports | | `import { x } from './file'` transpiled to Go package import |
153
+ | Node.js built-in imports | ✅ | `import { join } from 'node:path'` mapped to Go stdlib |
154
+ | Go package imports | ✅ | `import { x } from 'go:pkg'` |
155
+ | npm package imports | ✅ | `import { x } from 'pkg'` mapped to Go module imports |
156
+ | Named exports | ✅ | `export function` / `export const` declarations |
153
157
 
154
158
  TypeNative is currently in early development and new features are being added regularly. The goal for `1.0` release is for TypeNative to transpile itself.
package/TODO.md ADDED
@@ -0,0 +1,29 @@
1
+ **index.ts needs:**
2
+
3
+ | Feature | Example |
4
+ | ------------------------------- | ------------------------------------------------------------------- |
5
+ | Default imports | `import inquirer from 'inquirer'`, `import fs from 'fs-extra'` |
6
+ | `process` global | `process.argv`, `process.platform` |
7
+ | Array `findIndex` | `process.argv.findIndex(a => a === '--script')` |
8
+ | `JSON.parse` / `JSON.stringify` | `JSON.parse(fs.readFileSync(...))`, `JSON.stringify(pckg, null, 2)` |
9
+ | Named async IIFE | `(async function main() { ... })()` |
10
+
11
+ **transpiler.ts additionally needs:**
12
+
13
+ | Feature | Example |
14
+ | ----------------------------------- | --------------------------------------------------- |
15
+ | Default imports | `import ts from 'typescript'` |
16
+ | Spread in array literals | `[...importedPackages]`, `[...arr1, ...arr2]` |
17
+ | `for...of` with tuple destructuring | `for (const [funcName, fn] of Object.entries(...))` |
18
+ | `Object.entries()` | `Object.entries(mapping.functions)` |
19
+ | Arrow function IIFE | `(() => { ... })()` |
20
+ | Array `findIndex` | `parameters.findIndex(p => !!p.initializer)` |
21
+
22
+ **Priority order** (most blocking first):
23
+
24
+ 1. **Default imports** — used at the top of both files, nothing runs without this
25
+ 2. **Spread in array literals** — used throughout transpiler.ts for array construction
26
+ 3. **`for...of` with tuple destructuring + `Object.entries`** — used for iterating import mappings
27
+ 4. **`process` global** — core to CLI argument parsing in index.ts
28
+ 5. **IIFE** — the entire entry point of index.ts is an async IIFE
29
+ 6. **`findIndex`**, **`JSON.parse/stringify`** — utility methods used in both files
package/bin/index.js CHANGED
@@ -1,145 +1,281 @@
1
1
  #!/usr/bin/env node
2
2
  import inquirer from 'inquirer';
3
3
  import fs from 'fs-extra';
4
- import path from 'path';
4
+ import path from 'node:path';
5
5
  import { execa } from 'execa';
6
6
  import { transpileToNative } from './transpiler.js';
7
- import { fileURLToPath } from 'url';
7
+ import { fileURLToPath } from 'node:url';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = path.dirname(__filename);
10
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!');`
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!');
74
+ }
75
+ return;
51
76
  }
52
- ]);
53
- if (newCommand) {
54
- const projectName = answers.projectName.trim();
55
- await fs.ensureDir(projectName);
56
- await fs.writeFile(
57
- path.join(projectName, 'main.ts'),
58
- `// Write your TypeScript code here\nconsole.log('Hello, World!');\n`,
59
- { encoding: 'utf-8' }
60
- );
61
- await fs.writeFile(path.join(projectName, 'tsconfig.json'), getTsConfig(), {
62
- encoding: 'utf-8'
63
- });
64
- await fs.writeFile(path.join(projectName, 'package.json'), getPackageJson(projectName), {
65
- encoding: 'utf-8'
66
- });
67
- await fs.writeFile(path.join(projectName, '.gitignore'), getGitIgnore(), {
68
- encoding: 'utf-8'
69
- });
70
- await fs.writeFile(path.join(projectName, 'README.md'), getReadMe(projectName), {
71
- encoding: 'utf-8'
72
- });
73
- console.log(`Project "${projectName}" created successfully!`);
74
- if (answers.installDependencies) {
75
- console.log('Installing dependencies...');
76
- await execa('npm', ['install'], { cwd: projectName, stdio: 'inherit' });
77
- console.log('Dependencies installed successfully!');
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' });
81
+ 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
+ }
99
+ }
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
+ }
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 };
116
+ }
117
+ }
118
+ : undefined);
119
+ const exeName = process.platform === 'win32' ? 'native.exe' : 'native';
120
+ const exePath = `dist/${exeName}`;
121
+ await fs.ensureDir('dist');
122
+ // Clean up stale Go files from previous runs before writing new ones
123
+ for (const existing of await fs.readdir('dist')) {
124
+ if (existing.endsWith('.go'))
125
+ await fs.remove(`dist/${existing}`);
126
+ }
127
+ await fs.writeFile('dist/code.go', transpileResult.main, { encoding: 'utf-8' });
128
+ const goFiles = ['dist/code.go'];
129
+ for (const [filename, content] of transpileResult.files) {
130
+ await fs.writeFile(`dist/${filename}`, content, { encoding: 'utf-8' });
131
+ goFiles.push(`dist/${filename}`);
78
132
  }
79
- return;
80
- }
81
- const tsCode = answers.tsCode
82
- ? answers.tsCode
83
- : await fs.readFile(source ?? answers.path, { encoding: 'utf-8' });
84
- const nativeCode = transpileToNative(tsCode);
85
- const exeName = process.platform === 'win32' ? 'native.exe' : 'native';
86
- const exePath = `dist/${exeName}`;
87
- await fs.ensureDir('dist');
88
- await fs.writeFile('dist/code.go', nativeCode, { encoding: 'utf-8' });
89
- await execa('go', ['build', '-o', exePath, 'dist/code.go'], {
90
- stdio: 'inherit'
91
- });
92
- // await fs.remove('dist/code.go');
93
- if (scriptMode) {
94
- await execa(exePath, {
95
- stdio: 'inherit'
133
+ await execa('go', ['build', '-o', exePath, ...goFiles], {
134
+ stdio: 'inherit'
96
135
  });
97
- //await fs.remove(exePath);
98
- } else if (output || answers.output) {
99
- await fs.copy(exePath, output ?? answers.output, { overwrite: true });
100
- //await fs.remove(exePath);
101
- console.log(`Created native executable at: ${output ?? answers.output}`);
102
- }
136
+ // await fs.remove('dist/code.go');
137
+ if (scriptMode) {
138
+ await execa(exePath, {
139
+ stdio: 'inherit'
140
+ });
141
+ //await fs.remove(exePath);
142
+ }
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}`);
147
+ }
103
148
  })();
104
- function getPackageJson(projectName) {
105
- const exeName = process.platform === 'win32' ? `${projectName}.exe` : projectName;
106
- const pckg = {
107
- name: projectName,
108
- version: '1.0.0',
109
- scripts: {
110
- execute: 'npx typenative --source main.ts --script',
111
- build: `npx typenative --source main.ts --output bin/${exeName}`
112
- },
113
- devDependencies: {
114
- typenative: '^0.0.16'
149
+ function normalizeCjsContent(code) {
150
+ code = code.replace(/['"]use strict['"];?\n?/g, '');
151
+ code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
152
+ return code;
153
+ }
154
+ function tryInjectDtsTypes(jsContent, packageName, searchDir) {
155
+ if (!searchDir)
156
+ return null;
157
+ // Look for *.d.ts files in searchDir that declare the module
158
+ let dtsBody = null;
159
+ try {
160
+ const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
161
+ for (const file of fs.readdirSync(searchDir)) {
162
+ if (!file.endsWith('.d.ts'))
163
+ continue;
164
+ const content = fs.readFileSync(path.join(searchDir, file), 'utf-8');
165
+ const match = content.match(new RegExp(`declare module ['"]${escaped}['"][^{]*\\{([\\s\\S]*?)\\}`));
166
+ if (match) {
167
+ dtsBody = match[1];
168
+ break;
169
+ }
170
+ }
115
171
  }
116
- };
117
- return JSON.stringify(pckg, null, 2);
172
+ catch {
173
+ return null;
174
+ }
175
+ if (!dtsBody)
176
+ return null;
177
+ // Extract typed function signatures from the .d.ts module body
178
+ const signatures = new Map();
179
+ const sigRegex = /export function (\w+)\(([^)]*)\)\s*:\s*([^\n;]+)/g;
180
+ let m;
181
+ while ((m = sigRegex.exec(dtsBody)) !== null) {
182
+ signatures.set(m[1], { params: m[2].trim(), returnType: m[3].trim() });
183
+ }
184
+ if (signatures.size === 0)
185
+ return null;
186
+ // Replace untyped signatures in the normalized JS with typed ones from .d.ts
187
+ return jsContent.replace(/export function (\w+)\s*\(([^)]*)\)/g, (match, name) => {
188
+ const sig = signatures.get(name);
189
+ if (!sig)
190
+ return match;
191
+ return `export function ${name}(${sig.params}): ${sig.returnType}`;
192
+ });
118
193
  }
119
- function getTsConfig() {
120
- const tsConfig = {
121
- include: ['**/*.ts'],
122
- compilerOptions: {
123
- target: 'es2020',
124
- lib: [],
125
- types: ['./node_modules/typenative/types/typenative.d.ts'],
126
- rootDir: '.',
127
- strict: true,
128
- noImplicitAny: true
194
+ function resolveNpmPackage(fromDir, packageName) {
195
+ // Walk up the directory tree looking for node_modules/<packageName>
196
+ let searchDir = fromDir;
197
+ while (true) {
198
+ const pkgDir = path.join(searchDir, 'node_modules', packageName);
199
+ const pkgJsonPath = path.join(pkgDir, 'package.json');
200
+ try {
201
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
202
+ // Build candidate entry points: TypeScript preferred, JavaScript as fallback
203
+ const tsCandidates = [];
204
+ const jsCandidates = [];
205
+ for (const field of ['source', 'main', 'module']) {
206
+ const entry = pkgJson[field];
207
+ if (!entry)
208
+ continue;
209
+ if (entry.endsWith('.ts'))
210
+ tsCandidates.push(entry);
211
+ else if (entry.endsWith('.js')) {
212
+ tsCandidates.push(entry.replace(/\.js$/, '.ts'));
213
+ jsCandidates.push(entry);
214
+ }
215
+ }
216
+ tsCandidates.push('index.ts', 'src/index.ts');
217
+ jsCandidates.push('index.js', 'src/index.js');
218
+ const candidates = [...tsCandidates, ...jsCandidates];
219
+ for (const candidate of candidates) {
220
+ const fullPath = path.resolve(pkgDir, candidate);
221
+ try {
222
+ return { content: fs.readFileSync(fullPath, 'utf-8'), dir: path.dirname(fullPath) };
223
+ }
224
+ catch {
225
+ /* try next candidate */
226
+ }
227
+ }
228
+ }
229
+ catch {
230
+ /* no package.json here, keep walking up */
231
+ }
232
+ const parent = path.dirname(searchDir);
233
+ if (parent === searchDir)
234
+ break; // filesystem root
235
+ searchDir = parent;
129
236
  }
130
- };
131
- return JSON.stringify(tsConfig, null, 2);
237
+ return null;
238
+ }
239
+ function getPackageJson(projectName) {
240
+ const exeName = process.platform === 'win32' ? `${projectName}.exe` : projectName;
241
+ const pckg = {
242
+ name: projectName,
243
+ version: '1.0.0',
244
+ scripts: {
245
+ execute: 'npx typenative --source main.ts --script',
246
+ build: `npx typenative --source main.ts --output bin/${exeName}`
247
+ },
248
+ devDependencies: {
249
+ typenative: '^0.0.19'
250
+ }
251
+ };
252
+ return JSON.stringify(pckg, null, 2);
253
+ }
254
+ function getTsConfig() {
255
+ const tsConfig = {
256
+ include: ['**/*.ts'],
257
+ compilerOptions: {
258
+ target: 'es2020',
259
+ lib: [],
260
+ types: ['typenative', 'typenative/go', 'typenative/npm'],
261
+ rootDir: '.',
262
+ strict: true,
263
+ noImplicitAny: true,
264
+ allowSyntheticDefaultImports: true
265
+ }
266
+ };
267
+ return JSON.stringify(tsConfig, null, 2);
132
268
  }
133
269
  function getGitIgnore() {
134
- return `# TypeNative generated files
270
+ return `# TypeNative generated files
135
271
  node_modules/
136
272
  dist/
137
273
  bin/
138
274
  `;
139
275
  }
140
276
  function getReadMe(projectName) {
141
- const exeName = process.platform === 'win32' ? `${projectName}.exe` : projectName;
142
- return `# ${projectName}
277
+ const exeName = process.platform === 'win32' ? `${projectName}.exe` : projectName;
278
+ return `# ${projectName}
143
279
 
144
280
  This project was created using TypeNative, a tool to transpile TypeScript code to native Go code.
145
281
 
package/bin/transpiler.js CHANGED
@@ -17,7 +17,22 @@ const interfacePropertyTypes = new Map();
17
17
  const typeAliases = new Map();
18
18
  const enumNames = new Set();
19
19
  const enumBaseTypes = new Map();
20
- export function transpileToNative(code) {
20
+ // Maps local TS name → Go qualified name (e.g. 'Println' → 'fmt.Println', 'myFmt' → 'fmt')
21
+ const importAliases = new Map();
22
+ // Callback for resolving import specifiers to source code.
23
+ // specifier: the raw import string (relative path or package name)
24
+ // fromDir: directory of the file containing the import (null = main file's dir)
25
+ // Returns the file content and its directory (for resolving that file's own imports)
26
+ let fileResolver = null;
27
+ // Directory of the file currently being processed (null = main entry file)
28
+ let currentFileDir = null;
29
+ // Tracks already-included files by a stable key to prevent duplicates/cycles
30
+ const includedLocalImports = new Set();
31
+ // Collects Go source files generated from local TS imports (filename → content)
32
+ let localImportFiles = new Map();
33
+ export function transpileToNative(code, options) {
34
+ fileResolver = options?.readFile ?? null;
35
+ currentFileDir = null;
21
36
  const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
22
37
  TypeCheker = ts.createProgram(['main.ts'], {}).getTypeChecker();
23
38
  importedPackages.clear();
@@ -34,17 +49,21 @@ export function transpileToNative(code) {
34
49
  typeAliases.clear();
35
50
  enumNames.clear();
36
51
  enumBaseTypes.clear();
52
+ importAliases.clear();
53
+ includedLocalImports.clear();
54
+ localImportFiles = new Map();
37
55
  const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
38
56
  const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
39
- return `package main
57
+ const main = `package main
40
58
 
41
59
  ${[...importedPackages].map((pkg) => `import "${pkg}"`).join('\n')}
42
60
 
43
61
  func main() {
44
62
  ${transpiledCode.trim()}
45
63
  }
46
-
64
+
47
65
  ${transpiledCodeOutside.trim()}`;
66
+ return { main, files: localImportFiles };
48
67
  }
49
68
  export function visit(node, options = {}) {
50
69
  let code = '';
@@ -57,6 +76,9 @@ export function visit(node, options = {}) {
57
76
  else if (ts.isIdentifier(node)) {
58
77
  if (node.text === 'undefined')
59
78
  return 'nil';
79
+ const goAlias = importAliases.get(node.text);
80
+ if (goAlias)
81
+ return goAlias;
60
82
  return getSafeName(node.text);
61
83
  }
62
84
  else if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
@@ -555,6 +577,12 @@ export function visit(node, options = {}) {
555
577
  else if (ts.isNonNullExpression(node)) {
556
578
  return visit(node.expression);
557
579
  }
580
+ else if (ts.isImportDeclaration(node)) {
581
+ return visitImportDeclaration(node);
582
+ }
583
+ else if (ts.isExportDeclaration(node) || ts.isExportAssignment(node)) {
584
+ return '';
585
+ }
558
586
  const syntaxKind = ts.SyntaxKind[node.kind];
559
587
  if (!['FirstStatement', 'EndOfFileToken'].includes(syntaxKind)) {
560
588
  console.log(ts.SyntaxKind[node.kind], node.getText());
@@ -656,9 +684,7 @@ function inferFunctionBodyReturnType(node) {
656
684
  return undefined;
657
685
  }
658
686
  function inferArrowFunctionGoType(node) {
659
- const params = node.parameters
660
- .map((p) => (p.type ? getType(p.type) : 'interface{}'))
661
- .join(', ');
687
+ const params = node.parameters.map((p) => (p.type ? getType(p.type) : 'interface{}')).join(', ');
662
688
  const retType = inferFunctionBodyReturnType(node);
663
689
  return `func(${params})${retType ? ` ${retType}` : ''}`;
664
690
  }
@@ -975,6 +1001,9 @@ function visitArrayHigherOrderCall(node) {
975
1001
  ? getArrayElementTypeFromGoType(ownerType)
976
1002
  : 'interface{}';
977
1003
  if (methodName === 'join') {
1004
+ // Only intercept array.join — if owner type is unknown/not array, fall through to callHandlers
1005
+ if (!isArrayLikeGoType(ownerType))
1006
+ return undefined;
978
1007
  importedPackages.add('strings');
979
1008
  importedPackages.add('fmt');
980
1009
  const separator = node.arguments[0] ? visit(node.arguments[0]) : '""';
@@ -1572,9 +1601,7 @@ function getFunctionParametersInfo(parameters) {
1572
1601
  prefixBlockContent: ''
1573
1602
  };
1574
1603
  }
1575
- const hasRequiredAfterDefault = parameters
1576
- .slice(firstDefaultIndex)
1577
- .some((p) => !p.initializer);
1604
+ const hasRequiredAfterDefault = parameters.slice(firstDefaultIndex).some((p) => !p.initializer);
1578
1605
  if (hasRequiredAfterDefault) {
1579
1606
  return {
1580
1607
  signature: parameters.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`).join(', '),
@@ -1714,6 +1741,219 @@ function visitNewSet(node) {
1714
1741
  const values = initArg.elements.map((el) => `${tmp}[${visit(el)}] = struct{}{}`).join('; ');
1715
1742
  return `func() ${setType} { ${tmp} := make(${setType}); ${values}; return ${tmp} }()`;
1716
1743
  }
1744
+ function specifierToGoFileName(specifier) {
1745
+ const segments = specifier.split(/[/\\]/);
1746
+ let name = segments[segments.length - 1] || segments[segments.length - 2] || 'import';
1747
+ // Strip all extensions (e.g. .spec.ts → '', .ts → '')
1748
+ name = name.replace(/(\.[^.]+)+$/, '');
1749
+ name = name.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
1750
+ if (!name)
1751
+ name = 'import';
1752
+ // Deduplicate filename if already taken by a different import
1753
+ let candidate = name + '.go';
1754
+ let i = 2;
1755
+ while (localImportFiles.has(candidate)) {
1756
+ candidate = `${name}_${i}.go`;
1757
+ i++;
1758
+ }
1759
+ return candidate;
1760
+ }
1761
+ function normalizeCjsToEsm(code) {
1762
+ // Remove 'use strict' directive
1763
+ code = code.replace(/['"]use strict['"];?\n?/g, '');
1764
+ // module.exports.X = function(...) { } or exports.X = function(...) { }
1765
+ code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
1766
+ return code;
1767
+ }
1768
+ function includeLocalImport(code, dir, goFileName) {
1769
+ const prevDir = currentFileDir;
1770
+ currentFileDir = dir;
1771
+ // Normalize CommonJS modules to ES module syntax before parsing
1772
+ if (!code.includes('export ') && (code.includes('module.exports') || code.includes('exports.'))) {
1773
+ code = normalizeCjsToEsm(code);
1774
+ }
1775
+ if (!goFileName) {
1776
+ // Inline mode (npm packages): pull declarations into the current transpilation context
1777
+ const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
1778
+ for (const stmt of sf.statements) {
1779
+ visit(stmt, { addFunctionOutside: true });
1780
+ }
1781
+ currentFileDir = prevDir;
1782
+ return;
1783
+ }
1784
+ // Separate-file mode (local TS imports): transpile to its own Go file
1785
+ const savedOutsideNodes = outsideNodes;
1786
+ const savedPackages = [...importedPackages];
1787
+ outsideNodes = [];
1788
+ importedPackages.clear();
1789
+ const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
1790
+ const inlineLines = [];
1791
+ for (const stmt of sf.statements) {
1792
+ const result = visit(stmt, { addFunctionOutside: true });
1793
+ if (result.trim())
1794
+ inlineLines.push(result);
1795
+ }
1796
+ const fileImports = [...importedPackages].map((pkg) => `import "${pkg}"`).join('\n');
1797
+ const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
1798
+ const fileInline = inlineLines.join('\n');
1799
+ const parts = ['package main'];
1800
+ if (fileImports)
1801
+ parts.push(fileImports);
1802
+ if (fileInline.trim())
1803
+ parts.push(fileInline.trim());
1804
+ if (fileOutside.trim())
1805
+ parts.push(fileOutside.trim());
1806
+ localImportFiles.set(goFileName, parts.join('\n\n'));
1807
+ // Restore main-file state
1808
+ outsideNodes = savedOutsideNodes;
1809
+ importedPackages.clear();
1810
+ for (const p of savedPackages)
1811
+ importedPackages.add(p);
1812
+ currentFileDir = prevDir;
1813
+ }
1814
+ function registerGoPackageAliases(node, goPkg) {
1815
+ const pkgName = goPkg.split('/').pop();
1816
+ if (!node.importClause)
1817
+ return;
1818
+ const clause = node.importClause;
1819
+ // Default import: `import fmt from 'go:fmt'` or `import myFmt from 'go:fmt'`
1820
+ if (clause.name && clause.name.text !== pkgName) {
1821
+ importAliases.set(clause.name.text, pkgName);
1822
+ }
1823
+ if (clause.namedBindings) {
1824
+ if (ts.isNamespaceImport(clause.namedBindings)) {
1825
+ // `import * as myFmt from 'go:fmt'`
1826
+ const localName = clause.namedBindings.name.text;
1827
+ if (localName !== pkgName) {
1828
+ importAliases.set(localName, pkgName);
1829
+ }
1830
+ }
1831
+ else if (ts.isNamedImports(clause.namedBindings)) {
1832
+ // `import { Println, Sprintf as Spf } from 'go:fmt'`
1833
+ for (const el of clause.namedBindings.elements) {
1834
+ if (el.isTypeOnly)
1835
+ continue;
1836
+ const localName = el.name.text;
1837
+ const importedName = el.propertyName?.text ?? localName;
1838
+ importAliases.set(localName, `${pkgName}.${importedName}`);
1839
+ }
1840
+ }
1841
+ }
1842
+ }
1843
+ function getImportLocalName(node) {
1844
+ const clause = node.importClause;
1845
+ if (!clause)
1846
+ return null;
1847
+ if (clause.name)
1848
+ return clause.name.text;
1849
+ if (clause.namedBindings) {
1850
+ if (ts.isNamespaceImport(clause.namedBindings))
1851
+ return clause.namedBindings.name.text;
1852
+ }
1853
+ return null;
1854
+ }
1855
+ // Per-module table: maps Node.js function name → Go expression template.
1856
+ // For default/namespace imports (e.g. `import path from 'node:path'`), entries are registered
1857
+ // as `callHandlers[localName.funcName]`. For named imports (e.g. `import { join } from 'node:path'`),
1858
+ // the Go identifier is stored in importAliases so bare calls like `join(...)` resolve correctly.
1859
+ const nodeModuleMappings = {
1860
+ path: {
1861
+ goPackage: 'path/filepath',
1862
+ functions: {
1863
+ join: (args) => `filepath.Join(${args.join(', ')})`,
1864
+ dirname: (args) => `filepath.Dir(${args[0]})`,
1865
+ basename: (args) => args[1]
1866
+ ? `strings.TrimSuffix(filepath.Base(${args[0]}), ${args[1]})`
1867
+ : `filepath.Base(${args[0]})`,
1868
+ extname: (args) => `filepath.Ext(${args[0]})`,
1869
+ resolve: (args) => `func() string { p, _ := filepath.Abs(filepath.Join(${args.join(', ')})); return p }()`
1870
+ }
1871
+ }
1872
+ // Future node stdlib modules can be added here as additional keys:
1873
+ // fs: { goPackage: 'os', functions: { ... } },
1874
+ // os: { goPackage: 'os', functions: { ... } },
1875
+ };
1876
+ // Mapping from Node.js stdlib module names to Go setup functions.
1877
+ // Each entry adds the required Go imports and registers call handlers for the local identifier.
1878
+ function setupNodeModuleImport(node, nodeModule) {
1879
+ const mapping = nodeModuleMappings[nodeModule];
1880
+ if (!mapping)
1881
+ return;
1882
+ importedPackages.add(mapping.goPackage);
1883
+ const clause = node.importClause;
1884
+ if (!clause)
1885
+ return;
1886
+ if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
1887
+ // Named imports: `import { join, dirname } from 'node:path'`
1888
+ // Map each local name directly to its Go qualified function via importAliases,
1889
+ // so bare calls like `join(...)` resolve to `filepath.Join(...)`.
1890
+ for (const el of clause.namedBindings.elements) {
1891
+ if (el.isTypeOnly)
1892
+ continue;
1893
+ const localName = el.name.text;
1894
+ const importedName = el.propertyName?.text ?? localName;
1895
+ const fn = mapping.functions[importedName];
1896
+ if (fn) {
1897
+ // Register a call handler keyed on the local name
1898
+ callHandlers[localName] = (_caller, args) => fn(args);
1899
+ }
1900
+ }
1901
+ }
1902
+ else {
1903
+ // Default import (`import path from 'node:path'`) or
1904
+ // namespace import (`import * as path from 'node:path'`)
1905
+ const localName = getImportLocalName(node) ?? nodeModule;
1906
+ for (const [funcName, fn] of Object.entries(mapping.functions)) {
1907
+ callHandlers[`${localName}.${funcName}`] = (_caller, args) => fn(args);
1908
+ }
1909
+ }
1910
+ }
1911
+ function visitImportDeclaration(node) {
1912
+ if (!ts.isStringLiteral(node.moduleSpecifier))
1913
+ return '';
1914
+ const moduleSpec = node.moduleSpecifier.text;
1915
+ // Relative imports → transpile to a separate Go file
1916
+ if (moduleSpec.startsWith('.') || moduleSpec.startsWith('/')) {
1917
+ // Key combines current dir + specifier to avoid cross-package collisions
1918
+ const key = `${currentFileDir ?? ''}::${moduleSpec}`;
1919
+ if (fileResolver && !includedLocalImports.has(key)) {
1920
+ includedLocalImports.add(key);
1921
+ const resolved = fileResolver(moduleSpec, currentFileDir);
1922
+ if (resolved) {
1923
+ const goFileName = specifierToGoFileName(moduleSpec);
1924
+ includeLocalImport(resolved.content, resolved.dir, goFileName);
1925
+ }
1926
+ }
1927
+ return '';
1928
+ }
1929
+ // Type-only imports contribute nothing to runtime
1930
+ if (node.importClause?.isTypeOnly)
1931
+ return '';
1932
+ // Go standard library: `import { Println } from 'go:fmt'` → import "fmt"
1933
+ if (moduleSpec.startsWith('go:')) {
1934
+ const goPkg = moduleSpec.slice(3);
1935
+ importedPackages.add(goPkg);
1936
+ registerGoPackageAliases(node, goPkg);
1937
+ return '';
1938
+ }
1939
+ // Node.js standard library: `import path from 'node:path'` → mapped Go packages
1940
+ if (moduleSpec.startsWith('node:')) {
1941
+ const nodeModule = moduleSpec.slice(5);
1942
+ setupNodeModuleImport(node, nodeModule);
1943
+ return '';
1944
+ }
1945
+ // npm package (bare specifier) → transpile to its own Go file
1946
+ const npmKey = `npm::${moduleSpec}`;
1947
+ if (fileResolver && !includedLocalImports.has(npmKey)) {
1948
+ includedLocalImports.add(npmKey);
1949
+ const resolved = fileResolver(moduleSpec, currentFileDir);
1950
+ if (resolved) {
1951
+ const goFileName = specifierToGoFileName(moduleSpec);
1952
+ includeLocalImport(resolved.content, resolved.dir, goFileName);
1953
+ }
1954
+ }
1955
+ return '';
1956
+ }
1717
1957
  function getForOfVarNames(initializer) {
1718
1958
  if (!ts.isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
1719
1959
  return ['_'];
package/go.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference path="types/typenative-go.d.ts" />
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference path="types/typenative.d.ts" />
package/npm.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference path="types/typenative-npm.d.ts" />
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "typenative",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "description": "Build native applications using Typescript.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "typenative": "bin/index.js"
8
8
  },
9
+ "types": "./index.d.ts",
9
10
  "scripts": {
10
11
  "build": "tsc",
11
12
  "watch": "tsc --watch",
@@ -35,7 +36,11 @@
35
36
  "test21": "node ./bin/index --source test/Test21.spec.ts --script",
36
37
  "test22": "node ./bin/index --source test/Test22.spec.ts --script",
37
38
  "test23": "node ./bin/index --source test/Test23.spec.ts --script",
38
- "test24": "node ./bin/index --source test/Test24.spec.ts --script"
39
+ "test24": "node ./bin/index --source test/Test24.spec.ts --script",
40
+ "test25": "node ./bin/index --source test/Test25.spec.ts --script",
41
+ "test25_export": "node ./bin/index --source test/Test25-export.spec.ts --script",
42
+ "test26": "node ./bin/index --source test/Test26.spec.ts --script",
43
+ "test27": "node ./bin/index --source test/Test27.spec.ts --script"
39
44
  },
40
45
  "repository": {
41
46
  "type": "git",
@@ -50,13 +55,13 @@
50
55
  "devDependencies": {
51
56
  "@types/fs-extra": "^11.0.4",
52
57
  "@types/inquirer": "^9.0.9",
53
- "@types/node": "^25.2.3",
54
- "rimraf": "^6.1.2"
58
+ "@types/node": "^25.3.5",
59
+ "rimraf": "^6.1.3"
55
60
  },
56
61
  "dependencies": {
57
62
  "execa": "^9.6.1",
58
- "fs-extra": "^11.3.3",
59
- "inquirer": "^13.2.4",
63
+ "fs-extra": "^11.3.4",
64
+ "inquirer": "^13.3.0",
60
65
  "nanoid": "^5.1.6",
61
66
  "typescript": "^5.9.3"
62
67
  }
@@ -0,0 +1,15 @@
1
+ // Type definitions for TypeNative go standard library modules
2
+
3
+ declare module 'go:fmt' {
4
+ export function Println(...args: any[]): void;
5
+ export function Sprintf(format: string, ...args: any[]): string;
6
+ }
7
+
8
+ declare module 'go:strconv' {
9
+ export function FormatBool(value: boolean): string;
10
+ }
11
+
12
+ declare module 'go:strings' {
13
+ export function ToUpper(s: string): string;
14
+ export function ToLower(s: string): string;
15
+ }
@@ -0,0 +1,5 @@
1
+ // Type definitions for TypeNative NPM standard library modules
2
+
3
+ declare module 'node:path' {
4
+ export function join(...parts: string[]): string;
5
+ }
@@ -1,3 +1,5 @@
1
+ // Type definitions for TypeNative
2
+
1
3
  interface Boolean {}
2
4
 
3
5
  interface CallableFunction extends Function {}
@@ -197,7 +199,7 @@ interface Error {
197
199
  }
198
200
 
199
201
  interface ErrorConstructor {
200
- new(message?: string): Error;
202
+ new (message?: string): Error;
201
203
  }
202
204
 
203
205
  declare var Error: ErrorConstructor;