typenative 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish-github-packages.yml +13 -5
- package/CHANGELOG.md +59 -0
- package/README.md +45 -20
- package/TODO.md +30 -25
- package/bin/cli.js +62 -0
- package/bin/index.js +104 -132
- package/bin/main.js +31 -0
- package/bin/parse-native.js +37 -0
- package/bin/parse-node.js +110 -0
- package/bin/prompt.js +29 -0
- package/bin/transpiler.js +1416 -288
- package/package.json +8 -7
- package/tsparser/go.mod +14 -0
- package/tsparser/go.sum +22 -0
- package/tsparser/main.go +279 -0
- package/tsparser/tsparser.exe +0 -0
- package/types/typenative-npm.d.ts +44 -0
- package/types/typenative.d.ts +99 -1
package/bin/index.js
CHANGED
|
@@ -1,151 +1,123 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Core compiler logic — kept free of Node-only dependencies (inquirer, execa,
|
|
2
|
+
// fs-extra) so this module transpiles with TypeNative itself.
|
|
3
|
+
// Node-only interactive prompting lives in src/cli.ts; the self-hosted entry
|
|
4
|
+
// point is src/main.ts.
|
|
4
5
|
import path from 'node:path';
|
|
5
|
-
import
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import { platform } from 'node:os';
|
|
8
|
+
// TypeNative maps node:child_process to a synchronous Go API whose shape differs
|
|
9
|
+
// from Node's; under Node these functions are never called (the CLI wrapper does
|
|
10
|
+
// not reach them), so they're accessed through `any` casts for type-checking.
|
|
11
|
+
import * as childProcess from 'node:child_process';
|
|
6
12
|
import { transpileToNative } from './transpiler.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
validate: (input) => input.trim() !== ''
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
type: 'input',
|
|
40
|
-
name: 'output',
|
|
41
|
-
message: 'Enter Output Path:',
|
|
42
|
-
when: !newCommand && !scriptMode && !output,
|
|
43
|
-
validate: (input) => input.trim() !== ''
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
type: 'editor',
|
|
47
|
-
name: 'tsCode',
|
|
48
|
-
message: 'Write your typescript code here:',
|
|
49
|
-
when: !newCommand && scriptMode && !source,
|
|
50
|
-
default: `console.log('Hello, World!');`
|
|
51
|
-
}
|
|
52
|
-
]);
|
|
53
|
-
if (newCommand) {
|
|
54
|
-
const projectName = answers.projectName.trim();
|
|
55
|
-
await fs.ensureDir(projectName);
|
|
56
|
-
await fs.writeFile(path.join(projectName, 'main.ts'), `// Write your TypeScript code here\nconsole.log('Hello, World!');\n`, { encoding: 'utf-8' });
|
|
57
|
-
await fs.writeFile(path.join(projectName, 'tsconfig.json'), getTsConfig(), {
|
|
58
|
-
encoding: 'utf-8'
|
|
59
|
-
});
|
|
60
|
-
await fs.writeFile(path.join(projectName, 'package.json'), getPackageJson(projectName), {
|
|
61
|
-
encoding: 'utf-8'
|
|
62
|
-
});
|
|
63
|
-
await fs.writeFile(path.join(projectName, '.gitignore'), getGitIgnore(), {
|
|
64
|
-
encoding: 'utf-8'
|
|
65
|
-
});
|
|
66
|
-
await fs.writeFile(path.join(projectName, 'README.md'), getReadMe(projectName), {
|
|
67
|
-
encoding: 'utf-8'
|
|
68
|
-
});
|
|
69
|
-
console.log(`Project "${projectName}" created successfully!`);
|
|
70
|
-
if (answers.installDependencies) {
|
|
71
|
-
console.log('Installing dependencies...');
|
|
72
|
-
await execa('npm', ['install'], { cwd: projectName, stdio: 'inherit' });
|
|
73
|
-
console.log('Dependencies installed successfully!');
|
|
13
|
+
// Run a command and capture its output. Uses Node's spawnSync under Node and a
|
|
14
|
+
// Go helper (os/exec) once transpiled — both synchronous, same result shape.
|
|
15
|
+
export function runCommand(name, args) {
|
|
16
|
+
const result = childProcess.spawnSync(name, args, { encoding: 'utf-8' });
|
|
17
|
+
return { stdout: result.stdout, stderr: result.stderr, status: result.status };
|
|
18
|
+
}
|
|
19
|
+
// Run a command with the parent's stdio, returns the exit code.
|
|
20
|
+
export function runInherit(name, args = []) {
|
|
21
|
+
const result = childProcess.spawnSync(name, args, { stdio: 'inherit' });
|
|
22
|
+
return result.status;
|
|
23
|
+
}
|
|
24
|
+
export function createProject(projectName, installDependencies) {
|
|
25
|
+
fs.mkdirSync(projectName, { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(projectName, 'main.ts'), `// Write your TypeScript code here\nconsole.log('Hello, World!');\n`);
|
|
27
|
+
fs.writeFileSync(path.join(projectName, 'tsconfig.json'), getTsConfig());
|
|
28
|
+
fs.writeFileSync(path.join(projectName, 'package.json'), getPackageJson(projectName));
|
|
29
|
+
fs.writeFileSync(path.join(projectName, '.gitignore'), getGitIgnore());
|
|
30
|
+
fs.writeFileSync(path.join(projectName, 'README.md'), getReadMe(projectName));
|
|
31
|
+
console.log(`Project "${projectName}" created successfully!`);
|
|
32
|
+
if (installDependencies) {
|
|
33
|
+
console.log('Installing dependencies...');
|
|
34
|
+
// npm --prefix avoids needing cwd support in the subprocess binding
|
|
35
|
+
const result = runCommand('npm', ['install', '--prefix', projectName]);
|
|
36
|
+
if (result.stdout)
|
|
37
|
+
console.log(result.stdout.trim());
|
|
38
|
+
if (result.status !== 0) {
|
|
39
|
+
if (result.stderr)
|
|
40
|
+
console.error(result.stderr.trim());
|
|
41
|
+
process.exit(result.status);
|
|
74
42
|
}
|
|
75
|
-
|
|
43
|
+
console.log('Dependencies installed successfully!');
|
|
76
44
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
45
|
+
}
|
|
46
|
+
export function run(opts) {
|
|
47
|
+
const sourcePath = opts.tsCode ? null : opts.source;
|
|
48
|
+
const tsCode = opts.tsCode ? opts.tsCode : fs.readFileSync(sourcePath, 'utf-8');
|
|
81
49
|
const sourceDir = sourcePath ? path.dirname(path.resolve(sourcePath)) : null;
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
50
|
+
const transpileOptions = { parse: opts.parse };
|
|
51
|
+
if (sourceDir) {
|
|
52
|
+
transpileOptions.readFile = (specifier, fromDir) => {
|
|
53
|
+
const baseDir = fromDir ?? sourceDir;
|
|
54
|
+
// Relative or absolute path → resolve from baseDir
|
|
55
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
56
|
+
// ES convention: ./x.js may refer to x.ts
|
|
57
|
+
const tsSpecifier = specifier.endsWith('.js')
|
|
58
|
+
? specifier.replace(/\.js$/, '.ts')
|
|
59
|
+
: specifier;
|
|
60
|
+
for (const candidate of [tsSpecifier + '.ts', tsSpecifier, specifier]) {
|
|
61
|
+
try {
|
|
62
|
+
const fullPath = path.resolve(baseDir, candidate);
|
|
63
|
+
return {
|
|
64
|
+
content: fs.readFileSync(fullPath, 'utf-8'),
|
|
65
|
+
dir: path.dirname(fullPath)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
/* not found */
|
|
99
70
|
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
// npm package — walk up from baseDir looking for node_modules/<name>
|
|
103
|
-
const resolved = resolveNpmPackage(baseDir, specifier);
|
|
104
|
-
if (!resolved)
|
|
105
|
-
return null;
|
|
106
|
-
let { content, dir } = resolved;
|
|
107
|
-
// Normalize CommonJS to ES module syntax
|
|
108
|
-
if (!content.includes('export ') && (content.includes('module.exports') || content.includes('exports.'))) {
|
|
109
|
-
content = normalizeCjsContent(content);
|
|
110
71
|
}
|
|
111
|
-
|
|
112
|
-
const typed = tryInjectDtsTypes(content, specifier, sourceDir);
|
|
113
|
-
if (typed)
|
|
114
|
-
content = typed;
|
|
115
|
-
return { content, dir };
|
|
72
|
+
return null;
|
|
116
73
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
74
|
+
// npm package — walk up from baseDir looking for node_modules/<name>
|
|
75
|
+
const resolved = resolveNpmPackage(baseDir, specifier);
|
|
76
|
+
if (!resolved)
|
|
77
|
+
return null;
|
|
78
|
+
let { content, dir } = resolved;
|
|
79
|
+
// Normalize CommonJS to ES module syntax
|
|
80
|
+
if (!content.includes('export ') &&
|
|
81
|
+
(content.includes('module.exports') || content.includes('exports.'))) {
|
|
82
|
+
content = normalizeCjsContent(content);
|
|
83
|
+
}
|
|
84
|
+
// Inject types from a local ambient .d.ts if available
|
|
85
|
+
const typed = tryInjectDtsTypes(content, specifier, sourceDir);
|
|
86
|
+
if (typed)
|
|
87
|
+
content = typed;
|
|
88
|
+
return { content, dir };
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const transpileResult = transpileToNative(tsCode, transpileOptions);
|
|
92
|
+
const exeName = platform() === 'win32' ? 'native.exe' : 'native';
|
|
120
93
|
const exePath = `dist/${exeName}`;
|
|
121
|
-
|
|
94
|
+
fs.mkdirSync('dist', { recursive: true });
|
|
122
95
|
// Clean up stale Go files from previous runs before writing new ones
|
|
123
|
-
for (const existing of
|
|
96
|
+
for (const existing of fs.readdirSync('dist')) {
|
|
124
97
|
if (existing.endsWith('.go'))
|
|
125
|
-
|
|
98
|
+
fs.rmSync(`dist/${existing}`);
|
|
126
99
|
}
|
|
127
|
-
|
|
100
|
+
fs.writeFileSync('dist/code.go', transpileResult.main);
|
|
128
101
|
const goFiles = ['dist/code.go'];
|
|
129
102
|
for (const [filename, content] of transpileResult.files) {
|
|
130
|
-
|
|
103
|
+
fs.writeFileSync(`dist/${filename}`, content);
|
|
131
104
|
goFiles.push(`dist/${filename}`);
|
|
132
105
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
stdio: 'inherit'
|
|
140
|
-
});
|
|
141
|
-
//await fs.remove(exePath);
|
|
106
|
+
const buildResult = runCommand('go', ['build', '-o', exePath, ...goFiles]);
|
|
107
|
+
if (buildResult.status !== 0) {
|
|
108
|
+
console.error('go build failed:');
|
|
109
|
+
if (buildResult.stderr)
|
|
110
|
+
console.error(buildResult.stderr.trim());
|
|
111
|
+
process.exit(1);
|
|
142
112
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
//await fs.remove(exePath);
|
|
146
|
-
console.log(`Created native executable at: ${output ?? answers.output}`);
|
|
113
|
+
if (opts.scriptMode) {
|
|
114
|
+
process.exit(runInherit(exePath));
|
|
147
115
|
}
|
|
148
|
-
|
|
116
|
+
else if (opts.output) {
|
|
117
|
+
fs.copyFileSync(exePath, opts.output);
|
|
118
|
+
console.log(`Created native executable at: ${opts.output}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
149
121
|
function normalizeCjsContent(code) {
|
|
150
122
|
code = code.replace(/['"]use strict['"];?\n?/g, '');
|
|
151
123
|
code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
|
|
@@ -237,7 +209,7 @@ function resolveNpmPackage(fromDir, packageName) {
|
|
|
237
209
|
return null;
|
|
238
210
|
}
|
|
239
211
|
function getPackageJson(projectName) {
|
|
240
|
-
const exeName =
|
|
212
|
+
const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
|
|
241
213
|
const pckg = {
|
|
242
214
|
name: projectName,
|
|
243
215
|
version: '1.0.0',
|
|
@@ -274,7 +246,7 @@ bin/
|
|
|
274
246
|
`;
|
|
275
247
|
}
|
|
276
248
|
function getReadMe(projectName) {
|
|
277
|
-
const exeName =
|
|
249
|
+
const exeName = platform() === 'win32' ? `${projectName}.exe` : projectName;
|
|
278
250
|
return `# ${projectName}
|
|
279
251
|
|
|
280
252
|
This project was created using TypeNative, a tool to transpile TypeScript code to native Go code.
|
package/bin/main.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Self-hosting entry point: this file is what TypeNative compiles to produce
|
|
2
|
+
// the native compiler binary. Interactive prompts use the transpilable
|
|
3
|
+
// prompt module (sync stdin) instead of inquirer.
|
|
4
|
+
import { run, createProject } from './index.js';
|
|
5
|
+
import { ask, askMultiline, confirm } from './prompt.js';
|
|
6
|
+
import { parseAstJsonNative } from './parse-native.js';
|
|
7
|
+
(function main() {
|
|
8
|
+
const scriptMode = process.argv.findIndex((a) => a === '--script') > -1;
|
|
9
|
+
const newCommand = process.argv.findIndex((a) => a === '--new') > -1;
|
|
10
|
+
const sourceIndex = process.argv.findIndex((a) => a === '--source');
|
|
11
|
+
let source = sourceIndex > -1 ? process.argv[sourceIndex + 1] : null;
|
|
12
|
+
const outputIndex = process.argv.findIndex((a) => a === '--output');
|
|
13
|
+
let output = outputIndex > -1 ? process.argv[outputIndex + 1] : null;
|
|
14
|
+
if (newCommand) {
|
|
15
|
+
const projectName = ask('Enter Project Name:');
|
|
16
|
+
const installDependencies = confirm('Do you want to install dependencies?');
|
|
17
|
+
createProject(projectName, installDependencies);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let tsCode = null;
|
|
21
|
+
if (!newCommand && !scriptMode && !source) {
|
|
22
|
+
source = ask('Enter Path to typescript main file:');
|
|
23
|
+
}
|
|
24
|
+
if (!newCommand && !scriptMode && !output) {
|
|
25
|
+
output = ask('Enter Output Path:');
|
|
26
|
+
}
|
|
27
|
+
if (!newCommand && scriptMode && !source) {
|
|
28
|
+
tsCode = askMultiline('Write your typescript code here:');
|
|
29
|
+
}
|
|
30
|
+
run({ source, output, scriptMode, tsCode, parse: parseAstJsonNative });
|
|
31
|
+
})();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Self-hosted parser: spawns the tsparser Go tool (tsparser/main.go) to parse
|
|
2
|
+
// TypeScript into the normalized JSON AST. Fully transpilable — used by the
|
|
3
|
+
// native compiler binary instead of the typescript npm package.
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as childProcess from 'node:child_process';
|
|
7
|
+
export function parseAstJsonNative(code) {
|
|
8
|
+
const exe = findTsparser();
|
|
9
|
+
const result = childProcess.spawnSync(exe, [], { input: code, encoding: 'utf-8' });
|
|
10
|
+
if (result.status !== 0) {
|
|
11
|
+
throw new Error('tsparser failed: ' + result.stderr);
|
|
12
|
+
}
|
|
13
|
+
return JSON.parse(result.stdout);
|
|
14
|
+
}
|
|
15
|
+
// Locates the tsparser binary: TYPENATIVE_TSPARSER env var, next to this
|
|
16
|
+
// executable, or in a sibling tsparser/ directory (dev checkout layout).
|
|
17
|
+
function findTsparser() {
|
|
18
|
+
const exeName = platformIsWindows() ? 'tsparser.exe' : 'tsparser';
|
|
19
|
+
const envPath = process.env.TYPENATIVE_TSPARSER;
|
|
20
|
+
if (envPath && fs.existsSync(envPath))
|
|
21
|
+
return envPath;
|
|
22
|
+
const thisDir = path.dirname(path.resolve(process.argv[0]));
|
|
23
|
+
const candidates = [
|
|
24
|
+
path.join(thisDir, exeName),
|
|
25
|
+
path.join(thisDir, 'tsparser', exeName),
|
|
26
|
+
path.join(thisDir, '..', 'tsparser', exeName),
|
|
27
|
+
path.join(thisDir, '..', '..', 'tsparser', exeName)
|
|
28
|
+
];
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
if (fs.existsSync(candidate))
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
throw new Error('tsparser binary not found; set TYPENATIVE_TSPARSER');
|
|
34
|
+
}
|
|
35
|
+
function platformIsWindows() {
|
|
36
|
+
return process.platform === 'win32';
|
|
37
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Node-side AST adapter: converts a `typescript` npm package AST into the same
|
|
2
|
+
// normalized JSON shape that the `tsparser` Go tool emits
|
|
3
|
+
// (tsparser/main.go). The transpiler consumes this shape, so dev-CLI output and
|
|
4
|
+
// self-hosted (Go parser) output are field-compatible.
|
|
5
|
+
//
|
|
6
|
+
// Node-only module: imports the `typescript` package directly and is injected
|
|
7
|
+
// into the transpiler by the Node CLI wrapper (src/cli.ts). The self-hosted
|
|
8
|
+
// binary uses src/parse-native.ts instead.
|
|
9
|
+
import ts from 'typescript';
|
|
10
|
+
// Internal ts.Node bookkeeping properties that must not leak into the JSON
|
|
11
|
+
const SKIP_PROPS = new Set([
|
|
12
|
+
'pos',
|
|
13
|
+
'end',
|
|
14
|
+
'flags',
|
|
15
|
+
'modifierFlagsCache',
|
|
16
|
+
'transformFlags',
|
|
17
|
+
'parent',
|
|
18
|
+
'original',
|
|
19
|
+
'symbol',
|
|
20
|
+
'id',
|
|
21
|
+
'jsDoc',
|
|
22
|
+
'flowNode',
|
|
23
|
+
'contextFlags',
|
|
24
|
+
'parserContextFlags',
|
|
25
|
+
'excludeFlags',
|
|
26
|
+
'modifierFlags',
|
|
27
|
+
'flowId',
|
|
28
|
+
'symbolCount',
|
|
29
|
+
'identifierCount',
|
|
30
|
+
'nodeArrayCache',
|
|
31
|
+
'parsedByParser',
|
|
32
|
+
'autoGenerateKind',
|
|
33
|
+
'autoGenerateId',
|
|
34
|
+
'emitNode',
|
|
35
|
+
'localSymbol',
|
|
36
|
+
// 'text'/'escapedText' are only emitted for token-like kinds (TEXT_KINDS);
|
|
37
|
+
// SourceFile.text would otherwise leak the whole source into the AST
|
|
38
|
+
'text',
|
|
39
|
+
'escapedText',
|
|
40
|
+
'endOfFileToken',
|
|
41
|
+
'externalModuleIndicator',
|
|
42
|
+
'commonJsModuleIndicator',
|
|
43
|
+
'localJsxNamespace',
|
|
44
|
+
'localJsxFactory',
|
|
45
|
+
'localJsxFragmentFactory'
|
|
46
|
+
]);
|
|
47
|
+
const TEXT_KINDS = new Set([
|
|
48
|
+
ts.SyntaxKind.Identifier,
|
|
49
|
+
ts.SyntaxKind.PrivateIdentifier,
|
|
50
|
+
ts.SyntaxKind.StringLiteral,
|
|
51
|
+
ts.SyntaxKind.NumericLiteral,
|
|
52
|
+
ts.SyntaxKind.BigIntLiteral,
|
|
53
|
+
ts.SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
54
|
+
ts.SyntaxKind.RegularExpressionLiteral,
|
|
55
|
+
ts.SyntaxKind.TemplateHead,
|
|
56
|
+
ts.SyntaxKind.TemplateMiddle,
|
|
57
|
+
ts.SyntaxKind.TemplateTail,
|
|
58
|
+
ts.SyntaxKind.JsxText
|
|
59
|
+
]);
|
|
60
|
+
// Reverse enum map preferring canonical names over aliases
|
|
61
|
+
// (ts.SyntaxKind[8] is "FirstLiteralToken", the alias of NumericLiteral)
|
|
62
|
+
const KIND_NAMES = (() => {
|
|
63
|
+
const names = {};
|
|
64
|
+
for (const key of Object.keys(ts.SyntaxKind)) {
|
|
65
|
+
const val = ts.SyntaxKind[key];
|
|
66
|
+
if (typeof val === 'number' && names[val] === undefined)
|
|
67
|
+
names[val] = key;
|
|
68
|
+
}
|
|
69
|
+
return names;
|
|
70
|
+
})();
|
|
71
|
+
function isNode(v) {
|
|
72
|
+
return typeof v === 'object' && v !== null && typeof v.kind === 'number' && typeof v.pos === 'number';
|
|
73
|
+
}
|
|
74
|
+
function convertNode(node) {
|
|
75
|
+
const obj = { kind: KIND_NAMES[node.kind] ?? String(node.kind) };
|
|
76
|
+
if (TEXT_KINDS.has(node.kind)) {
|
|
77
|
+
obj.text = node.text ?? node.escapedText;
|
|
78
|
+
}
|
|
79
|
+
for (const key of Object.keys(node)) {
|
|
80
|
+
if (SKIP_PROPS.has(key))
|
|
81
|
+
continue;
|
|
82
|
+
const v = node[key];
|
|
83
|
+
if (typeof v === 'function')
|
|
84
|
+
continue;
|
|
85
|
+
if (v === null || v === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
if (isNode(v)) {
|
|
88
|
+
obj[key] = convertNode(v);
|
|
89
|
+
}
|
|
90
|
+
else if (Array.isArray(v)) {
|
|
91
|
+
const arr = v.filter((e) => isNode(e)).map((e) => convertNode(e));
|
|
92
|
+
if (arr.length > 0)
|
|
93
|
+
obj[key] = arr;
|
|
94
|
+
}
|
|
95
|
+
else if (typeof v === 'number' && key === 'operator') {
|
|
96
|
+
// unary operator: emit the kind name (matches the Go producer)
|
|
97
|
+
obj[key] = KIND_NAMES[v] ?? String(v);
|
|
98
|
+
}
|
|
99
|
+
else if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
|
|
100
|
+
if (key !== 'kind')
|
|
101
|
+
obj[key] = v;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return obj;
|
|
105
|
+
}
|
|
106
|
+
// Parse TypeScript source into the normalized JSON AST shape.
|
|
107
|
+
export function parseAstJson(code) {
|
|
108
|
+
const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
109
|
+
return convertNode(sourceFile);
|
|
110
|
+
}
|
package/bin/prompt.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Interactive prompts for the self-hosted native binary.
|
|
2
|
+
// Implemented exclusively with TypeNative-mapped APIs (node:readline → Go helper)
|
|
3
|
+
// so this module transpiles. Under Node the namespace import of node:readline is
|
|
4
|
+
// valid but `question` is only available once transpiled — the Node CLI wrapper
|
|
5
|
+
// (src/cli.ts) resolves prompts via inquirer instead and never calls these.
|
|
6
|
+
import * as readlineSync from 'node:readline';
|
|
7
|
+
export function ask(message) {
|
|
8
|
+
let input = readlineSync.question(message + ' ');
|
|
9
|
+
while (input.trim() === '') {
|
|
10
|
+
console.log('Please enter a non-empty value.');
|
|
11
|
+
input = readlineSync.question(message + ' ');
|
|
12
|
+
}
|
|
13
|
+
return input.trim();
|
|
14
|
+
}
|
|
15
|
+
export function askMultiline(message) {
|
|
16
|
+
console.log(message);
|
|
17
|
+
console.log("(finish with a line containing only 'END')");
|
|
18
|
+
const lines = [];
|
|
19
|
+
let line = readlineSync.question('');
|
|
20
|
+
while (line.trim() !== 'END') {
|
|
21
|
+
lines.push(line);
|
|
22
|
+
line = readlineSync.question('');
|
|
23
|
+
}
|
|
24
|
+
return lines.join('\n');
|
|
25
|
+
}
|
|
26
|
+
export function confirm(message) {
|
|
27
|
+
const answer = readlineSync.question(message + ' (y/n): ');
|
|
28
|
+
return answer.trim().toLowerCase() === 'y';
|
|
29
|
+
}
|