create-vidra-app 0.1.0

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.
Files changed (28) hide show
  1. package/README.md +39 -0
  2. package/bin/create-vidra-app.mjs +2 -0
  3. package/bin/vidra.mjs +2 -0
  4. package/dist/cli.js +777 -0
  5. package/dist/index.js +241 -0
  6. package/package.json +56 -0
  7. package/templates/react-vite/NuGet.Config +8 -0
  8. package/templates/react-vite/README.md +57 -0
  9. package/templates/react-vite/_gitignore +16 -0
  10. package/templates/react-vite/package.json +14 -0
  11. package/templates/react-vite/src/{{projectName}}.Host/App.xaml +5 -0
  12. package/templates/react-vite/src/{{projectName}}.Host/App.xaml.cs +14 -0
  13. package/templates/react-vite/src/{{projectName}}.Host/MainPage.cs +28 -0
  14. package/templates/react-vite/src/{{projectName}}.Host/MauiProgram.cs +26 -0
  15. package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/AppDelegate.cs +9 -0
  16. package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/Info.plist +11 -0
  17. package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/Program.cs +11 -0
  18. package/templates/react-vite/src/{{projectName}}.Host/Platforms/Windows/App.xaml +8 -0
  19. package/templates/react-vite/src/{{projectName}}.Host/Platforms/Windows/App.xaml.cs +11 -0
  20. package/templates/react-vite/src/{{projectName}}.Host/{{projectName}}.Host.csproj +57 -0
  21. package/templates/react-vite/ui/index.html +12 -0
  22. package/templates/react-vite/ui/package.json +23 -0
  23. package/templates/react-vite/ui/src/App.tsx +240 -0
  24. package/templates/react-vite/ui/src/index.css +109 -0
  25. package/templates/react-vite/ui/src/main.tsx +10 -0
  26. package/templates/react-vite/ui/src/vite-env.d.ts +1 -0
  27. package/templates/react-vite/ui/tsconfig.json +21 -0
  28. package/templates/react-vite/ui/vite.config.ts +33 -0
