@tsonic/tsonic
Version:
TypeScript to C# to NativeAOT compiler
1,513 lines (1,461 loc) • 339 kB
JavaScript
#!/usr/bin/env node
// packages/cli/dist/cli/constants.js
var VERSION = "0.0.1";
// packages/cli/dist/cli/help.js
var showHelp = () => {
console.log(`
Tsonic - TypeScript to C# to NativeAOT compiler v${VERSION}
USAGE:
tsonic <command> [options]
COMMANDS:
project init Initialize a new Tsonic project
emit [entry] Generate C# code only
build [entry] Build native executable
run [entry] [-- args...] Build and run executable
GLOBAL OPTIONS:
-h, --help Show help
-v, --version Show version
-V, --verbose Verbose output
-q, --quiet Suppress output
-c, --config <file> Config file path (default: tsonic.json)
--runtime <mode> Runtime mode: js (default) or dotnet
EMIT/BUILD/RUN OPTIONS:
-s, --src <dir> Source root directory
-o, --out <path> Output directory (emit) or file (build)
-n, --namespace <ns> Root namespace override
-r, --rid <rid> Runtime identifier (e.g., linux-x64)
-O, --optimize <level> Optimization: size or speed
-k, --keep-temp Keep build artifacts
--no-strip Keep debug symbols
PROJECT INIT OPTIONS:
--runtime <mode> Runtime mode: js (default) or dotnet
--skip-types Skip installing type declarations
--types-version <ver> Version of type declarations to install
EXAMPLES:
tsonic project init
tsonic project init --runtime dotnet
tsonic emit src/App.ts
tsonic build src/App.ts --rid linux-x64
tsonic run src/App.ts -- --arg1 value1
LEARN MORE:
Documentation: https://tsonic.dev/docs
GitHub: https://github.com/tsoniclang/tsonic
`);
};
// packages/cli/dist/cli/parser.js
var parseArgs = (args2) => {
const options = {};
let command = "";
let entryFile;
const programArgs = [];
let captureProgramArgs = false;
for (let i = 0; i < args2.length; i++) {
const arg = args2[i];
if (!arg)
continue;
if (arg === "--") {
captureProgramArgs = true;
continue;
}
if (captureProgramArgs) {
programArgs.push(arg);
continue;
}
if (!command && !arg.startsWith("-")) {
command = arg;
const nextArg = args2[i + 1];
if (command === "project" && nextArg === "init") {
command = "project:init";
i++;
}
continue;
}
if (command && !entryFile && !arg.startsWith("-")) {
entryFile = arg;
continue;
}
switch (arg) {
case "-h":
case "--help":
options.verbose = true;
return { command: "help", options: {} };
case "-v":
case "--version":
return { command: "version", options: {} };
case "-V":
case "--verbose":
options.verbose = true;
break;
case "-q":
case "--quiet":
options.quiet = true;
break;
case "-c":
case "--config":
options.config = args2[++i] ?? "";
break;
case "-s":
case "--src":
options.src = args2[++i] ?? "";
break;
case "-o":
case "--out":
options.out = args2[++i] ?? "";
break;
case "-n":
case "--namespace":
options.namespace = args2[++i] ?? "";
break;
case "-r":
case "--rid":
options.rid = args2[++i] ?? "";
break;
case "--runtime":
options.runtime = args2[++i] ?? "js";
break;
case "--skip-types":
options.skipTypes = true;
break;
case "--types-version":
options.typesVersion = args2[++i] ?? "";
break;
case "-O":
case "--optimize":
options.optimize = args2[++i] ?? "speed";
break;
case "-k":
case "--keep-temp":
options.keepTemp = true;
break;
case "--no-strip":
options.noStrip = true;
break;
case "-L":
case "--lib":
{
const libPath = args2[++i] ?? "";
if (libPath) {
options.lib = options.lib || [];
options.lib.push(libPath);
}
}
break;
}
}
return { command, entryFile, options, programArgs };
};
// packages/cli/dist/cli/dispatcher.js
import { dirname as dirname8, join as join13 } from "node:path";
// packages/backend/dist/project-generator.js
var formatPackageReferences = (packages) => {
if (packages.length === 0) {
return "";
}
const refs = packages.map((pkg) => ` <PackageReference Include="${pkg.name}" Version="${pkg.version}" />`).join("\n");
return `
<ItemGroup>
${refs}
</ItemGroup>`;
};
var formatAssemblyReferences = (refs) => {
if (refs.length === 0) {
return "";
}
const refElements = refs.map((ref) => ` <Reference Include="${ref.name}">
<HintPath>${ref.hintPath}</HintPath>
</Reference>`).join("\n");
return `
<ItemGroup>
${refElements}
</ItemGroup>`;
};
var capitalizeFirst = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
var generatePackageMetadata = (metadata) => {
const authors = metadata.authors.join(";");
const tags = metadata.tags?.join(";") || "";
return `
<PackageId>${metadata.id}</PackageId>
<Version>${metadata.version}</Version>
<Authors>${authors}</Authors>
<Description>${metadata.description}</Description>${metadata.projectUrl ? `
<PackageProjectUrl>${metadata.projectUrl}</PackageProjectUrl>` : ""}${metadata.license ? `
<PackageLicenseExpression>${metadata.license}</PackageLicenseExpression>` : ""}${tags ? `
<PackageTags>${tags}</PackageTags>` : ""}`;
};
var generateExecutableProperties = (config, execConfig) => {
const nativeAotSettings = execConfig.nativeAot ? `
<!-- NativeAOT settings -->
<PublishAot>true</PublishAot>
<PublishSingleFile>${execConfig.singleFile}</PublishSingleFile>
<PublishTrimmed>${execConfig.trimmed}</PublishTrimmed>
<InvariantGlobalization>${execConfig.invariantGlobalization}</InvariantGlobalization>
<StripSymbols>${execConfig.stripSymbols}</StripSymbols>
<!-- Optimization -->
<OptimizationPreference>${capitalizeFirst(execConfig.optimization)}</OptimizationPreference>
<IlcOptimizationPreference>${capitalizeFirst(execConfig.optimization)}</IlcOptimizationPreference>` : `
<PublishSingleFile>${execConfig.singleFile}</PublishSingleFile>
<SelfContained>${execConfig.selfContained}</SelfContained>`;
return ` <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>${config.dotnetVersion}</TargetFramework>
<RootNamespace>${config.rootNamespace}</RootNamespace>
<AssemblyName>${config.outputName}</AssemblyName>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>${nativeAotSettings}
</PropertyGroup>`;
};
var generateLibraryProperties = (config, libConfig) => {
const targetFrameworks = libConfig.targetFrameworks.join(";");
const isMultiTarget = libConfig.targetFrameworks.length > 1;
const targetProp = isMultiTarget ? `<TargetFrameworks>${targetFrameworks}</TargetFrameworks>` : `<TargetFramework>${libConfig.targetFrameworks[0]}</TargetFramework>`;
const docSettings = libConfig.generateDocumentation ? `
<GenerateDocumentationFile>true</GenerateDocumentationFile>` : "";
const symbolSettings = libConfig.includeSymbols ? `
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>` : `
<DebugType>none</DebugType>`;
const packageSettings = libConfig.packable && libConfig.packageMetadata ? generatePackageMetadata(libConfig.packageMetadata) : "";
return ` <PropertyGroup>
<OutputType>Library</OutputType>
${targetProp}
<RootNamespace>${config.rootNamespace}</RootNamespace>
<AssemblyName>${config.outputName}</AssemblyName>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>${docSettings}${symbolSettings}
<IsPackable>${libConfig.packable}</IsPackable>${packageSettings}
</PropertyGroup>`;
};
var generateConsoleAppProperties = (config, consoleConfig) => {
return ` <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>${consoleConfig.targetFramework}</TargetFramework>
<RootNamespace>${config.rootNamespace}</RootNamespace>
<AssemblyName>${config.outputName}</AssemblyName>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>
<PublishSingleFile>${consoleConfig.singleFile}</PublishSingleFile>
<SelfContained>${consoleConfig.selfContained}</SelfContained>
</PropertyGroup>`;
};
var generatePropertyGroup = (config, outputConfig) => {
switch (outputConfig.type) {
case "executable":
return generateExecutableProperties(config, outputConfig);
case "library":
return generateLibraryProperties(config, outputConfig);
case "console-app":
return generateConsoleAppProperties(config, outputConfig);
}
};
var generateCsproj = (config) => {
const packageRefs = formatPackageReferences(config.packages);
const assemblyRefs = formatAssemblyReferences(config.assemblyReferences ?? []);
const runtimeRef = config.runtimePath ? `
<ItemGroup>
<ProjectReference Include="${config.runtimePath}" />
</ItemGroup>` : "";
const propertyGroup = generatePropertyGroup(config, config.outputConfig);
return `<Project Sdk="Microsoft.NET.Sdk">
${propertyGroup}${packageRefs}${assemblyRefs}${runtimeRef}
</Project>
`;
};
// packages/backend/dist/program-generator.js
var generateProgramCs = (entryInfo) => {
const returnType = entryInfo.isAsync ? "async Task" : "void";
const awaitKeyword = entryInfo.isAsync ? "await " : "";
const usings = ["using System;", "using System.Threading.Tasks;"];
if (entryInfo.runtime !== "dotnet") {
usings.push("using Tsonic.Runtime;");
}
usings.push(`using ${entryInfo.namespace};`);
return `${usings.join("\n")}
public static class Program
{
public static ${returnType} Main(string[] args)
{
${awaitKeyword}${entryInfo.className}.${entryInfo.methodName}();
}
}
`;
};
// packages/backend/dist/dotnet.js
import { spawnSync } from "child_process";
var checkDotnetInstalled = () => {
const result = spawnSync("dotnet", ["--version"], {
encoding: "utf-8"
});
if (result.error) {
return {
ok: false,
error: ".NET SDK not found. Install from https://dot.net"
};
}
if (result.status !== 0) {
return {
ok: false,
error: "dotnet command failed",
stderr: result.stderr
};
}
return {
ok: true,
stdout: result.stdout.trim()
};
};
var detectRid = () => {
const platform = process.platform;
const arch = process.arch;
const ridMap = {
"darwin-x64": "osx-x64",
"darwin-arm64": "osx-arm64",
"linux-x64": "linux-x64",
"linux-arm64": "linux-arm64",
"win32-x64": "win-x64",
"win32-arm64": "win-arm64"
};
const key = `${platform}-${arch}`;
return ridMap[key] || "linux-x64";
};
// packages/cli/dist/config.js
import { readFileSync, existsSync } from "node:fs";
import { join, resolve, dirname } from "node:path";
var loadConfig = (configPath) => {
if (!existsSync(configPath)) {
return {
ok: false,
error: `Config file not found: ${configPath}`
};
}
try {
const content = readFileSync(configPath, "utf-8");
const config = JSON.parse(content);
if (!config.rootNamespace) {
return {
ok: false,
error: "tsonic.json: 'rootNamespace' is required"
};
}
return { ok: true, value: config };
} catch (error2) {
return {
ok: false,
error: `Failed to parse tsonic.json: ${error2 instanceof Error ? error2.message : String(error2)}`
};
}
};
var findConfig = (startDir) => {
let currentDir = resolve(startDir);
while (true) {
const configPath = join(currentDir, "tsonic.json");
if (existsSync(configPath)) {
return configPath;
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
return null;
}
currentDir = parentDir;
}
};
var autoDetectOutputType = (entryPoint) => {
if (!entryPoint) {
return "library";
}
return "executable";
};
var resolveOutputConfig = (config, cliOptions, entryPoint) => {
const configOutput = config.output ?? {};
const outputType = cliOptions.type ?? configOutput.type ?? autoDetectOutputType(entryPoint);
const baseConfig = {
type: outputType,
name: configOutput.name ?? config.outputName
};
if (outputType === "executable") {
return {
...baseConfig,
nativeAot: cliOptions.noAot ? false : configOutput.nativeAot ?? true,
singleFile: cliOptions.singleFile ?? configOutput.singleFile ?? true,
trimmed: configOutput.trimmed ?? true,
stripSymbols: cliOptions.noStrip ? false : configOutput.stripSymbols ?? true,
optimization: cliOptions.optimize ?? configOutput.optimization ?? "speed",
invariantGlobalization: config.buildOptions?.invariantGlobalization ?? true,
selfContained: cliOptions.selfContained ?? configOutput.selfContained ?? true
};
}
if (outputType === "library") {
return {
...baseConfig,
targetFrameworks: configOutput.targetFrameworks ?? [
config.dotnetVersion ?? "net10.0"
],
generateDocumentation: cliOptions.generateDocs ?? configOutput.generateDocumentation ?? true,
includeSymbols: cliOptions.includeSymbols ?? configOutput.includeSymbols ?? true,
packable: cliOptions.pack ?? configOutput.packable ?? false,
package: configOutput.package
};
}
return {
...baseConfig,
singleFile: cliOptions.singleFile ?? configOutput.singleFile ?? true,
selfContained: cliOptions.selfContained ?? configOutput.selfContained ?? true
};
};
var resolveConfig = (config, cliOptions, projectRoot, entryFile) => {
const entryPoint = entryFile ?? config.entryPoint;
const sourceRoot = cliOptions.src ?? config.sourceRoot ?? (entryPoint ? dirname(entryPoint) : "src");
const runtime = config.runtime ?? "js";
const defaultTypeRoots = runtime === "js" ? ["node_modules/@tsonic/js-globals"] : ["node_modules/@tsonic/dotnet-globals"];
const typeRoots = config.dotnet?.typeRoots ?? defaultTypeRoots;
const configLibraries = config.dotnet?.libraries ?? [];
const cliLibraries = cliOptions.lib ?? [];
const libraries = [...configLibraries, ...cliLibraries];
const outputConfig = resolveOutputConfig(config, cliOptions, entryPoint);
return {
rootNamespace: cliOptions.namespace ?? config.rootNamespace,
entryPoint,
projectRoot,
sourceRoot,
outputDirectory: config.outputDirectory ?? "generated",
outputName: cliOptions.out ?? config.outputName ?? "app",
rid: cliOptions.rid ?? config.rid ?? detectRid(),
dotnetVersion: config.dotnetVersion ?? "net10.0",
optimize: cliOptions.optimize ?? config.optimize ?? "speed",
runtime: config.runtime ?? "js",
// Only include user-specified packages
// Runtime DLLs are bundled with @tsonic/tsonic and added as assembly references
packages: config.dotnet?.packages ?? config.packages ?? [],
outputConfig,
stripSymbols: cliOptions.noStrip ? false : config.buildOptions?.stripSymbols ?? true,
invariantGlobalization: config.buildOptions?.invariantGlobalization ?? true,
keepTemp: cliOptions.keepTemp ?? false,
verbose: cliOptions.verbose ?? false,
quiet: cliOptions.quiet ?? false,
typeRoots,
libraries
};
};
// packages/cli/dist/commands/init.js
import { writeFileSync, existsSync as existsSync2, readFileSync as readFileSync2, mkdirSync } from "node:fs";
import { join as join2 } from "node:path";
import { spawnSync as spawnSync2 } from "node:child_process";
var DEFAULT_GITIGNORE = `# .NET build artifacts
generated/bin/
generated/obj/
# Optional: Uncomment to ignore generated C# files
# generated/**/*.cs
# Output executables
*.exe
app
# Dependencies
node_modules/
`;
var SAMPLE_MAIN_TS_JS = `export function main(): void {
console.log("Hello from Tsonic!");
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
console.log("Doubled:", doubled.join(", "));
}
`;
var SAMPLE_MAIN_TS_DOTNET = `import { Console } from "@tsonic/dotnet/System";
import { File } from "@tsonic/dotnet/System.IO";
export function main(): void {
Console.writeLine("Reading README.md...");
const content = File.readAllText("./README.md");
Console.writeLine(content);
}
`;
var SAMPLE_README = `# My Tsonic Project
This is a sample Tsonic project that demonstrates .NET interop.
## Getting Started
Build and run the project:
\`\`\`bash
npm run build
./app
\`\`\`
Or run directly:
\`\`\`bash
npm run dev
\`\`\`
## Project Structure
- \`src/main.ts\` - Entry point
- \`tsonic.json\` - Project configuration
- \`generated/\` - Generated C# code (gitignored)
`;
var CLI_PACKAGE = { name: "@tsonic/tsonic", version: "latest" };
var getTypePackageInfo = (runtime) => {
if (runtime === "js") {
return {
packages: [
CLI_PACKAGE,
{ name: "@tsonic/js-globals", version: "latest" },
{ name: "@tsonic/types", version: "latest" }
],
typeRoots: ["node_modules/@tsonic/js-globals"]
};
}
return {
packages: [
CLI_PACKAGE,
{ name: "@tsonic/dotnet-globals", version: "latest" },
{ name: "@tsonic/dotnet", version: "latest" }
],
typeRoots: ["node_modules/@tsonic/dotnet-globals"]
};
};
var generateConfig = (includeTypeRoots, runtime) => {
const config = {
$schema: "https://tsonic.dev/schema/v1.json",
rootNamespace: "MyApp",
entryPoint: "src/App.ts",
sourceRoot: "src",
outputDirectory: "generated",
outputName: "app",
runtime,
optimize: "speed",
packages: [],
buildOptions: {
stripSymbols: true,
invariantGlobalization: true
}
};
if (includeTypeRoots) {
const typeInfo = getTypePackageInfo(runtime);
config.dotnet = {
typeRoots: typeInfo.typeRoots
};
}
return JSON.stringify(config, null, 2) + "\n";
};
var createOrUpdatePackageJson = (packageJsonPath) => {
let packageJson;
if (existsSync2(packageJsonPath)) {
const existing = readFileSync2(packageJsonPath, "utf-8");
packageJson = JSON.parse(existing);
if (!packageJson.name) {
packageJson.name = "my-tsonic-app";
}
if (!packageJson.version) {
packageJson.version = "1.0.0";
}
if (!packageJson.type) {
packageJson.type = "module";
}
const existingScripts = packageJson.scripts || {};
packageJson.scripts = {
...existingScripts,
build: "tsonic build src/App.ts",
dev: "tsonic run src/App.ts"
};
if (!packageJson.devDependencies) {
packageJson.devDependencies = {};
}
} else {
packageJson = {
name: "my-tsonic-app",
version: "1.0.0",
type: "module",
scripts: {
build: "tsonic build src/App.ts",
dev: "tsonic run src/App.ts"
},
devDependencies: {}
};
}
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
};
var installPackage = (packageName, version) => {
const result = spawnSync2("npm", ["install", "--save-dev", `${packageName}@${version}`], {
stdio: "inherit",
encoding: "utf-8"
});
if (result.status !== 0) {
return {
ok: false,
error: `Failed to install ${packageName}@${version}`
};
}
return { ok: true, value: void 0 };
};
var initProject = (cwd, options = {}) => {
const runtime = options.runtime ?? "js";
const tsonicJsonPath = join2(cwd, "tsonic.json");
const gitignorePath = join2(cwd, ".gitignore");
const srcDir = join2(cwd, "src");
const appTsPath = join2(srcDir, "App.ts");
const readmePath = join2(cwd, "README.md");
const packageJsonPath = join2(cwd, "package.json");
if (existsSync2(tsonicJsonPath)) {
return {
ok: false,
error: "tsonic.json already exists. Project is already initialized."
};
}
try {
const packageJsonExists = existsSync2(packageJsonPath);
createOrUpdatePackageJson(packageJsonPath);
console.log(packageJsonExists ? "\u2713 Updated package.json" : "\u2713 Created package.json");
const shouldInstallTypes = !options.skipTypes;
const typeInfo = getTypePackageInfo(runtime);
if (shouldInstallTypes) {
for (const pkg of typeInfo.packages) {
const version = options.typesVersion ?? pkg.version;
console.log(`Installing type declarations (${pkg.name}@${version})...`);
const installResult = installPackage(pkg.name, version);
if (!installResult.ok) {
return installResult;
}
console.log(`\u2713 Installed ${pkg.name}`);
}
}
const config = generateConfig(shouldInstallTypes, runtime);
writeFileSync(tsonicJsonPath, config, "utf-8");
console.log("\u2713 Created tsonic.json");
if (existsSync2(gitignorePath)) {
const existing = readFileSync2(gitignorePath, "utf-8");
if (!existing.includes("generated/")) {
writeFileSync(gitignorePath, existing + "\n" + DEFAULT_GITIGNORE, "utf-8");
console.log("\u2713 Updated .gitignore");
}
} else {
writeFileSync(gitignorePath, DEFAULT_GITIGNORE, "utf-8");
console.log("\u2713 Created .gitignore");
}
if (!existsSync2(srcDir)) {
mkdirSync(srcDir, { recursive: true });
}
if (!existsSync2(appTsPath)) {
const sampleCode = runtime === "js" ? SAMPLE_MAIN_TS_JS : SAMPLE_MAIN_TS_DOTNET;
writeFileSync(appTsPath, sampleCode, "utf-8");
console.log("\u2713 Created src/App.ts");
}
if (!existsSync2(readmePath)) {
writeFileSync(readmePath, SAMPLE_README, "utf-8");
console.log("\u2713 Created README.md");
}
console.log("\n\u2713 Project initialized successfully!");
console.log("\nNext steps:");
console.log(" npm run build # Build executable");
console.log(" npm run dev # Run directly");
return { ok: true, value: void 0 };
} catch (error2) {
return {
ok: false,
error: `Failed to initialize project: ${error2 instanceof Error ? error2.message : String(error2)}`
};
}
};
// packages/cli/dist/commands/emit.js
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync10, readdirSync as readdirSync6, copyFileSync } from "node:fs";
import { join as join10, dirname as dirname7, relative as relative4, resolve as resolve6 } from "node:path";
// packages/frontend/dist/types/diagnostic.js
var createDiagnostic = (code, severity, message, location, hint, relatedLocations) => ({
code,
severity,
message,
location,
hint,
relatedLocations
});
var isError = (diagnostic) => diagnostic.severity === "error";
var createDiagnosticsCollector = () => ({
diagnostics: [],
hasErrors: false
});
var addDiagnostic = (collector, diagnostic) => ({
diagnostics: [...collector.diagnostics, diagnostic],
hasErrors: collector.hasErrors || isError(diagnostic)
});
// packages/frontend/dist/types/result.js
var ok = (value) => ({
ok: true,
value
});
var error = (error2) => ({
ok: false,
error: error2
});
// packages/frontend/dist/program/config.js
import * as ts from "typescript";
var defaultTsConfig = {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.NodeNext,
moduleResolution: ts.ModuleResolutionKind.NodeNext,
// We use the globals from the BCL bindings directory instead of npm packages
// The BCL bindings include a globals.d.ts that provides minimal types
noLib: true,
types: [],
// No npm packages - globals come from BCL bindings typeRoots
strict: false,
// Disabled to allow .NET type usage
esModuleInterop: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
allowJs: false,
checkJs: false,
noEmit: true,
resolveJsonModule: false,
isolatedModules: true,
// Re-enabled - safe now that DOM globals are gone
verbatimModuleSyntax: false,
// Disabled to allow .NET type imports
noImplicitAny: false,
// Allow any for .NET types
allowImportingTsExtensions: true
// ESM requires .ts/.js extensions
};
// packages/frontend/dist/program/metadata.js
import * as fs from "node:fs";
import * as path from "node:path";
// packages/frontend/dist/dotnet-metadata.js
var DotnetMetadataRegistry = class {
constructor() {
this.metadata = /* @__PURE__ */ new Map();
}
/**
* Load a metadata file and add its types to the registry
*/
loadMetadataFile(_filePath, content) {
for (const [typeName, typeMetadata] of Object.entries(content.types)) {
this.metadata.set(typeName, typeMetadata);
}
}
/**
* Look up metadata for a .NET type by fully-qualified name
*/
getTypeMetadata(qualifiedName) {
return this.metadata.get(qualifiedName);
}
/**
* Look up metadata for a specific member of a .NET type
* @param qualifiedTypeName Fully-qualified type name (e.g., "System.IO.StringWriter")
* @param memberSignature Member signature (e.g., "ToString()" or "Write(string)")
*/
getMemberMetadata(qualifiedTypeName, memberSignature) {
const typeMetadata = this.metadata.get(qualifiedTypeName);
if (!typeMetadata) {
return void 0;
}
return typeMetadata.members[memberSignature];
}
/**
* Check if a member is virtual (can be overridden)
*/
isVirtualMember(qualifiedTypeName, memberSignature) {
const memberMetadata = this.getMemberMetadata(qualifiedTypeName, memberSignature);
return memberMetadata?.virtual === true;
}
/**
* Check if a member is sealed (cannot be overridden)
*/
isSealedMember(qualifiedTypeName, memberSignature) {
const memberMetadata = this.getMemberMetadata(qualifiedTypeName, memberSignature);
return memberMetadata?.sealed === true;
}
/**
* Get all loaded type names
*/
getAllTypeNames() {
return Array.from(this.metadata.keys());
}
/**
* Clear all loaded metadata
*/
clear() {
this.metadata.clear();
}
};
// packages/frontend/dist/program/metadata.js
var scanForDeclarationFiles = (dir) => {
if (!fs.existsSync(dir)) {
return [];
}
const results = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...scanForDeclarationFiles(fullPath));
} else if (entry.name.endsWith(".d.ts")) {
results.push(fullPath);
}
}
return results;
};
var loadDotnetMetadata = (typeRoots) => {
const registry = new DotnetMetadataRegistry();
for (const typeRoot of typeRoots) {
const absoluteRoot = path.resolve(typeRoot);
const declFiles = scanForDeclarationFiles(absoluteRoot);
for (const declPath of declFiles) {
const metadataPath = declPath.replace(/\.d\.ts$/, ".metadata.json");
try {
if (fs.existsSync(metadataPath)) {
const content = fs.readFileSync(metadataPath, "utf-8");
const metadataFile = JSON.parse(content);
registry.loadMetadataFile(metadataPath, metadataFile);
}
} catch (err) {
console.warn(`Failed to load metadata from ${metadataPath}:`, err);
}
}
}
return registry;
};
// packages/frontend/dist/program/bindings.js
import * as fs2 from "node:fs";
import * as path2 from "node:path";
var isFullBindingManifest = (manifest) => {
return "assembly" in manifest && "namespaces" in manifest;
};
var isTsbindgenBindingFile = (manifest) => {
return "namespace" in manifest && "types" in manifest && !("namespaces" in manifest);
};
var validateBindingFile = (obj, filePath) => {
if (obj === null || typeof obj !== "object") {
return `${filePath}: Expected object, got ${typeof obj}`;
}
const manifest = obj;
if ("namespace" in manifest && "types" in manifest) {
if (typeof manifest.namespace !== "string") {
return `${filePath}: 'namespace' must be a string`;
}
if (!Array.isArray(manifest.types)) {
return `${filePath}: 'types' must be an array`;
}
return void 0;
}
if ("assembly" in manifest && "namespaces" in manifest) {
if (typeof manifest.assembly !== "string") {
return `${filePath}: 'assembly' must be a string`;
}
if (!Array.isArray(manifest.namespaces)) {
return `${filePath}: 'namespaces' must be an array`;
}
return void 0;
}
if ("bindings" in manifest) {
if (typeof manifest.bindings !== "object" || manifest.bindings === null) {
return `${filePath}: 'bindings' must be an object`;
}
return void 0;
}
return `${filePath}: Unrecognized binding file format. Expected tsbindgen (namespace+types), full (assembly+namespaces), or legacy (bindings) format.`;
};
var BindingRegistry = class {
constructor() {
this.simpleBindings = /* @__PURE__ */ new Map();
this.namespaces = /* @__PURE__ */ new Map();
this.types = /* @__PURE__ */ new Map();
this.members = /* @__PURE__ */ new Map();
}
/**
* Load a binding manifest file and add its bindings to the registry
* Supports legacy, full, and tsbindgen formats
*/
addBindings(_filePath, manifest) {
if (isFullBindingManifest(manifest)) {
for (const ns of manifest.namespaces) {
this.namespaces.set(ns.alias, ns);
for (const type of ns.types) {
this.types.set(type.alias, type);
for (const member of type.members) {
const key = `${type.alias}.${member.alias}`;
this.members.set(key, member);
}
}
}
} else if (isTsbindgenBindingFile(manifest)) {
for (const tsbType of manifest.types) {
const members = [];
for (const method of tsbType.methods) {
members.push({
kind: "method",
name: method.clrName,
// alias = tsEmitName (what TS code uses, e.g., "add")
alias: method.tsEmitName,
binding: {
assembly: method.declaringAssemblyName,
type: method.declaringClrType,
// member = clrName (what C# emits, e.g., "Add")
member: method.clrName
}
});
}
for (const prop of tsbType.properties) {
members.push({
kind: "property",
name: prop.clrName,
alias: prop.tsEmitName,
binding: {
assembly: prop.declaringAssemblyName,
type: prop.declaringClrType,
member: prop.clrName
}
});
}
for (const field of tsbType.fields) {
members.push({
kind: "property",
name: field.clrName,
alias: field.tsEmitName,
binding: {
assembly: field.declaringAssemblyName,
type: field.declaringClrType,
member: field.clrName
}
});
}
const typeBinding = {
name: tsbType.clrName,
alias: tsbType.tsEmitName,
kind: "class",
// Default to class; could be refined with more metadata
members
};
this.types.set(typeBinding.alias, typeBinding);
const arityMatch = typeBinding.alias.match(/^(.+)_(\d+)$/);
const simpleAlias = arityMatch ? arityMatch[1] : null;
if (simpleAlias && simpleAlias !== typeBinding.alias) {
this.types.set(simpleAlias, typeBinding);
}
for (const member of members) {
const tsKey = `${typeBinding.alias}.${member.alias}`;
this.members.set(tsKey, member);
if (simpleAlias) {
const simpleKey = `${simpleAlias}.${member.alias}`;
this.members.set(simpleKey, member);
}
if (member.alias !== member.name) {
const clrKey = `${typeBinding.alias}.${member.name}`;
this.members.set(clrKey, member);
if (simpleAlias) {
const simpleClrKey = `${simpleAlias}.${member.name}`;
this.members.set(simpleClrKey, member);
}
}
}
}
} else {
for (const [name, descriptor] of Object.entries(manifest.bindings)) {
this.simpleBindings.set(name, descriptor);
}
}
}
/**
* Look up a simple global/module binding (legacy format)
*/
getBinding(name) {
return this.simpleBindings.get(name);
}
/**
* Look up a namespace binding by TS alias
*/
getNamespace(tsAlias) {
return this.namespaces.get(tsAlias);
}
/**
* Look up a type binding by TS alias
*/
getType(tsAlias) {
return this.types.get(tsAlias);
}
/**
* Look up a member binding by TS type alias and member alias
*/
getMember(typeAlias, memberAlias) {
const key = `${typeAlias}.${memberAlias}`;
return this.members.get(key);
}
/**
* Get all loaded simple bindings (legacy)
*/
getAllBindings() {
return Array.from(this.simpleBindings.entries());
}
/**
* Get all loaded namespaces
*/
getAllNamespaces() {
return Array.from(this.namespaces.values());
}
/**
* Clear all loaded bindings
*/
clear() {
this.simpleBindings.clear();
this.namespaces.clear();
this.types.clear();
this.members.clear();
}
};
var scanForDeclarationFiles2 = (dir) => {
if (!fs2.existsSync(dir)) {
return [];
}
const results = [];
const entries = fs2.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path2.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...scanForDeclarationFiles2(fullPath));
} else if (entry.name.endsWith(".d.ts")) {
results.push(fullPath);
}
}
return results;
};
var loadBindings = (typeRoots) => {
const registry = new BindingRegistry();
for (const typeRoot of typeRoots) {
const absoluteRoot = path2.resolve(typeRoot);
if (!fs2.existsSync(absoluteRoot)) {
continue;
}
const rootManifests = fs2.readdirSync(absoluteRoot).filter((f) => f.endsWith(".bindings.json")).map((f) => path2.join(absoluteRoot, f));
for (const manifestPath of rootManifests) {
loadBindingsFromPath(registry, manifestPath);
}
const declFiles = scanForDeclarationFiles2(absoluteRoot);
for (const declPath of declFiles) {
const manifestPath = declPath.replace(/\.d\.ts$/, ".bindings.json");
loadBindingsFromPath(registry, manifestPath);
if (path2.basename(declPath) === "index.d.ts") {
const dirBindings = path2.join(path2.dirname(declPath), "bindings.json");
loadBindingsFromPath(registry, dirBindings);
}
}
}
return registry;
};
var loadBindingsFromPath = (registry, bindingsPath) => {
try {
if (fs2.existsSync(bindingsPath)) {
const content = fs2.readFileSync(bindingsPath, "utf-8");
const parsed = JSON.parse(content);
const validationError = validateBindingFile(parsed, bindingsPath);
if (validationError) {
console.warn(`Invalid bindings file: ${validationError}`);
return;
}
registry.addBindings(bindingsPath, parsed);
}
} catch (err) {
if (err instanceof SyntaxError) {
console.warn(`Failed to parse bindings from ${bindingsPath}: Invalid JSON - ${err.message}`);
} else {
console.warn(`Failed to load bindings from ${bindingsPath}:`, err);
}
}
};
var loadAllDiscoveredBindings = (registry, discoveredPaths) => {
for (const bindingsPath of discoveredPaths) {
loadBindingsFromPath(registry, bindingsPath);
}
};
// packages/frontend/dist/program/diagnostics.js
import * as ts2 from "typescript";
var collectTsDiagnostics = (program) => {
const tsDiagnostics = [
...program.getConfigFileParsingDiagnostics(),
...program.getOptionsDiagnostics(),
...program.getSyntacticDiagnostics(),
...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics()
];
return tsDiagnostics.reduce((collector, tsDiag) => {
const diagnostic = convertTsDiagnostic(tsDiag);
return diagnostic ? addDiagnostic(collector, diagnostic) : collector;
}, createDiagnosticsCollector());
};
var convertTsDiagnostic = (tsDiag) => {
if (tsDiag.category === ts2.DiagnosticCategory.Suggestion) {
return null;
}
const message = ts2.flattenDiagnosticMessageText(tsDiag.messageText, "\n");
if (message.includes("only refers to a type, but is being used as a value") && tsDiag.file) {
const sourceText = tsDiag.file.getText();
if (sourceText.includes('from "System') || sourceText.includes('from "Microsoft') || sourceText.includes('from "Windows')) {
return null;
}
}
const severity = tsDiag.category === ts2.DiagnosticCategory.Error ? "error" : tsDiag.category === ts2.DiagnosticCategory.Warning ? "warning" : "info";
const location = tsDiag.file && tsDiag.start !== void 0 ? getSourceLocation(tsDiag.file, tsDiag.start, tsDiag.length ?? 1) : void 0;
return createDiagnostic(
"TSN2001",
// Generic TypeScript error
severity,
message,
location
);
};
var getSourceLocation = (file, start, length) => {
const { line, character } = file.getLineAndCharacterOfPosition(start);
return {
file: file.fileName,
line: line + 1,
column: character + 1,
length
};
};
// packages/frontend/dist/program/creation.js
import * as ts3 from "typescript";
import * as path3 from "node:path";
import * as fs3 from "node:fs";
// packages/frontend/dist/resolver/clr-bindings-resolver.js
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
import { dirname as dirname3, join as join5 } from "node:path";
import { createRequire } from "node:module";
var ClrBindingsResolver = class {
constructor(baseDir) {
this.pkgRootCache = /* @__PURE__ */ new Map();
this.bindingsExistsCache = /* @__PURE__ */ new Map();
this.namespaceCache = /* @__PURE__ */ new Map();
this.require = createRequire(join5(baseDir, "package.json"));
}
/**
* Resolve an import specifier to determine if it's a CLR namespace import
*/
resolve(moduleSpecifier) {
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
return { isClr: false };
}
const parsed = this.parseModuleSpecifier(moduleSpecifier);
if (!parsed) {
return { isClr: false };
}
const { packageName, subpath } = parsed;
if (!subpath) {
return { isClr: false };
}
const pkgRoot = this.resolvePkgRoot(packageName);
if (!pkgRoot) {
return { isClr: false };
}
const bindingsPath = join5(pkgRoot, subpath, "bindings.json");
if (!this.hasBindings(bindingsPath)) {
return { isClr: false };
}
const resolvedNamespace = this.extractNamespace(bindingsPath, subpath);
const metadataPath = join5(pkgRoot, subpath, "internal", "metadata.json");
const hasMetadata = this.fileExists(metadataPath);
return {
isClr: true,
packageName,
resolvedNamespace,
bindingsPath,
metadataPath: hasMetadata ? metadataPath : void 0
};
}
/**
* Parse a module specifier into package name and subpath
*
* Examples:
* - "@scope/pkg/System.IO" -> { packageName: "@scope/pkg", subpath: "System.IO" }
* - "mypkg/Namespace" -> { packageName: "mypkg", subpath: "Namespace" }
* - "@scope/pkg" -> null (no subpath)
* - "mypkg" -> null (no subpath)
*/
parseModuleSpecifier(spec) {
if (spec.startsWith("@")) {
const match = spec.match(/^(@[^/]+\/[^/]+)(?:\/(.+))?$/);
if (!match)
return null;
const packageName = match[1];
const subpath = match[2];
if (!packageName || !subpath)
return null;
return { packageName, subpath };
} else {
const slashIdx = spec.indexOf("/");
if (slashIdx === -1)
return null;
return {
packageName: spec.slice(0, slashIdx),
subpath: spec.slice(slashIdx + 1)
};
}
}
/**
* Resolve package root directory using Node resolution
*
* Uses require.resolve to find the package's package.json,
* then returns the directory containing it.
*/
resolvePkgRoot(packageName) {
const cached = this.pkgRootCache.get(packageName);
if (cached !== void 0) {
return cached;
}
try {
const pkgJsonPath = this.require.resolve(`${packageName}/package.json`);
const pkgRoot = dirname3(pkgJsonPath);
this.pkgRootCache.set(packageName, pkgRoot);
return pkgRoot;
} catch {
try {
const paths = this.require.resolve.paths(packageName);
if (paths) {
for (const searchPath of paths) {
const pkgDir = packageName.startsWith("@") ? join5(searchPath, packageName) : join5(searchPath, packageName);
if (existsSync5(join5(pkgDir, "package.json"))) {
this.pkgRootCache.set(packageName, pkgDir);
return pkgDir;
}
}
}
} catch {
}
this.pkgRootCache.set(packageName, null);
return null;
}
}
/**
* Check if bindings.json exists at the given path (cached)
*/
hasBindings(bindingsPath) {
const cached = this.bindingsExistsCache.get(bindingsPath);
if (cached !== void 0) {
return cached;
}
const exists = existsSync5(bindingsPath);
this.bindingsExistsCache.set(bindingsPath, exists);
return exists;
}
/**
* Extract the namespace from a bindings.json file (cached).
* Falls back to the subpath if namespace cannot be extracted.
*
* This reads the 'namespace' field from tsbindgen format files,
* which is the authoritative CLR namespace for the binding.
*/
extractNamespace(bindingsPath, fallback) {
const cached = this.namespaceCache.get(bindingsPath);
if (cached !== void 0) {
return cached ?? fallback;
}
try {
const content = readFileSync5(bindingsPath, "utf-8");
const parsed = JSON.parse(content);
if (parsed !== null && typeof parsed === "object" && "namespace" in parsed && typeof parsed.namespace === "string") {
const namespace = parsed.namespace;
this.namespaceCache.set(bindingsPath, namespace);
return namespace;
}
this.namespaceCache.set(bindingsPath, null);
return fallback;
} catch {
this.namespaceCache.set(bindingsPath, null);
return fallback;
}
}
/**
* Check if a file exists (not cached - used for optional files)
*/
fileExists(filePath) {
return existsSync5(filePath);
}
/**
* Get all discovered binding paths (for loading)
*/
getDiscoveredBindingPaths() {
const paths = /* @__PURE__ */ new Set();
for (const [path8, exists] of this.bindingsExistsCache) {
if (exists) {
paths.add(path8);
}
}
return paths;
}
};
var createClrBindingsResolver = (sourceRoot) => {
return new ClrBindingsResolver(sourceRoot);
};
// packages/frontend/dist/program/creation.js
var scanForDeclarationFiles3 = (dir) => {
if (!fs3.existsSync(dir)) {
return [];
}
const results = [];
const entries = fs3.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path3.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...scanForDeclarationFiles3(fullPath));
} else if (entry.name.endsWith(".d.ts")) {
results.push(fullPath);
}
}
return results;
};
var createCompilerOptions = (options) => {
const baseConfig = {
...defaultTsConfig,
strict: options.strict ?? true,
rootDir: options.sourceRoot
};
if (options.useStandardLib) {
return {
...baseConfig,
noLib: false,
types: void 0
// Use default type resolution
};
}
return baseConfig;
};
var createProgram2 = (filePaths, options) => {
const absolutePaths = filePaths.map((fp) => path3.resolve(fp));
const typeRoots = options.typeRoots ?? [
"node_modules/@tsonic/dotnet-types/types"
];
if (options.verbose && typeRoots.length > 0) {
console.log(`TypeRoots: ${typeRoots.join(", ")}`);
}
const declarationFiles = [];
for (const typeRoot of typeRoots) {
const absoluteRoot = path3.resolve(typeRoot);
declarationFiles.push(...scanForDeclarationFiles3(absoluteRoot));
}
const namespaceIndexFiles = [];
for (const typeRoot of typeRoots) {
const absoluteRoot = path3.resolve(typeRoot);
if (options.verbose) {
console.log(`Checking typeRoot: ${absoluteRoot}, exists: ${fs3.existsSync(absoluteRoot)}`);
}
if (fs3.existsSync(absoluteRoot)) {
const entries = fs3.readdirSync(absoluteRoot, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && !entry.name.startsWith("_") && !entry.name.startsWith("internal")) {
const indexPath = path3.join(absoluteRoot, entry.name, "index.d.ts");
if (fs3.existsSync(indexPath)) {
namespaceIndexFiles.push(indexPath);
if (options.verbose) {
console.log(` Found namespace: ${entry.name} -> ${indexPath}`);
}
}
}
}
}
}
const allFiles = [
...absolutePaths,
...declarationFiles,
...namespaceIndexFiles
];
const tsOptions = createCompilerOptions(options);
const host = ts3.createCompilerHost(tsOptions);
const namespaceFiles = /* @__PURE__ */ new Map();
for (const indexFile of namespaceIndexFiles) {
const dirName = path3.basename(path3.dirname(indexFile));
namespaceFiles.set(dirName, indexFile);
}
if (options.verbose && namespaceFiles.size > 0) {
console.log(`Found ${namespaceFiles.size} .NET namespace declarations`);
for (const [ns, file] of namespaceFiles) {
console.log(` ${ns} -> ${file}`);
}
}
const originalGetSourceFile = host.getSourceFile;
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
const baseName = path3.basename(fileName, path3.extname(fileName));
const declarationPath = namespaceFiles.get(baseName);
if (declarationPath !== void 0 && fileName.endsWith(".ts")) {
const virtualContent = `export * from '${declarationPath.replace(/\.d\.ts$/, "")}';`;
return ts3.createSourceFile(fileName, virtualContent, languageVersion, true);
}
return originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
};
const hostWithResolve = host;
hostWithResolve.resolveModuleNames = (moduleNames, containingFile) => {
return moduleNames.map((moduleName) => {
if (options.verbose) {
console.log(`Resolving module: ${moduleName} from ${containingFile}`);
}
const resolvedFile = namespaceFiles.get(moduleName);
if (resolvedFile !== void 0) {
if (options.verbose) {
console.log(` Resolved .NET namespace ${moduleName} to ${resolvedFile}`);
}
return {
resolvedFileName: resolvedFile,
isExternalLibraryImport: true
};
}
const result = ts3.resolveModuleName(moduleName, containingFile, tsOptions, host);
return result.resolvedModule;
});
};
const program = ts3.createProgram(allFiles, tsOptions, host);
const diagnostics = collectTsDiagnostics(program);
if (diagnostics.hasErrors) {
return error(diagnostics);
}
const sourceFiles = program.getSourceFiles().filter((sf) => !sf.isDeclarationFile && absolutePaths.includes(sf.fileName));
const metadata = loadDotnetMetadata(typeRoots);
const bindings = loadBindings(typeRoots);
const clrResolver = createClrBindingsResolver(options.projectRoot);
return ok({
program,
checker: program.getTypeChecker(),
options,
sourceFiles,
metadata,
bindings,
clrResolver
});
};
// packages/frontend/dist/program/dependency-graph.js
import * as ts40 from "typescript";
import { relative as relative3, resolve as resolve5 } from "path";
// packages/frontend/dist/ir/builder/orchestrator.js
import { relative as relative2 } from "path";
// packages/frontend/dist/ir/converters/statements/declarations/registry.js
var _metadataRegistry = new DotnetMetadataRegistry();
var _bindingRegistry = new BindingRegistry();
var setMetadataRegistry = (registry) => {
_metadataRegistry = registry;
};
var getMetadataRegistry = () => _metadataRegistry;
var setBindingRegistry = (registry) => {
_bindingRegistry = registry;
};
var getBindingRegistry = () => _bindingRegistry;
// packages/frontend/dist/resolver/namespace.js
import { dirname as dirname5, relative } from "path";
var getNamespaceFromPath = (filePath, sourceRoot, rootNamespace) => {
const fileDir = dirname5(filePath);
let relativePath = relative(sourceRoot, fileDir).replace(/\\/g, "/");
const parts = relativePath.split("/").filter((p) => p !== "" && p !== "." && p !== "src");
return parts.length === 0 ? rootNamespace : `${rootNamespace}.${parts.join(".")}`;
};
// packages/frontend/dist/resolver/naming.js
import * as path4 from "node:path";
var getClassNameFromPath = (filePath) => {
const basename7 = path4.basename(filePath, ".ts");
return basename7.replace(/-/g, "");
};
// packages/frontend/dist/ir/statement-converter.js
import * as ts34 from "typescript";
// packages/frontend/dist/ir/expression-converter.js
import * as ts21 from "typescript";
// packages/frontend/dist/ir/type-converter/orchestrator.js
import * as ts9 from "typescript";
// packages/frontend/dist/ir/type-converter/primitives.js
import * as ts4 from "typescript";
var convertPrimitiveKeyword = (kind) => {
switch (kind) {
case ts4.SyntaxKind.StringKeyword:
return { kind: "primitiveType", name: "string" };
case ts4.SyntaxKind.NumberKeyword:
return { kind: "primitiveType", name: "number" };
case ts4.SyntaxKind.BooleanKeyword:
return { kind: "primitiveType", name: "boolean" };
case ts4.SyntaxKind.NullKeyword:
return { kind: "primitiveType", name: "null" };
case ts4.SyntaxKind.UndefinedKeyword:
return { kind: "primitiveType", name: "undefined" };
case ts4.SyntaxKind.VoidKeyword:
return { kind: "