UNPKG

@tsonic/tsonic

Version:

TypeScript to C# to NativeAOT compiler

1,513 lines (1,461 loc) 339 kB
#!/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: "