@tsonic/cli 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,406 +0,0 @@
1
- /**
2
- * tsonic emit command - Generate C# code only
3
- */
4
-
5
- import {
6
- mkdirSync,
7
- writeFileSync,
8
- existsSync,
9
- readdirSync,
10
- copyFileSync,
11
- } from "node:fs";
12
- import { join, dirname, relative, resolve } from "node:path";
13
- import {
14
- buildModuleDependencyGraph,
15
- type Diagnostic,
16
- type IrModule,
17
- type CompilerOptions,
18
- } from "@tsonic/frontend";
19
- import { emitCSharpFiles } from "@tsonic/emitter";
20
- import {
21
- generateCsproj,
22
- generateProgramCs,
23
- type EntryInfo,
24
- type BuildConfig,
25
- type ExecutableConfig,
26
- type LibraryConfig,
27
- type AssemblyReference,
28
- } from "@tsonic/backend";
29
- import type { ResolvedConfig, Result } from "../types.js";
30
-
31
- /**
32
- * Find project .csproj file in current directory
33
- */
34
- const findProjectCsproj = (): string | null => {
35
- const cwd = process.cwd();
36
- const files = readdirSync(cwd);
37
- const csprojFile = files.find((f) => f.endsWith(".csproj"));
38
- return csprojFile ? join(cwd, csprojFile) : null;
39
- };
40
-
41
- /**
42
- * Find runtime DLLs from @tsonic/tsonic npm package
43
- * Returns assembly references for the csproj file
44
- */
45
- const findRuntimeDlls = (
46
- runtime: "js" | "dotnet",
47
- outputDir: string
48
- ): readonly AssemblyReference[] => {
49
- // Try to find @tsonic/tsonic package runtime directory
50
- const possiblePaths = [
51
- // From project's node_modules
52
- join(process.cwd(), "node_modules/@tsonic/tsonic/runtime"),
53
- // From CLI's node_modules (when installed globally or via npx)
54
- join(import.meta.dirname, "../../runtime"),
55
- join(import.meta.dirname, "../../../runtime"),
56
- ];
57
-
58
- let runtimeDir: string | null = null;
59
- for (const p of possiblePaths) {
60
- if (existsSync(p)) {
61
- runtimeDir = p;
62
- break;
63
- }
64
- }
65
-
66
- if (!runtimeDir) {
67
- return [];
68
- }
69
-
70
- const refs: AssemblyReference[] = [];
71
-
72
- // Calculate relative path from output directory to runtime directory
73
- const relativeRuntimeDir = relative(outputDir, runtimeDir);
74
-
75
- // Always include Tsonic.Runtime
76
- const runtimeDll = join(runtimeDir, "Tsonic.Runtime.dll");
77
- if (existsSync(runtimeDll)) {
78
- refs.push({
79
- name: "Tsonic.Runtime",
80
- hintPath: join(relativeRuntimeDir, "Tsonic.Runtime.dll"),
81
- });
82
- }
83
-
84
- // Include Tsonic.JSRuntime for js mode
85
- if (runtime === "js") {
86
- const jsRuntimeDll = join(runtimeDir, "Tsonic.JSRuntime.dll");
87
- if (existsSync(jsRuntimeDll)) {
88
- refs.push({
89
- name: "Tsonic.JSRuntime",
90
- hintPath: join(relativeRuntimeDir, "Tsonic.JSRuntime.dll"),
91
- });
92
- }
93
- }
94
-
95
- return refs;
96
- };
97
-
98
- /**
99
- * Extract entry point information from IR module
100
- */
101
- const extractEntryInfo = (
102
- entryModule: IrModule,
103
- runtime?: "js" | "dotnet"
104
- ): EntryInfo | null => {
105
- // Look for exported 'main' function
106
- for (const exp of entryModule.exports) {
107
- if (exp.kind === "declaration") {
108
- const decl = exp.declaration;
109
- if (decl.kind === "functionDeclaration" && decl.name === "main") {
110
- return {
111
- namespace: entryModule.namespace,
112
- className: entryModule.className,
113
- methodName: "main",
114
- isAsync: decl.isAsync,
115
- needsProgram: true,
116
- runtime,
117
- };
118
- }
119
- } else if (exp.kind === "named" && exp.name === "main") {
120
- // Named export of 'main'
121
- // Look in body for the function declaration
122
- for (const stmt of entryModule.body) {
123
- if (stmt.kind === "functionDeclaration" && stmt.name === "main") {
124
- return {
125
- namespace: entryModule.namespace,
126
- className: entryModule.className,
127
- methodName: "main",
128
- isAsync: stmt.isAsync,
129
- needsProgram: true,
130
- runtime,
131
- };
132
- }
133
- }
134
- }
135
- }
136
-
137
- // No main function found
138
- return null;
139
- };
140
-
141
- /**
142
- * Emit C# code from TypeScript
143
- */
144
- export const emitCommand = (
145
- config: ResolvedConfig
146
- ): Result<{ filesGenerated: number; outputDir: string }, string> => {
147
- const {
148
- entryPoint,
149
- outputDirectory,
150
- rootNamespace,
151
- sourceRoot,
152
- packages,
153
- typeRoots,
154
- } = config;
155
-
156
- // For libraries, entry point is optional
157
- if (!entryPoint && config.outputConfig.type !== "library") {
158
- return {
159
- ok: false,
160
- error: "Entry point is required for executable builds",
161
- };
162
- }
163
-
164
- if (!config.quiet) {
165
- const target = entryPoint ?? sourceRoot;
166
- console.log(`Emitting C# code for ${target}...`);
167
- }
168
-
169
- try {
170
- // For libraries without entry point, we need a different approach
171
- // For now, require entry point (library multi-file support can be added later)
172
- if (!entryPoint) {
173
- return {
174
- ok: false,
175
- error:
176
- "Entry point is required (library multi-file support coming soon)",
177
- };
178
- }
179
-
180
- // Combine typeRoots and libraries for TypeScript compilation
181
- const allTypeRoots = [...typeRoots, ...config.libraries];
182
-
183
- // Build dependency graph - this traverses all imports and builds IR for all modules
184
- const compilerOptions: CompilerOptions = {
185
- sourceRoot,
186
- rootNamespace,
187
- typeRoots: allTypeRoots,
188
- verbose: config.verbose,
189
- };
190
- const graphResult = buildModuleDependencyGraph(entryPoint, compilerOptions);
191
-
192
- if (!graphResult.ok) {
193
- const errorMessages = graphResult.error
194
- .map((d: Diagnostic) => {
195
- if (d.location) {
196
- return `${d.location.file}:${d.location.line} ${d.message}`;
197
- }
198
- return d.message;
199
- })
200
- .join("\n");
201
- return {
202
- ok: false,
203
- error: `TypeScript compilation failed:\n${errorMessages}`,
204
- };
205
- }
206
-
207
- const { modules, entryModule } = graphResult.value;
208
-
209
- if (config.verbose) {
210
- console.log(` Discovered ${modules.length} TypeScript modules`);
211
- for (const module of modules) {
212
- console.log(` - ${module.filePath}`);
213
- }
214
- }
215
-
216
- // irResult.value was an array of modules, now it's graphResult.value.modules
217
- const irResult = { ok: true as const, value: modules };
218
-
219
- // Emit C# code
220
- const absoluteEntryPoint = entryPoint ? resolve(entryPoint) : undefined;
221
- const emitResult = emitCSharpFiles(irResult.value, {
222
- rootNamespace,
223
- entryPointPath: absoluteEntryPoint,
224
- libraries: config.libraries,
225
- runtime: config.runtime,
226
- });
227
-
228
- if (!emitResult.ok) {
229
- // Handle file name collision errors
230
- for (const error of emitResult.errors) {
231
- console.error(`error ${error.code}: ${error.message}`);
232
- }
233
- process.exit(1);
234
- }
235
-
236
- const csFiles = emitResult.files;
237
-
238
- // Create output directory
239
- const outputDir = join(process.cwd(), outputDirectory);
240
- mkdirSync(outputDir, { recursive: true });
241
-
242
- // Write C# files preserving directory structure
243
- for (const [modulePath, csCode] of csFiles) {
244
- // Convert module path to C# file path
245
- // src/models/User.ts → generated/src/models/User.cs
246
- const csPath = modulePath.replace(/\.ts$/, ".cs");
247
- const fullPath = join(outputDir, csPath);
248
-
249
- mkdirSync(dirname(fullPath), { recursive: true });
250
- writeFileSync(fullPath, csCode, "utf-8");
251
-
252
- if (config.verbose) {
253
- const relPath = relative(process.cwd(), fullPath);
254
- console.log(` Generated: ${relPath}`);
255
- }
256
- }
257
-
258
- // Generate Program.cs entry point wrapper (only for executables)
259
- if (absoluteEntryPoint) {
260
- // entryModule is already provided by buildDependencyGraph
261
- // But double-check by comparing relative paths
262
- const entryRelative = relative(sourceRoot, absoluteEntryPoint).replace(
263
- /\\/g,
264
- "/"
265
- );
266
- const foundEntryModule =
267
- irResult.value.find((m: IrModule) => m.filePath === entryRelative) ??
268
- entryModule;
269
-
270
- if (foundEntryModule) {
271
- const entryInfo = extractEntryInfo(foundEntryModule, config.runtime);
272
- if (entryInfo) {
273
- const programCs = generateProgramCs(entryInfo);
274
- const programPath = join(outputDir, "Program.cs");
275
- writeFileSync(programPath, programCs, "utf-8");
276
-
277
- if (config.verbose) {
278
- console.log(` Generated: ${relative(process.cwd(), programPath)}`);
279
- }
280
- }
281
- }
282
- }
283
-
284
- // Generate or copy existing .csproj
285
- const csprojPath = join(outputDir, "tsonic.csproj");
286
- const projectCsproj = findProjectCsproj();
287
-
288
- if (projectCsproj) {
289
- // Copy existing .csproj from project root (preserves user edits)
290
- copyFileSync(projectCsproj, csprojPath);
291
-
292
- if (config.verbose) {
293
- console.log(
294
- ` Copied: ${relative(process.cwd(), projectCsproj)} → ${relative(process.cwd(), csprojPath)} (user edits preserved)`
295
- );
296
- }
297
- } else if (!existsSync(csprojPath)) {
298
- // Find Tsonic runtime - try multiple approaches:
299
- // 1. ProjectReference to .csproj (development/monorepo)
300
- // 2. Assembly references to DLLs (npm installed package)
301
- let runtimePath: string | undefined;
302
- let assemblyReferences: readonly AssemblyReference[] = [];
303
-
304
- // 1. Try monorepo structure (development) - ProjectReference
305
- const monorepoPath = resolve(
306
- join(import.meta.dirname, "../../../runtime/src/Tsonic.Runtime.csproj")
307
- );
308
- if (existsSync(monorepoPath)) {
309
- runtimePath = monorepoPath;
310
- } else {
311
- // 2. Try installed package structure - ProjectReference
312
- const installedPath = resolve(
313
- join(
314
- import.meta.dirname,
315
- "../../../../@tsonic/runtime/src/Tsonic.Runtime.csproj"
316
- )
317
- );
318
- if (existsSync(installedPath)) {
319
- runtimePath = installedPath;
320
- } else {
321
- // 3. Try to find runtime DLLs from npm package
322
- assemblyReferences = findRuntimeDlls(
323
- config.runtime ?? "js",
324
- outputDir
325
- );
326
- }
327
- }
328
-
329
- // Warn if no runtime found
330
- if (!runtimePath && assemblyReferences.length === 0 && !config.quiet) {
331
- console.warn(
332
- "Warning: Tsonic runtime not found. You may need to add references manually."
333
- );
334
- }
335
-
336
- // Build output configuration
337
- const outputType = config.outputConfig.type ?? "executable";
338
- let outputConfig: ExecutableConfig | LibraryConfig;
339
-
340
- if (outputType === "library") {
341
- outputConfig = {
342
- type: "library",
343
- targetFrameworks: config.outputConfig.targetFrameworks ?? [
344
- config.dotnetVersion,
345
- ],
346
- generateDocumentation:
347
- config.outputConfig.generateDocumentation ?? true,
348
- includeSymbols: config.outputConfig.includeSymbols ?? true,
349
- packable: config.outputConfig.packable ?? false,
350
- packageMetadata: config.outputConfig.package,
351
- };
352
- } else {
353
- outputConfig = {
354
- type: "executable",
355
- nativeAot: config.outputConfig.nativeAot ?? true,
356
- singleFile: config.outputConfig.singleFile ?? true,
357
- trimmed: config.outputConfig.trimmed ?? true,
358
- stripSymbols: config.stripSymbols,
359
- optimization: config.optimize === "size" ? "Size" : "Speed",
360
- invariantGlobalization: config.invariantGlobalization,
361
- selfContained: config.outputConfig.selfContained ?? true,
362
- };
363
- }
364
-
365
- const buildConfig: BuildConfig = {
366
- rootNamespace,
367
- outputName: config.outputName,
368
- dotnetVersion: config.dotnetVersion,
369
- runtimePath,
370
- assemblyReferences,
371
- packages,
372
- outputConfig,
373
- };
374
-
375
- const csproj = generateCsproj(buildConfig);
376
- writeFileSync(csprojPath, csproj, "utf-8");
377
-
378
- if (config.verbose) {
379
- console.log(` Generated: ${relative(process.cwd(), csprojPath)}`);
380
- }
381
- } else if (config.verbose) {
382
- console.log(
383
- ` Preserved: ${relative(process.cwd(), csprojPath)} (user edits kept)`
384
- );
385
- }
386
-
387
- if (!config.quiet) {
388
- console.log(
389
- `\n✓ Generated ${csFiles.size} C# files in ${outputDirectory}/`
390
- );
391
- }
392
-
393
- return {
394
- ok: true,
395
- value: {
396
- filesGenerated: csFiles.size,
397
- outputDir,
398
- },
399
- };
400
- } catch (error) {
401
- return {
402
- ok: false,
403
- error: `Emit failed: ${error instanceof Error ? error.message : String(error)}`,
404
- };
405
- }
406
- };