package/dist/cli.js ADDED
@@ -0,0 +1,777 @@
1
+ // src/cli.ts
2
+ import chalk5 from "chalk";
3
+
4
+ // src/commands/build.ts
5
+ import path5 from "path";
6
+ import fs4 from "fs-extra";
7
+ import { execSync as execSync2 } from "child_process";
8
+ import chalk3 from "chalk";
9
+
10
+ // src/utils.ts
11
+ var parseArgs = (argv) => {
12
+ const args = { _: [] };
13
+ for (let i = 2; i < argv.length; i++) {
14
+ const arg = argv[i];
15
+ if (arg.startsWith("--")) {
16
+ const [key, val] = arg.slice(2).split("=");
17
+ args[key] = val ?? argv[++i] ?? true;
18
+ } else {
19
+ args._.push(arg);
20
+ }
21
+ }
22
+ return args;
23
+ };
24
+
25
+ // src/project.ts
26
+ import path from "path";
27
+ import fs from "fs-extra";
28
+ import chalk from "chalk";
29
+ var detectPlatform = () => {
30
+ switch (process.platform) {
31
+ case "darwin":
32
+ return "macos";
33
+ case "win32":
34
+ return "windows";
35
+ case "linux":
36
+ return "linux";
37
+ default:
38
+ return "macos";
39
+ }
40
+ };
41
+ var detectProject = (cwd) => {
42
+ let dir = cwd;
43
+ while (true) {
44
+ const uiDir = path.join(dir, "ui");
45
+ const srcDir = path.join(dir, "src");
46
+ const pkgJson = path.join(dir, "package.json");
47
+ if (fs.existsSync(pkgJson) && fs.existsSync(uiDir) && fs.existsSync(srcDir)) {
48
+ const csproj = findHostCsproj(srcDir);
49
+ if (csproj) {
50
+ const projectName = path.basename(csproj.dir).replace(/\.Host$/, "");
51
+ const displayVersion = readCsprojVersion(csproj.path);
52
+ return {
53
+ root: dir,
54
+ hostDir: csproj.dir,
55
+ csprojPath: csproj.path,
56
+ projectName,
57
+ displayVersion,
58
+ uiDir
59
+ };
60
+ }
61
+ }
62
+ const parent = path.dirname(dir);
63
+ if (parent === dir) break;
64
+ dir = parent;
65
+ }
66
+ console.error(
67
+ chalk.red(
68
+ " Could not detect Vidra project. Run this command from your project root.\n Expected: package.json, ui/, src/<Name>.Host/<Name>.Host.csproj"
69
+ )
70
+ );
71
+ process.exit(1);
72
+ };
73
+ var findHostCsproj = (srcDir) => {
74
+ if (!fs.existsSync(srcDir)) return null;
75
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
76
+ if (entry.isDirectory() && entry.name.endsWith(".Host")) {
77
+ const csprojPath = path.join(
78
+ srcDir,
79
+ entry.name,
80
+ `${entry.name}.csproj`
81
+ );
82
+ if (fs.existsSync(csprojPath)) {
83
+ return { dir: path.join(srcDir, entry.name), path: csprojPath };
84
+ }
85
+ }
86
+ }
87
+ return null;
88
+ };
89
+ var readCsprojVersion = (csprojPath) => {
90
+ const content = fs.readFileSync(csprojPath, "utf-8");
91
+ const match = content.match(
92
+ /<ApplicationDisplayVersion>(.*?)<\/ApplicationDisplayVersion>/
93
+ );
94
+ return match?.[1] ?? "0.1.0";
95
+ };
96
+
97
+ // src/targets/macos.ts
98
+ import path2 from "path";
99
+ import fs2 from "fs-extra";
100
+ import { execSync } from "child_process";
101
+ import os from "os";
102
+ var macosTarget = {
103
+ name: "macos",
104
+ framework: "net10.0-maccatalyst",
105
+ extraPublishArgs: "-p:CreatePackage=false",
106
+ findBundle(publishDir, _projectName) {
107
+ if (!fs2.existsSync(publishDir)) return null;
108
+ for (const entry of fs2.readdirSync(publishDir, { withFileTypes: true })) {
109
+ if (entry.name.endsWith(".app") && entry.isDirectory()) {
110
+ return path2.join(publishDir, entry.name);
111
+ }
112
+ }
113
+ return null;
114
+ },
115
+ async package(appPath, outputDir, meta) {
116
+ const dmgName = `${meta.projectName}-${meta.displayVersion}-macos.dmg`;
117
+ const dmgPath = path2.join(outputDir, dmgName);
118
+ const volName = meta.projectName;
119
+ const staging = fs2.mkdtempSync(path2.join(os.tmpdir(), "vidra-dmg-"));
120
+ try {
121
+ fs2.copySync(appPath, path2.join(staging, path2.basename(appPath)));
122
+ fs2.symlinkSync("/Applications", path2.join(staging, "Applications"));
123
+ if (fs2.existsSync(dmgPath)) {
124
+ fs2.removeSync(dmgPath);
125
+ }
126
+ execSync(
127
+ `hdiutil create -volname "${volName}" -srcfolder "${staging}" -ov -format UDZO "${dmgPath}"`,
128
+ { stdio: "pipe" }
129
+ );
130
+ } finally {
131
+ fs2.removeSync(staging);
132
+ }
133
+ return dmgPath;
134
+ }
135
+ };
136
+
137
+ // src/signing.ts
138
+ import path3 from "path";
139
+ import { execFileSync } from "child_process";
140
+ import chalk2 from "chalk";
141
+ var signMacAppBundleIfPossible = (appBundle, options) => {
142
+ if (process.platform !== "darwin") return;
143
+ const codesignIdentity = resolveMacCodeSigningIdentity();
144
+ if (!codesignIdentity) {
145
+ options.warn(
146
+ chalk2.yellow(
147
+ " No usable macOS signing identity found. The app will remain ad-hoc signed."
148
+ )
149
+ );
150
+ options.warn(
151
+ chalk2.yellow(
152
+ " Set VIDRA_MACOS_CODESIGN_KEY to override identity selection if needed."
153
+ )
154
+ );
155
+ return;
156
+ }
157
+ try {
158
+ execFileSync(
159
+ "codesign",
160
+ ["--force", "--deep", "--sign", codesignIdentity, appBundle],
161
+ {
162
+ stdio: options.verbose ? "inherit" : "pipe"
163
+ }
164
+ );
165
+ options.log(
166
+ ` ${chalk2.dim("Signing:")} ${chalk2.cyan(path3.basename(appBundle))} ${chalk2.dim(`with ${codesignIdentity}`)}`
167
+ );
168
+ } catch (error) {
169
+ options.warn(
170
+ chalk2.yellow(
171
+ " Failed to re-sign the macOS app bundle. It may remain ad-hoc signed."
172
+ )
173
+ );
174
+ options.warn(chalk2.dim(formatExecError(error)));
175
+ }
176
+ };
177
+ var resolveMacCodeSigningIdentity = () => {
178
+ const override = process.env.VIDRA_MACOS_CODESIGN_KEY?.trim();
179
+ if (override) {
180
+ return override;
181
+ }
182
+ try {
183
+ const output = execFileSync(
184
+ "security",
185
+ ["find-identity", "-v", "-p", "codesigning"],
186
+ {
187
+ stdio: ["ignore", "pipe", "pipe"],
188
+ encoding: "utf8"
189
+ }
190
+ );
191
+ const identities = output.split(/\r?\n/).map((line) => line.match(/"([^"]+)"/)?.[1] ?? null).filter((value) => value !== null);
192
+ return identities.find((identity) => identity.startsWith("Apple Development:")) ?? identities.find((identity) => identity.startsWith("Developer ID Application:")) ?? null;
193
+ } catch {
194
+ return null;
195
+ }
196
+ };
197
+ var formatExecError = (error) => {
198
+ const err = error;
199
+ if (Buffer.isBuffer(err.stderr)) {
200
+ return err.stderr.toString();
201
+ }
202
+ if (typeof err.stderr === "string") {
203
+ return err.stderr;
204
+ }
205
+ return err.message ?? String(error);
206
+ };
207
+
208
+ // src/targets/windows.ts
209
+ import path4 from "path";
210
+ import fs3 from "fs-extra";
211
+ var windowsTarget = {
212
+ name: "windows",
213
+ framework: "net10.0-windows10.0.19041.0",
214
+ extraPublishArgs: "-p:RuntimeIdentifierOverride=win-x64 -p:WindowsPackageType=MSIX",
215
+ findBundle(publishDir, _projectName) {
216
+ const appPackagesDir = path4.join(publishDir, "win-x64", "AppPackages");
217
+ if (!fs3.existsSync(appPackagesDir)) return null;
218
+ for (const entry of fs3.readdirSync(appPackagesDir, {
219
+ withFileTypes: true
220
+ })) {
221
+ if (!entry.isDirectory()) continue;
222
+ const subDir = path4.join(appPackagesDir, entry.name);
223
+ for (const file of fs3.readdirSync(subDir)) {
224
+ if (file.endsWith(".msix")) {
225
+ return path4.join(subDir, file);
226
+ }
227
+ }
228
+ }
229
+ return null;
230
+ },
231
+ async package(msixPath, outputDir, meta) {
232
+ const outName = `${meta.projectName}-${meta.displayVersion}-windows.msix`;
233
+ const outPath = path4.join(outputDir, outName);
234
+ fs3.copySync(msixPath, outPath, { overwrite: true });
235
+ return outPath;
236
+ }
237
+ };
238
+
239
+ // src/commands/build.ts
240
+ var VERSION = "0.1.0";
241
+ var TARGETS = {
242
+ macos: macosTarget,
243
+ windows: windowsTarget
244
+ };
245
+ var buildCommand = async (argv) => {
246
+ const args = parseArgs(["_", "_", ...argv]);
247
+ const verbose = !!args["verbose"];
248
+ const targetName = args["target"] || detectPlatform();
249
+ console.log();
250
+ console.log(
251
+ ` ${chalk3.bold.cyan("vidra build")} ${chalk3.dim(`v${VERSION}`)}`
252
+ );
253
+ console.log();
254
+ const target = TARGETS[targetName];
255
+ if (!target) {
256
+ const supported = Object.keys(TARGETS).join(", ");
257
+ console.error(
258
+ chalk3.red(
259
+ ` Unsupported target: ${targetName}. Supported: ${supported}`
260
+ )
261
+ );
262
+ process.exit(1);
263
+ }
264
+ const project = detectProject(process.cwd());
265
+ console.log(` ${chalk3.dim("Project:")} ${chalk3.cyan(project.projectName)}`);
266
+ console.log(` ${chalk3.dim("Target:")} ${chalk3.cyan(target.name)} (${target.framework})`);
267
+ console.log();
268
+ stepBuildUi(project, verbose);
269
+ stepCopyAssets(project);
270
+ const publishDir = stepDotnetPublish(project, target, verbose);
271
+ const bundlePath = target.findBundle(publishDir, project.projectName);
272
+ if (!bundlePath) {
273
+ console.error(
274
+ chalk3.red(` Could not find build artifact in ${publishDir}`)
275
+ );
276
+ process.exit(1);
277
+ }
278
+ console.log(
279
+ ` ${chalk3.dim("Bundle:")} ${chalk3.cyan(path5.basename(bundlePath))}`
280
+ );
281
+ if (target.name === "macos") {
282
+ signMacAppBundleIfPossible(bundlePath, {
283
+ verbose,
284
+ log: console.log,
285
+ warn: console.warn
286
+ });
287
+ }
288
+ const outputDir = path5.join(project.root, "dist");
289
+ fs4.ensureDirSync(outputDir);
290
+ console.log();
291
+ console.log(` ${chalk3.dim(`Packaging for ${target.name}...`)}`);
292
+ const startPkg = Date.now();
293
+ let outputPath;
294
+ try {
295
+ outputPath = await target.package(bundlePath, outputDir, {
296
+ projectName: project.projectName,
297
+ displayVersion: project.displayVersion
298
+ });
299
+ } catch (e) {
300
+ const err = e;
301
+ console.error(chalk3.red(` Packaging failed.`));
302
+ console.error(chalk3.dim(err.stderr?.toString() || err.message));
303
+ process.exit(1);
304
+ }
305
+ const pkgTime = ((Date.now() - startPkg) / 1e3).toFixed(1);
306
+ const sizeBytes = fs4.statSync(outputPath).size;
307
+ const sizeMB = (sizeBytes / (1024 * 1024)).toFixed(1);
308
+ console.log(
309
+ ` ${chalk3.green(">")} ${path5.basename(outputPath)} ${chalk3.dim(`(${sizeMB} MB, ${pkgTime}s)`)}`
310
+ );
311
+ console.log();
312
+ console.log(
313
+ ` ${chalk3.green("Done!")} Output: ${chalk3.cyan(path5.relative(project.root, outputPath))}`
314
+ );
315
+ console.log();
316
+ };
317
+ var stepBuildUi = (project, verbose) => {
318
+ console.log(` ${chalk3.dim("Building UI...")}`);
319
+ const start = Date.now();
320
+ try {
321
+ execSync2("npm run build", {
322
+ cwd: project.uiDir,
323
+ stdio: verbose ? "inherit" : "pipe"
324
+ });
325
+ } catch (e) {
326
+ const err = e;
327
+ console.error(chalk3.red(" Vite build failed."));
328
+ console.error(chalk3.dim(err.stderr?.toString() || err.message));
329
+ process.exit(1);
330
+ }
331
+ const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
332
+ console.log(` ${chalk3.green(">")} Vite build complete ${chalk3.dim(`(${elapsed}s)`)}`);
333
+ console.log();
334
+ };
335
+ var stepCopyAssets = (project) => {
336
+ console.log(` ${chalk3.dim("Copying assets to host project...")}`);
337
+ const viteDist = path5.join(project.uiDir, "dist");
338
+ if (!fs4.existsSync(viteDist)) {
339
+ console.error(chalk3.red(` ui/dist not found. Vite build may have failed.`));
340
+ process.exit(1);
341
+ }
342
+ const wwwroot = path5.join(project.hostDir, "Resources", "Raw", "wwwroot");
343
+ fs4.removeSync(wwwroot);
344
+ fs4.copySync(viteDist, wwwroot);
345
+ const fileCount = countFiles(wwwroot);
346
+ console.log(
347
+ ` ${chalk3.green(">")} ${fileCount} files -> ${chalk3.dim("Resources/Raw/wwwroot/")}`
348
+ );
349
+ console.log();
350
+ };
351
+ var countFiles = (dir) => {
352
+ let count = 0;
353
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
354
+ if (entry.isDirectory()) {
355
+ count += countFiles(path5.join(dir, entry.name));
356
+ } else {
357
+ count++;
358
+ }
359
+ }
360
+ return count;
361
+ };
362
+ var stepDotnetPublish = (project, target, verbose) => {
363
+ console.log(
364
+ ` ${chalk3.dim(`Publishing .NET host (${target.framework})...`)}`
365
+ );
366
+ const start = Date.now();
367
+ const extraArgs = target.extraPublishArgs ?? "-p:CreatePackage=false";
368
+ try {
369
+ execSync2(
370
+ `dotnet publish "${project.csprojPath}" -c Release -f ${target.framework} ${extraArgs}`,
371
+ {
372
+ cwd: project.root,
373
+ stdio: verbose ? "inherit" : "pipe"
374
+ }
375
+ );
376
+ } catch (e) {
377
+ const err = e;
378
+ console.error(chalk3.red(" dotnet publish failed."));
379
+ console.error(chalk3.dim(err.stderr?.toString() || err.message));
380
+ process.exit(1);
381
+ }
382
+ const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
383
+ console.log(
384
+ ` ${chalk3.green(">")} dotnet publish complete ${chalk3.dim(`(${elapsed}s)`)}`
385
+ );
386
+ const publishDir = path5.join(
387
+ project.hostDir,
388
+ "bin",
389
+ "Release",
390
+ target.framework
391
+ );
392
+ return publishDir;
393
+ };
394
+
395
+ // src/commands/dev.ts
396
+ import path6 from "path";
397
+ import fs5 from "fs-extra";
398
+ import { execFileSync as execFileSync2, spawn } from "child_process";
399
+ import { request } from "http";
400
+ import chalk4 from "chalk";
401
+ var VERSION2 = "0.1.0";
402
+ var POLL_INTERVAL_MS = 500;
403
+ var POLL_TIMEOUT_MS = 3e4;
404
+ var NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
405
+ var DOTNET_COMMAND = process.platform === "win32" ? "dotnet.exe" : "dotnet";
406
+ var TARGETS2 = {
407
+ macos: {
408
+ name: "macos",
409
+ framework: "net10.0-maccatalyst"
410
+ },
411
+ windows: {
412
+ name: "windows",
413
+ framework: "net10.0-windows10.0.19041.0"
414
+ }
415
+ };
416
+ var devCommand = async (argv) => {
417
+ const args = parseArgs(["_", "_", ...argv]);
418
+ const targetName = args["target"] || detectPlatform();
419
+ const verbose = !!args["verbose"];
420
+ const viteUrl = process.env.VIDRA_DEV_URL || "http://localhost:5173";
421
+ const target = TARGETS2[targetName];
422
+ if (!target) {
423
+ const supported = Object.keys(TARGETS2).join(", ");
424
+ console.error(
425
+ chalk4.red(
426
+ ` Unsupported target: ${targetName}. Supported: ${supported}`
427
+ )
428
+ );
429
+ process.exit(1);
430
+ }
431
+ ensureTargetMatchesHostOs(target.name);
432
+ const project = detectProject(process.cwd());
433
+ const session = new DevSession(project, target, viteUrl, verbose);
434
+ await session.run();
435
+ };
436
+ var DevSession = class {
437
+ constructor(project, target, viteUrl, verbose) {
438
+ this.project = project;
439
+ this.target = target;
440
+ this.viteUrl = viteUrl;
441
+ this.verbose = verbose;
442
+ }
443
+ project;
444
+ target;
445
+ viteUrl;
446
+ verbose;
447
+ children = [];
448
+ buildConfig = process.env.VIDRA_BUILD_CONFIG || "Debug";
449
+ shuttingDown = false;
450
+ async run() {
451
+ this.installSignalHandlers();
452
+ console.log();
453
+ console.log(` ${chalk4.bold.cyan("vidra dev")} ${chalk4.dim(`v${VERSION2}`)}`);
454
+ console.log();
455
+ console.log(` ${chalk4.dim("Project:")} ${chalk4.cyan(this.project.projectName)}`);
456
+ console.log(
457
+ ` ${chalk4.dim("Target:")} ${chalk4.cyan(this.target.name)} (${this.target.framework})`
458
+ );
459
+ console.log();
460
+ const vite = this.startVite();
461
+ try {
462
+ await waitForServer(this.viteUrl, POLL_TIMEOUT_MS);
463
+ } catch (error) {
464
+ console.error(
465
+ chalk4.red(
466
+ ` ${error.message}`
467
+ )
468
+ );
469
+ this.shutdown(1);
470
+ }
471
+ console.log(` ${chalk4.dim("Vite:")} ${chalk4.cyan(this.viteUrl)}`);
472
+ console.log();
473
+ const host = this.target.name === "macos" ? this.launchMacosHost() : this.launchWindowsHost();
474
+ await waitForExit(vite, host);
475
+ }
476
+ installSignalHandlers() {
477
+ process.on("SIGINT", () => {
478
+ console.log("\n\x1B[90m[vidra]\x1B[0m Shutting down...");
479
+ this.shutdown(0);
480
+ });
481
+ process.on("SIGTERM", () => {
482
+ this.shutdown(0);
483
+ });
484
+ }
485
+ startVite() {
486
+ console.log(` ${chalk4.dim("Starting Vite dev server...")}`);
487
+ const vite = spawn(NPM_COMMAND, ["run", "dev"], {
488
+ cwd: this.project.uiDir,
489
+ stdio: ["ignore", "pipe", "pipe"]
490
+ });
491
+ return this.registerChild(vite, "ui", "Vite dev server");
492
+ }
493
+ launchMacosHost() {
494
+ console.log(
495
+ ` ${chalk4.dim(`Building MAUI host (${this.target.framework})...`)}`
496
+ );
497
+ try {
498
+ execFileSync2(
499
+ DOTNET_COMMAND,
500
+ [
501
+ "build",
502
+ "-c",
503
+ this.buildConfig,
504
+ "-f",
505
+ this.target.framework,
506
+ this.project.csprojPath
507
+ ],
508
+ {
509
+ cwd: this.project.root,
510
+ stdio: this.verbose ? "inherit" : "pipe"
511
+ }
512
+ );
513
+ } catch (error) {
514
+ console.error(chalk4.red(" MAUI build failed."));
515
+ console.error(chalk4.dim(formatExecError2(error)));
516
+ process.exit(1);
517
+ }
518
+ const appBundle = findMacAppBundle(
519
+ this.project.hostDir,
520
+ this.target.framework,
521
+ this.buildConfig
522
+ );
523
+ if (!appBundle) {
524
+ console.error(
525
+ chalk4.red(
526
+ ` Could not find .app bundle in ${path6.join(this.project.hostDir, "bin", this.buildConfig, this.target.framework)}`
527
+ )
528
+ );
529
+ process.exit(1);
530
+ }
531
+ signMacAppBundleIfPossible(appBundle, {
532
+ verbose: this.verbose,
533
+ log: console.log,
534
+ warn: console.warn
535
+ });
536
+ const binary = findMacExecutable(appBundle);
537
+ if (!binary) {
538
+ console.error(
539
+ chalk4.red(` Could not find the app executable in ${appBundle}.`)
540
+ );
541
+ process.exit(1);
542
+ }
543
+ console.log(` ${chalk4.dim("Launching host...")}`);
544
+ const host = spawn(binary, [], {
545
+ cwd: this.project.root,
546
+ stdio: ["ignore", "pipe", "pipe"],
547
+ env: { ...process.env, VIDRA_DEV_URL: this.viteUrl }
548
+ });
549
+ return this.registerChild(host, "host", path6.basename(binary));
550
+ }
551
+ launchWindowsHost() {
552
+ console.log(` ${chalk4.dim("Launching host...")}`);
553
+ const host = spawn(
554
+ DOTNET_COMMAND,
555
+ [
556
+ "build",
557
+ "-t:Run",
558
+ "-c",
559
+ this.buildConfig,
560
+ "-f",
561
+ this.target.framework,
562
+ this.project.csprojPath
563
+ ],
564
+ {
565
+ cwd: this.project.root,
566
+ stdio: ["ignore", "pipe", "pipe"],
567
+ env: { ...process.env, VIDRA_DEV_URL: this.viteUrl }
568
+ }
569
+ );
570
+ return this.registerChild(host, "host", "MAUI host");
571
+ }
572
+ registerChild(child, tag, label) {
573
+ this.children.push(child);
574
+ prefixStream(child.stdout, tag);
575
+ prefixStream(child.stderr, tag);
576
+ child.on("exit", (code) => {
577
+ if (this.shuttingDown) return;
578
+ if (tag === "ui") {
579
+ const exitCode = code ?? 1;
580
+ console.error(
581
+ chalk4.red(`
582
+ ${label} exited with code ${exitCode}.`)
583
+ );
584
+ this.shutdown(exitCode);
585
+ return;
586
+ }
587
+ if (code !== null && code !== 0) {
588
+ console.error(chalk4.red(`
589
+ ${label} exited with code ${code}.`));
590
+ }
591
+ this.shutdown(code ?? 0);
592
+ });
593
+ child.on("error", (error) => {
594
+ if (this.shuttingDown) return;
595
+ console.error(chalk4.red(`
596
+ Failed to start ${label}: ${error.message}`));
597
+ this.shutdown(1);
598
+ });
599
+ return child;
600
+ }
601
+ shutdown(exitCode) {
602
+ if (this.shuttingDown) return;
603
+ this.shuttingDown = true;
604
+ for (const child of this.children) {
605
+ killChild(child);
606
+ }
607
+ process.exit(exitCode);
608
+ }
609
+ };
610
+ var ensureTargetMatchesHostOs = (targetName) => {
611
+ if (targetName === "macos" && process.platform !== "darwin") {
612
+ console.error(chalk4.red(" The macOS dev target can only run on macOS."));
613
+ process.exit(1);
614
+ }
615
+ if (targetName === "windows" && process.platform !== "win32") {
616
+ console.error(
617
+ chalk4.red(" The Windows dev target can only run on Windows.")
618
+ );
619
+ process.exit(1);
620
+ }
621
+ };
622
+ var prefixStream = (stream, tag) => {
623
+ if (!stream) return;
624
+ stream.on("data", (chunk) => {
625
+ const lines = chunk.toString().split("\n");
626
+ for (const line of lines) {
627
+ if (line.length > 0) {
628
+ process.stdout.write(`\x1B[90m[${tag}]\x1B[0m ${line}
629
+ `);
630
+ }
631
+ }
632
+ });
633
+ };
634
+ var waitForServer = (url, timeoutMs) => {
635
+ const { hostname, port, pathname } = new URL(url);
636
+ const start = Date.now();
637
+ return new Promise((resolve, reject) => {
638
+ const poll = () => {
639
+ if (Date.now() - start > timeoutMs) {
640
+ reject(
641
+ new Error(`Timed out waiting for ${url} after ${timeoutMs}ms`)
642
+ );
643
+ return;
644
+ }
645
+ const req = request(
646
+ {
647
+ hostname,
648
+ port,
649
+ path: pathname,
650
+ method: "HEAD",
651
+ timeout: 1e3
652
+ },
653
+ () => resolve()
654
+ );
655
+ req.on("error", () => setTimeout(poll, POLL_INTERVAL_MS));
656
+ req.on("timeout", () => {
657
+ req.destroy();
658
+ setTimeout(poll, POLL_INTERVAL_MS);
659
+ });
660
+ req.end();
661
+ };
662
+ poll();
663
+ });
664
+ };
665
+ var waitForExit = (vite, host) => {
666
+ return new Promise((resolve) => {
667
+ const resolveOnce = () => resolve();
668
+ vite.once("exit", resolveOnce);
669
+ host.once("exit", resolveOnce);
670
+ });
671
+ };
672
+ var findMacAppBundle = (hostDir, framework, buildConfig) => {
673
+ const outputDir = path6.join(hostDir, "bin", buildConfig, framework);
674
+ return findAppBundleRecursive(outputDir);
675
+ };
676
+ var findAppBundleRecursive = (dir) => {
677
+ if (!fs5.existsSync(dir)) return null;
678
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
679
+ const fullPath = path6.join(dir, entry.name);
680
+ if (entry.isDirectory() && entry.name.endsWith(".app")) {
681
+ return fullPath;
682
+ }
683
+ if (entry.isDirectory()) {
684
+ const nested = findAppBundleRecursive(fullPath);
685
+ if (nested) return nested;
686
+ }
687
+ }
688
+ return null;
689
+ };
690
+ var findMacExecutable = (appBundle) => {
691
+ const macOsDir = path6.join(appBundle, "Contents", "MacOS");
692
+ if (!fs5.existsSync(macOsDir)) return null;
693
+ for (const entry of fs5.readdirSync(macOsDir, { withFileTypes: true })) {
694
+ if (entry.isFile()) {
695
+ return path6.join(macOsDir, entry.name);
696
+ }
697
+ }
698
+ return null;
699
+ };
700
+ var killChild = (child) => {
701
+ if (!child.pid || child.exitCode !== null) return;
702
+ if (process.platform === "win32") {
703
+ try {
704
+ execFileSync2("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
705
+ stdio: "ignore"
706
+ });
707
+ } catch {
708
+ child.kill();
709
+ }
710
+ return;
711
+ }
712
+ child.kill("SIGTERM");
713
+ };
714
+ var formatExecError2 = (error) => {
715
+ const err = error;
716
+ if (Buffer.isBuffer(err.stderr)) {
717
+ return err.stderr.toString();
718
+ }
719
+ if (typeof err.stderr === "string") {
720
+ return err.stderr;
721
+ }
722
+ return err.message;
723
+ };
724
+
725
+ // src/cli.ts
726
+ var VERSION3 = "0.1.0";
727
+ var printHelp = () => {
728
+ console.log(`
729
+ ${chalk5.bold("vidra")} ${chalk5.dim(`v${VERSION3}`)}
730
+
731
+ ${chalk5.dim("Usage:")}
732
+ vidra <command> [options]
733
+
734
+ ${chalk5.dim("Commands:")}
735
+ dev Start the development environment
736
+ build Build and package the application for distribution
737
+ help Show this help message
738
+
739
+ ${chalk5.dim("Examples:")}
740
+ vidra dev ${chalk5.dim("# start Vite + native host")}
741
+ vidra dev --target windows ${chalk5.dim("# run the Windows host")}
742
+ vidra build ${chalk5.dim("# auto-detect platform")}
743
+ vidra build --target macos ${chalk5.dim("# macOS DMG")}
744
+ vidra build --verbose ${chalk5.dim("# show full build output")}
745
+ `);
746
+ };
747
+ var main = async () => {
748
+ const args = process.argv.slice(2);
749
+ const command = args[0];
750
+ switch (command) {
751
+ case "dev":
752
+ await devCommand(args.slice(1));
753
+ break;
754
+ case "build":
755
+ await buildCommand(args.slice(1));
756
+ break;
757
+ case "help":
758
+ case "--help":
759
+ case "-h":
760
+ case void 0:
761
+ printHelp();
762
+ break;
763
+ case "--version":
764
+ case "-v":
765
+ console.log(VERSION3);
766
+ break;
767
+ default:
768
+ console.error(chalk5.red(` Unknown command: ${command}
769
+ `));
770
+ printHelp();
771
+ process.exit(1);
772
+ }
773
+ };
774
+ main().catch((e) => {
775
+ console.error(chalk5.red(e.message));
776
+ process.exit(1);
777
+ });