create-vidra-app 0.3.1 → 0.5.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.
- package/README.md +24 -31
- package/dist/index.js +33 -32
- package/package.json +13 -10
- package/templates/react-vite/README.md +22 -67
- package/templates/react-vite/_gitignore +6 -0
- package/templates/react-vite/package.json +3 -1
- package/templates/react-vite/src/{{projectName}}.Host/Entitlements.plist +18 -0
- package/templates/react-vite/src/{{projectName}}.Host/MainPage.cs +13 -2
- package/templates/react-vite/src/{{projectName}}.Host/MauiProgram.cs +23 -0
- package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/Program.cs +21 -0
- package/templates/react-vite/src/{{projectName}}.Host/Platforms/Windows/Program.cs +41 -0
- package/templates/react-vite/src/{{projectName}}.Host/{{projectName}}.Host.csproj +53 -1
- package/templates/react-vite/ui/package.json +3 -3
- package/templates/react-vite/ui/src/generated/vidra-access-policy.ts +6 -0
- package/templates/react-vite/ui/src/main.tsx +1 -0
- package/templates/react-vite/ui/vite.config.ts +4 -1
- package/templates/react-vite/vidra.config.ts +29 -0
- package/bin/vidra.mjs +0 -2
- package/dist/cli.js +0 -1898
package/dist/cli.js
DELETED
|
@@ -1,1898 +0,0 @@
|
|
|
1
|
-
// src/commands/build.ts
|
|
2
|
-
import path5 from "path";
|
|
3
|
-
import fs4 from "fs-extra";
|
|
4
|
-
import { execSync as execSync3 } from "child_process";
|
|
5
|
-
|
|
6
|
-
// src/utils.ts
|
|
7
|
-
var parseArgs = (argv) => {
|
|
8
|
-
const args = { _: [] };
|
|
9
|
-
for (let i = 2; i < argv.length; i++) {
|
|
10
|
-
const arg = argv[i];
|
|
11
|
-
if (arg.startsWith("--")) {
|
|
12
|
-
const [key, val] = arg.slice(2).split("=");
|
|
13
|
-
if (val !== void 0) {
|
|
14
|
-
args[key] = val;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
const next = argv[i + 1];
|
|
18
|
-
if (next !== void 0 && !next.startsWith("--")) {
|
|
19
|
-
args[key] = next;
|
|
20
|
-
i++;
|
|
21
|
-
} else {
|
|
22
|
-
args[key] = true;
|
|
23
|
-
}
|
|
24
|
-
} else {
|
|
25
|
-
args._.push(arg);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return args;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// src/exec.ts
|
|
32
|
-
import { execSync, spawn } from "child_process";
|
|
33
|
-
|
|
34
|
-
// src/theme.ts
|
|
35
|
-
import chalk from "chalk";
|
|
36
|
-
var CLI_VERSION = "0.3.1";
|
|
37
|
-
var STEP_LABEL_WIDTH = 16;
|
|
38
|
-
var lime = chalk.hex("#c8f751");
|
|
39
|
-
var value = chalk.hex("#e8e8ec");
|
|
40
|
-
var dim = chalk.hex("#7a7a86");
|
|
41
|
-
var amber = chalk.hex("#ffcf5c");
|
|
42
|
-
var green = chalk.hex("#86d98f");
|
|
43
|
-
var red = chalk.hex("#ff8585");
|
|
44
|
-
var slate = chalk.hex("#a9b0bd");
|
|
45
|
-
var uiColor = chalk.hex("#6fd3e0");
|
|
46
|
-
var hostColor = chalk.hex("#a48ce8");
|
|
47
|
-
var GLYPH_CHAR = {
|
|
48
|
-
done: "\u2713",
|
|
49
|
-
active: "\u25B8",
|
|
50
|
-
error: "\u2717",
|
|
51
|
-
manual: "?",
|
|
52
|
-
plan: "\u25C6",
|
|
53
|
-
skip: "\u2298"
|
|
54
|
-
};
|
|
55
|
-
var GLYPH_COLOR = {
|
|
56
|
-
done: green,
|
|
57
|
-
active: lime,
|
|
58
|
-
error: red,
|
|
59
|
-
manual: amber,
|
|
60
|
-
plan: slate,
|
|
61
|
-
skip: slate
|
|
62
|
-
};
|
|
63
|
-
var glyph = (name) => GLYPH_COLOR[name](GLYPH_CHAR[name]);
|
|
64
|
-
var TAG_COLOR = {
|
|
65
|
-
ui: uiColor,
|
|
66
|
-
host: hostColor
|
|
67
|
-
};
|
|
68
|
-
var TAG_WIDTH = 6;
|
|
69
|
-
var tag = (name) => TAG_COLOR[name](`[${name}]`.padEnd(TAG_WIDTH));
|
|
70
|
-
var blankTag = () => " ".repeat(TAG_WIDTH);
|
|
71
|
-
var wordmark = () => lime("vidra");
|
|
72
|
-
var header = (sub, context) => {
|
|
73
|
-
const base = ` ${wordmark()} ${value(sub)}`;
|
|
74
|
-
return context ? `${base} ${dim(`\u2014 ${context}`)}` : base;
|
|
75
|
-
};
|
|
76
|
-
var kv = (label, val, width = 9) => ` ${dim(label.padEnd(width))} ${value(val)}`;
|
|
77
|
-
var row = (opts) => {
|
|
78
|
-
const parts = [` ${glyph(opts.glyph)}`];
|
|
79
|
-
if (opts.label !== void 0) {
|
|
80
|
-
const padded = opts.labelWidth ? opts.label.padEnd(opts.labelWidth) : opts.label;
|
|
81
|
-
parts.push(value(padded));
|
|
82
|
-
}
|
|
83
|
-
if (opts.detail) parts.push(opts.detail);
|
|
84
|
-
return parts.join(" ");
|
|
85
|
-
};
|
|
86
|
-
var taggedRow = (glyphName, tagName, text) => ` ${glyph(glyphName)} ${tagName ? tag(tagName) : blankTag()} ${text}`;
|
|
87
|
-
var streamPrefix = (tagName) => ` ${dim("\xB7")} ${tag(tagName)}`;
|
|
88
|
-
var footer = (content) => ` ${content}`;
|
|
89
|
-
var fixLine = (cmd, label = "fix:") => ` ${amber(label)} ${lime(cmd)}`;
|
|
90
|
-
var planBadge = (text = "PLAN ONLY \xB7 BEING BUILT") => amber(`[ ${text} ]`);
|
|
91
|
-
|
|
92
|
-
// src/exec.ts
|
|
93
|
-
var toText = (value2) => {
|
|
94
|
-
if (value2 == null) return "";
|
|
95
|
-
return Buffer.isBuffer(value2) ? value2.toString() : value2;
|
|
96
|
-
};
|
|
97
|
-
var formatProcessError = (error) => {
|
|
98
|
-
const err = error;
|
|
99
|
-
const combined = [toText(err.stderr), toText(err.stdout)].map((s) => s.trim()).filter((s) => s.length > 0).join("\n").trim();
|
|
100
|
-
return combined.length > 0 ? combined : err.message ?? String(error);
|
|
101
|
-
};
|
|
102
|
-
var formatBuildError = (error) => {
|
|
103
|
-
const raw = formatProcessError(error);
|
|
104
|
-
const lines = raw.split(/\r?\n/);
|
|
105
|
-
const errorLines = lines.filter(
|
|
106
|
-
(line) => /(:\s*error\b|\berror\s+[A-Z]{2,}\d+|Build FAILED|MSB\d{4}|NETSDK\d{4})/i.test(
|
|
107
|
-
line
|
|
108
|
-
)
|
|
109
|
-
);
|
|
110
|
-
const picked = errorLines.length > 0 ? errorLines : lines.filter((line) => line.trim().length > 0).slice(-30);
|
|
111
|
-
return picked.join("\n").trim();
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// src/project.ts
|
|
115
|
-
import path from "path";
|
|
116
|
-
import fs from "fs-extra";
|
|
117
|
-
var detectPlatform = () => {
|
|
118
|
-
switch (process.platform) {
|
|
119
|
-
case "darwin":
|
|
120
|
-
return "macos";
|
|
121
|
-
case "win32":
|
|
122
|
-
return "windows";
|
|
123
|
-
case "linux":
|
|
124
|
-
return "linux";
|
|
125
|
-
default:
|
|
126
|
-
return "macos";
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
var detectProject = (cwd) => {
|
|
130
|
-
let dir = cwd;
|
|
131
|
-
while (true) {
|
|
132
|
-
const uiDir = path.join(dir, "ui");
|
|
133
|
-
const srcDir = path.join(dir, "src");
|
|
134
|
-
const pkgJson = path.join(dir, "package.json");
|
|
135
|
-
if (fs.existsSync(pkgJson) && fs.existsSync(uiDir) && fs.existsSync(srcDir)) {
|
|
136
|
-
const csproj = findHostCsproj(srcDir);
|
|
137
|
-
if (csproj) {
|
|
138
|
-
const projectName = path.basename(csproj.dir).replace(/\.Host$/, "");
|
|
139
|
-
const displayVersion = readCsprojVersion(csproj.path);
|
|
140
|
-
return {
|
|
141
|
-
root: dir,
|
|
142
|
-
hostDir: csproj.dir,
|
|
143
|
-
csprojPath: csproj.path,
|
|
144
|
-
projectName,
|
|
145
|
-
displayVersion,
|
|
146
|
-
uiDir
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const parent = path.dirname(dir);
|
|
151
|
-
if (parent === dir) break;
|
|
152
|
-
dir = parent;
|
|
153
|
-
}
|
|
154
|
-
console.error(
|
|
155
|
-
row({
|
|
156
|
-
glyph: "error",
|
|
157
|
-
detail: dim(
|
|
158
|
-
"Could not detect Vidra project. Run this command from your project root."
|
|
159
|
-
)
|
|
160
|
-
})
|
|
161
|
-
);
|
|
162
|
-
console.error(
|
|
163
|
-
footer(dim("expected: package.json, ui/, src/<Name>.Host/<Name>.Host.csproj"))
|
|
164
|
-
);
|
|
165
|
-
process.exit(1);
|
|
166
|
-
};
|
|
167
|
-
var findHostCsproj = (srcDir) => {
|
|
168
|
-
if (!fs.existsSync(srcDir)) return null;
|
|
169
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
170
|
-
if (entry.isDirectory() && entry.name.endsWith(".Host")) {
|
|
171
|
-
const csprojPath = path.join(
|
|
172
|
-
srcDir,
|
|
173
|
-
entry.name,
|
|
174
|
-
`${entry.name}.csproj`
|
|
175
|
-
);
|
|
176
|
-
if (fs.existsSync(csprojPath)) {
|
|
177
|
-
return { dir: path.join(srcDir, entry.name), path: csprojPath };
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return null;
|
|
182
|
-
};
|
|
183
|
-
var readCsprojVersion = (csprojPath) => {
|
|
184
|
-
const content = fs.readFileSync(csprojPath, "utf-8");
|
|
185
|
-
const match = content.match(
|
|
186
|
-
/<ApplicationDisplayVersion>(.*?)<\/ApplicationDisplayVersion>/
|
|
187
|
-
);
|
|
188
|
-
return match?.[1] ?? "0.1.0";
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
// src/targets/macos.ts
|
|
192
|
-
import path2 from "path";
|
|
193
|
-
import fs2 from "fs-extra";
|
|
194
|
-
import { execSync as execSync2 } from "child_process";
|
|
195
|
-
import os from "os";
|
|
196
|
-
var macosTarget = {
|
|
197
|
-
name: "macos",
|
|
198
|
-
framework: "net10.0-maccatalyst",
|
|
199
|
-
extraPublishArgs: "-p:CreatePackage=false",
|
|
200
|
-
findBundle(publishDir, _projectName) {
|
|
201
|
-
if (!fs2.existsSync(publishDir)) return null;
|
|
202
|
-
for (const entry of fs2.readdirSync(publishDir, { withFileTypes: true })) {
|
|
203
|
-
if (entry.name.endsWith(".app") && entry.isDirectory()) {
|
|
204
|
-
return path2.join(publishDir, entry.name);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return null;
|
|
208
|
-
},
|
|
209
|
-
async package(appPath, outputDir, meta) {
|
|
210
|
-
const dmgName = `${meta.projectName}-${meta.displayVersion}-macos.dmg`;
|
|
211
|
-
const dmgPath = path2.join(outputDir, dmgName);
|
|
212
|
-
const volName = meta.projectName;
|
|
213
|
-
const staging = fs2.mkdtempSync(path2.join(os.tmpdir(), "vidra-dmg-"));
|
|
214
|
-
try {
|
|
215
|
-
fs2.copySync(appPath, path2.join(staging, path2.basename(appPath)));
|
|
216
|
-
fs2.symlinkSync("/Applications", path2.join(staging, "Applications"));
|
|
217
|
-
if (fs2.existsSync(dmgPath)) {
|
|
218
|
-
fs2.removeSync(dmgPath);
|
|
219
|
-
}
|
|
220
|
-
execSync2(
|
|
221
|
-
`hdiutil create -volname "${volName}" -srcfolder "${staging}" -ov -format UDZO "${dmgPath}"`,
|
|
222
|
-
{ stdio: "pipe" }
|
|
223
|
-
);
|
|
224
|
-
} finally {
|
|
225
|
-
fs2.removeSync(staging);
|
|
226
|
-
}
|
|
227
|
-
return dmgPath;
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
// src/signing.ts
|
|
232
|
-
import path3 from "path";
|
|
233
|
-
import { execFileSync } from "child_process";
|
|
234
|
-
var signMacAppBundleIfPossible = (appBundle, options) => {
|
|
235
|
-
if (process.platform !== "darwin") return;
|
|
236
|
-
const identity = resolveMacCodeSigningIdentity();
|
|
237
|
-
const signWith = identity ?? "-";
|
|
238
|
-
const label = path3.basename(appBundle);
|
|
239
|
-
try {
|
|
240
|
-
execFileSync(
|
|
241
|
-
"codesign",
|
|
242
|
-
["--force", "--deep", "--sign", signWith, appBundle],
|
|
243
|
-
{
|
|
244
|
-
stdio: options.verbose ? "inherit" : "pipe"
|
|
245
|
-
}
|
|
246
|
-
);
|
|
247
|
-
options.log(
|
|
248
|
-
row({
|
|
249
|
-
glyph: "done",
|
|
250
|
-
label: "codesign",
|
|
251
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
252
|
-
detail: identity ? `${value(label)} ${dim(`with ${identity}`)}` : `${value(label)} ${dim("ad-hoc (-)")}`
|
|
253
|
-
})
|
|
254
|
-
);
|
|
255
|
-
} catch (error) {
|
|
256
|
-
options.warn(
|
|
257
|
-
row({
|
|
258
|
-
glyph: "manual",
|
|
259
|
-
label: "codesign",
|
|
260
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
261
|
-
detail: dim("could not sign the app bundle; it may fail to launch")
|
|
262
|
-
})
|
|
263
|
-
);
|
|
264
|
-
options.warn(
|
|
265
|
-
footer(
|
|
266
|
-
dim(
|
|
267
|
-
"install Xcode or the Command Line Tools (provides `codesign`), or set VIDRA_MACOS_CODESIGN_KEY."
|
|
268
|
-
)
|
|
269
|
-
)
|
|
270
|
-
);
|
|
271
|
-
options.warn(dim(formatExecError(error)));
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
var resolveMacCodeSigningIdentity = () => {
|
|
275
|
-
const override = process.env.VIDRA_MACOS_CODESIGN_KEY?.trim();
|
|
276
|
-
if (override) {
|
|
277
|
-
return override;
|
|
278
|
-
}
|
|
279
|
-
try {
|
|
280
|
-
const output = execFileSync(
|
|
281
|
-
"security",
|
|
282
|
-
["find-identity", "-v", "-p", "codesigning"],
|
|
283
|
-
{
|
|
284
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
285
|
-
encoding: "utf8"
|
|
286
|
-
}
|
|
287
|
-
);
|
|
288
|
-
const identities = output.split(/\r?\n/).map((line) => line.match(/"([^"]+)"/)?.[1] ?? null).filter((value2) => value2 !== null);
|
|
289
|
-
return identities.find((identity) => identity.startsWith("Apple Development:")) ?? identities.find((identity) => identity.startsWith("Developer ID Application:")) ?? null;
|
|
290
|
-
} catch {
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
var formatExecError = (error) => {
|
|
295
|
-
const err = error;
|
|
296
|
-
if (Buffer.isBuffer(err.stderr)) {
|
|
297
|
-
return err.stderr.toString();
|
|
298
|
-
}
|
|
299
|
-
if (typeof err.stderr === "string") {
|
|
300
|
-
return err.stderr;
|
|
301
|
-
}
|
|
302
|
-
return err.message ?? String(error);
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
// src/targets/windows.ts
|
|
306
|
-
import path4 from "path";
|
|
307
|
-
import os2 from "os";
|
|
308
|
-
import fs3 from "fs-extra";
|
|
309
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
310
|
-
var windowsTarget = {
|
|
311
|
-
name: "windows",
|
|
312
|
-
framework: "net10.0-windows10.0.19041.0",
|
|
313
|
-
// `RuntimeIdentifierOverride` (rather than `-r`/`RuntimeIdentifier`) is the
|
|
314
|
-
// MAUI-recommended way to set the Windows RID — it sidesteps WindowsAppSDK
|
|
315
|
-
// issue #3337, which otherwise pulls in the wrong packaging assets.
|
|
316
|
-
extraPublishArgs: "-p:WindowsPackageType=None -p:SelfContained=true -p:WindowsAppSDKSelfContained=true -p:RuntimeIdentifierOverride=win-x64",
|
|
317
|
-
findBundle(publishDir, _projectName) {
|
|
318
|
-
const preferred = path4.join(publishDir, "win-x64", "publish");
|
|
319
|
-
if (dirContainsExe(preferred)) return preferred;
|
|
320
|
-
return findDirWithExe(publishDir);
|
|
321
|
-
},
|
|
322
|
-
async package(publishOutputDir, outputDir, meta) {
|
|
323
|
-
const outName = `${meta.projectName}-${meta.displayVersion}-windows.zip`;
|
|
324
|
-
const outPath = path4.join(outputDir, outName);
|
|
325
|
-
if (fs3.existsSync(outPath)) fs3.removeSync(outPath);
|
|
326
|
-
const staging = fs3.mkdtempSync(path4.join(os2.tmpdir(), "vidra-zip-"));
|
|
327
|
-
const stagedApp = path4.join(staging, meta.projectName);
|
|
328
|
-
try {
|
|
329
|
-
fs3.copySync(publishOutputDir, stagedApp);
|
|
330
|
-
execFileSync2(
|
|
331
|
-
"powershell",
|
|
332
|
-
[
|
|
333
|
-
"-NoProfile",
|
|
334
|
-
"-NonInteractive",
|
|
335
|
-
"-Command",
|
|
336
|
-
`Compress-Archive -Path "${stagedApp}" -DestinationPath "${outPath}" -Force`
|
|
337
|
-
],
|
|
338
|
-
{ stdio: "pipe" }
|
|
339
|
-
);
|
|
340
|
-
} finally {
|
|
341
|
-
fs3.removeSync(staging);
|
|
342
|
-
}
|
|
343
|
-
return outPath;
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
var dirContainsExe = (dir) => fs3.existsSync(dir) && fs3.readdirSync(dir, { withFileTypes: true }).some((e) => e.isFile() && e.name.toLowerCase().endsWith(".exe"));
|
|
347
|
-
var findDirWithExe = (root) => {
|
|
348
|
-
if (!fs3.existsSync(root)) return null;
|
|
349
|
-
let fallback = null;
|
|
350
|
-
const walk = (dir) => {
|
|
351
|
-
if (dirContainsExe(dir)) {
|
|
352
|
-
if (path4.basename(dir) === "publish") return dir;
|
|
353
|
-
fallback ??= dir;
|
|
354
|
-
}
|
|
355
|
-
for (const e of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
356
|
-
if (e.isDirectory()) {
|
|
357
|
-
const found = walk(path4.join(dir, e.name));
|
|
358
|
-
if (found) return found;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return null;
|
|
362
|
-
};
|
|
363
|
-
return walk(root) ?? fallback;
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
// src/doctor.ts
|
|
367
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
368
|
-
import prompts from "prompts";
|
|
369
|
-
var DOTNET = process.platform === "win32" ? "dotnet.exe" : "dotnet";
|
|
370
|
-
var MAUI_DOCS = "https://learn.microsoft.com/dotnet/maui/get-started/installation";
|
|
371
|
-
var INSTALL_NET_10_FIX = "Install the .NET 10 SDK \u2014 https://dotnet.microsoft.com/download";
|
|
372
|
-
var bufToStr = (v) => v == null ? "" : Buffer.isBuffer(v) ? v.toString() : v;
|
|
373
|
-
var run = (cmd, args) => {
|
|
374
|
-
try {
|
|
375
|
-
const stdout = execFileSync3(cmd, args, {
|
|
376
|
-
encoding: "utf-8",
|
|
377
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
378
|
-
});
|
|
379
|
-
return { found: true, ok: true, stdout: stdout ?? "", stderr: "" };
|
|
380
|
-
} catch (e) {
|
|
381
|
-
const err = e;
|
|
382
|
-
return {
|
|
383
|
-
found: err.code !== "ENOENT",
|
|
384
|
-
ok: false,
|
|
385
|
-
stdout: bufToStr(err.stdout),
|
|
386
|
-
stderr: bufToStr(err.stderr)
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
var splitLines = (text) => text.split(/\r?\n/);
|
|
391
|
-
var matchesAny = (text, patterns) => patterns.some((pattern) => pattern.test(text));
|
|
392
|
-
var NET_10_VERSION = /^10\./;
|
|
393
|
-
var WORKLOAD_SET_VERSION_LINE = /Workload version:\s*([\w.-]+)/i;
|
|
394
|
-
var MAUI_WORKLOAD = /\bmaui\b/i;
|
|
395
|
-
var COMMAND_LINE_TOOLS_PATH = /CommandLineTools/i;
|
|
396
|
-
var hasNet10Sdk = (listSdksOutput) => splitLines(listSdksOutput).some((line) => NET_10_VERSION.test(line.trim()));
|
|
397
|
-
var newestNet10Sdk = (listSdksOutput) => splitLines(listSdksOutput).map((line) => line.trim().split(/\s+/)[0]).filter((version) => NET_10_VERSION.test(version)).sort(
|
|
398
|
-
(a, b) => a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" })
|
|
399
|
-
).pop();
|
|
400
|
-
var outputMentionsMaui = (workloadListOutput) => MAUI_WORKLOAD.test(workloadListOutput);
|
|
401
|
-
var workloadSetVersion = (workloadListOutput) => workloadListOutput.match(WORKLOAD_SET_VERSION_LINE)?.[1];
|
|
402
|
-
var MIN_MACCATALYST_WATCH_WORKLOAD_SET = [10, 0, 203];
|
|
403
|
-
var MIN_MACCATALYST_WATCH_SDK = [10, 0, 300];
|
|
404
|
-
var meetsMinimumVersion = (version, minimum) => {
|
|
405
|
-
const segments = version.split(".").map((s) => Number.parseInt(s, 10));
|
|
406
|
-
if (segments.slice(0, minimum.length).some(Number.isNaN)) return false;
|
|
407
|
-
for (let i = 0; i < minimum.length; i++) {
|
|
408
|
-
const segment = segments[i] ?? 0;
|
|
409
|
-
if (segment !== minimum[i]) return segment > minimum[i];
|
|
410
|
-
}
|
|
411
|
-
return true;
|
|
412
|
-
};
|
|
413
|
-
var workloadSetSupportsCSharpHotReload = (version) => meetsMinimumVersion(version, MIN_MACCATALYST_WATCH_WORKLOAD_SET);
|
|
414
|
-
var sdkSupportsCSharpHotReload = (version) => meetsMinimumVersion(version, MIN_MACCATALYST_WATCH_SDK);
|
|
415
|
-
var macCatalystHotReloadBlocker = () => {
|
|
416
|
-
const sdkRes = run(DOTNET, ["--version"]);
|
|
417
|
-
if (!sdkRes.found) return null;
|
|
418
|
-
const sdkVersion = sdkRes.stdout.trim();
|
|
419
|
-
if (sdkVersion && !sdkSupportsCSharpHotReload(sdkVersion)) {
|
|
420
|
-
return {
|
|
421
|
-
reason: `C# hot reload needs the .NET SDK 10.0.300+ \u2014 found ${sdkVersion}`,
|
|
422
|
-
fix: INSTALL_NET_10_FIX
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
const res = run(DOTNET, ["workload", "list"]);
|
|
426
|
-
if (!res.found) return null;
|
|
427
|
-
const version = workloadSetVersion(res.stdout);
|
|
428
|
-
if (!version || workloadSetSupportsCSharpHotReload(version)) return null;
|
|
429
|
-
return {
|
|
430
|
-
reason: `C# hot reload needs workload set 10.0.203+ \u2014 found ${version}`,
|
|
431
|
-
fix: "dotnet workload update"
|
|
432
|
-
};
|
|
433
|
-
};
|
|
434
|
-
var MISSING_WORKLOAD_SIGNATURES = [
|
|
435
|
-
/NETSDK1147/i,
|
|
436
|
-
/workloads?\s+must\s+be\s+installed/i,
|
|
437
|
-
/maui-maccatalyst/i,
|
|
438
|
-
/maui-windows/i,
|
|
439
|
-
/to\s+install\s+the\s+.*workload/i
|
|
440
|
-
];
|
|
441
|
-
var MISSING_XCODE_SIGNATURES = [
|
|
442
|
-
/valid\s+Xcode\s+installation\s+was\s+not\s+found/i,
|
|
443
|
-
/could\s+not\s+find\s+a\s+valid\s+Xcode\s+app\s+bundle/i,
|
|
444
|
-
/macios-missing-xcode/i
|
|
445
|
-
];
|
|
446
|
-
var OUTDATED_XCODE_SIGNATURES = [
|
|
447
|
-
/error\s+MT0180/i,
|
|
448
|
-
/requires\s+the\s+MacCatalyst\s+\S+\s+SDK\s+\(shipped\s+with\s+Xcode/i
|
|
449
|
-
];
|
|
450
|
-
var looksLikeMissingWorkload = (output) => matchesAny(output, MISSING_WORKLOAD_SIGNATURES);
|
|
451
|
-
var looksLikeMissingXcode = (output) => matchesAny(output, MISSING_XCODE_SIGNATURES);
|
|
452
|
-
var looksLikeXcodeTooOld = (output) => matchesAny(output, OUTDATED_XCODE_SIGNATURES);
|
|
453
|
-
var checkDotnetSdk = () => {
|
|
454
|
-
const name = ".NET SDK";
|
|
455
|
-
const res = run(DOTNET, ["--list-sdks"]);
|
|
456
|
-
if (!res.found) {
|
|
457
|
-
return {
|
|
458
|
-
name,
|
|
459
|
-
status: "missing",
|
|
460
|
-
detail: "`dotnet` was not found on your PATH",
|
|
461
|
-
fix: INSTALL_NET_10_FIX
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
if (!res.ok && !res.stdout) {
|
|
465
|
-
return { name, status: "unknown", detail: "could not run `dotnet --list-sdks`" };
|
|
466
|
-
}
|
|
467
|
-
if (hasNet10Sdk(res.stdout)) {
|
|
468
|
-
const newest = newestNet10Sdk(res.stdout);
|
|
469
|
-
return { name, status: "ok", detail: newest ? `found ${newest}` : "found 10.x" };
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
472
|
-
name,
|
|
473
|
-
status: "missing",
|
|
474
|
-
detail: "no 10.x SDK installed",
|
|
475
|
-
fix: INSTALL_NET_10_FIX
|
|
476
|
-
};
|
|
477
|
-
};
|
|
478
|
-
var checkMauiWorkload = (workloadList) => {
|
|
479
|
-
const name = ".NET MAUI workload";
|
|
480
|
-
if (!workloadList) {
|
|
481
|
-
return { name, status: "unknown", detail: "requires the .NET SDK first" };
|
|
482
|
-
}
|
|
483
|
-
if (!workloadList.found) {
|
|
484
|
-
return { name, status: "unknown", detail: "could not query workloads" };
|
|
485
|
-
}
|
|
486
|
-
if (outputMentionsMaui(workloadList.stdout)) {
|
|
487
|
-
return { name, status: "ok", detail: "installed" };
|
|
488
|
-
}
|
|
489
|
-
return {
|
|
490
|
-
name,
|
|
491
|
-
status: "missing",
|
|
492
|
-
detail: "not installed",
|
|
493
|
-
fix: "dotnet workload install maui"
|
|
494
|
-
};
|
|
495
|
-
};
|
|
496
|
-
var checkCSharpHotReload = (workloadList) => {
|
|
497
|
-
const name = "C# hot reload";
|
|
498
|
-
if (!workloadList) {
|
|
499
|
-
return { name, status: "unknown", detail: "requires the .NET SDK first" };
|
|
500
|
-
}
|
|
501
|
-
if (process.platform !== "darwin") {
|
|
502
|
-
return { name, status: "ok", detail: "dotnet watch supported" };
|
|
503
|
-
}
|
|
504
|
-
const sdkVersion = run(DOTNET, ["--version"]).stdout.trim();
|
|
505
|
-
if (sdkVersion && !sdkSupportsCSharpHotReload(sdkVersion)) {
|
|
506
|
-
return {
|
|
507
|
-
name,
|
|
508
|
-
status: "unknown",
|
|
509
|
-
detail: `SDK ${sdkVersion} \u2014 its dotnet-watch crashes Mac Catalyst apps on launch (needs 10.0.300+) \u2014 vidra dev falls back to a classic launch`,
|
|
510
|
-
fix: INSTALL_NET_10_FIX
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
const version = workloadSetVersion(workloadList.stdout);
|
|
514
|
-
if (!version) {
|
|
515
|
-
return {
|
|
516
|
-
name,
|
|
517
|
-
status: "unknown",
|
|
518
|
-
detail: "could not read the workload set version"
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
if (workloadSetSupportsCSharpHotReload(version)) {
|
|
522
|
-
return { name, status: "ok", detail: `workload set ${version}` };
|
|
523
|
-
}
|
|
524
|
-
return {
|
|
525
|
-
name,
|
|
526
|
-
status: "unknown",
|
|
527
|
-
detail: `workload set ${version} predates Mac Catalyst dotnet-watch support (needs 10.0.203+) \u2014 vidra dev falls back to a classic launch`,
|
|
528
|
-
fix: "dotnet workload update"
|
|
529
|
-
};
|
|
530
|
-
};
|
|
531
|
-
var checkXcode = () => {
|
|
532
|
-
const name = "Xcode";
|
|
533
|
-
const res = run("xcode-select", ["-p"]);
|
|
534
|
-
if (!res.found || !res.ok) {
|
|
535
|
-
return {
|
|
536
|
-
name,
|
|
537
|
-
status: "missing",
|
|
538
|
-
detail: "not found",
|
|
539
|
-
fix: "Install Xcode from the App Store"
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
const devDir = res.stdout.trim();
|
|
543
|
-
if (COMMAND_LINE_TOOLS_PATH.test(devDir)) {
|
|
544
|
-
return {
|
|
545
|
-
name,
|
|
546
|
-
status: "missing",
|
|
547
|
-
detail: "only Command Line Tools detected (Mac Catalyst needs full Xcode)",
|
|
548
|
-
fix: "Install Xcode, then: sudo xcode-select -s /Applications/Xcode.app"
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
return { name, status: "ok", detail: devDir };
|
|
552
|
-
};
|
|
553
|
-
var isMauiWorkloadInstalled = () => outputMentionsMaui(run(DOTNET, ["workload", "list"]).stdout);
|
|
554
|
-
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
555
|
-
var collectRequirements = (opts = {}) => {
|
|
556
|
-
const dotnet = checkDotnetSdk();
|
|
557
|
-
const workloadList = dotnet.status === "ok" ? run(DOTNET, ["workload", "list"]) : null;
|
|
558
|
-
const reqs = [
|
|
559
|
-
dotnet,
|
|
560
|
-
checkMauiWorkload(workloadList),
|
|
561
|
-
checkCSharpHotReload(workloadList)
|
|
562
|
-
];
|
|
563
|
-
if (opts.includeXcode ?? process.platform === "darwin") {
|
|
564
|
-
reqs.push(checkXcode());
|
|
565
|
-
}
|
|
566
|
-
return reqs;
|
|
567
|
-
};
|
|
568
|
-
var STATUS_GLYPH = {
|
|
569
|
-
ok: "done",
|
|
570
|
-
missing: "error",
|
|
571
|
-
unknown: "manual"
|
|
572
|
-
};
|
|
573
|
-
var printRequirements = (reqs) => {
|
|
574
|
-
const labelWidth = Math.max(0, ...reqs.map((r) => r.name.length)) + 2;
|
|
575
|
-
for (const r of reqs) {
|
|
576
|
-
console.log(
|
|
577
|
-
row({
|
|
578
|
-
glyph: STATUS_GLYPH[r.status],
|
|
579
|
-
label: r.name,
|
|
580
|
-
labelWidth,
|
|
581
|
-
detail: r.detail ? dim(r.detail) : void 0
|
|
582
|
-
})
|
|
583
|
-
);
|
|
584
|
-
if (r.status !== "ok" && r.fix) {
|
|
585
|
-
console.log(fixLine(r.fix));
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
var runDoctor = async () => {
|
|
590
|
-
console.log();
|
|
591
|
-
console.log(` ${lime("vidra")} ${value("doctor")}`);
|
|
592
|
-
console.log();
|
|
593
|
-
console.log(footer(dim("checking your environment\u2026")));
|
|
594
|
-
console.log();
|
|
595
|
-
const reqs = collectRequirements();
|
|
596
|
-
printRequirements(reqs);
|
|
597
|
-
console.log();
|
|
598
|
-
const missing = reqs.filter((r) => r.status === "missing");
|
|
599
|
-
if (missing.length === 0) {
|
|
600
|
-
console.log(
|
|
601
|
-
footer(
|
|
602
|
-
`${dim("all checks passed \u2014 you're ready to run")} ${lime(
|
|
603
|
-
"npm run dev"
|
|
604
|
-
)}${dim(".")}`
|
|
605
|
-
)
|
|
606
|
-
);
|
|
607
|
-
console.log();
|
|
608
|
-
return 0;
|
|
609
|
-
}
|
|
610
|
-
const n = missing.length;
|
|
611
|
-
console.log(
|
|
612
|
-
footer(
|
|
613
|
-
`${dim(
|
|
614
|
-
`${n} issue${n === 1 ? "" : "s"} found. apply the ${n === 1 ? "fix" : "fixes"} above, then re-run`
|
|
615
|
-
)} ${lime("npm run doctor")}${dim(".")}`
|
|
616
|
-
)
|
|
617
|
-
);
|
|
618
|
-
console.log();
|
|
619
|
-
return 1;
|
|
620
|
-
};
|
|
621
|
-
var installWorkload = (csprojPath) => {
|
|
622
|
-
const args = csprojPath ? ["workload", "restore", csprojPath] : ["workload", "install", "maui"];
|
|
623
|
-
console.log();
|
|
624
|
-
console.log(
|
|
625
|
-
row({
|
|
626
|
-
glyph: "active",
|
|
627
|
-
detail: `${dim("running")} ${value(`${DOTNET} ${args.join(" ")}`)}`
|
|
628
|
-
})
|
|
629
|
-
);
|
|
630
|
-
console.log(
|
|
631
|
-
footer(
|
|
632
|
-
dim("this can download several hundred MB and take a few minutes.")
|
|
633
|
-
)
|
|
634
|
-
);
|
|
635
|
-
console.log();
|
|
636
|
-
try {
|
|
637
|
-
execFileSync3(DOTNET, args, { stdio: "inherit" });
|
|
638
|
-
return true;
|
|
639
|
-
} catch {
|
|
640
|
-
console.error();
|
|
641
|
-
console.error(row({ glyph: "error", label: "workload install failed" }));
|
|
642
|
-
console.error(
|
|
643
|
-
footer(
|
|
644
|
-
dim(
|
|
645
|
-
"if this is a permissions error, your SDK is in a system location and needs elevation:"
|
|
646
|
-
)
|
|
647
|
-
)
|
|
648
|
-
);
|
|
649
|
-
console.error(fixLine("sudo dotnet workload install maui"));
|
|
650
|
-
console.error();
|
|
651
|
-
return false;
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
var ensureMauiWorkload = async (opts = {}) => {
|
|
655
|
-
const dotnet = checkDotnetSdk();
|
|
656
|
-
if (dotnet.status === "missing") {
|
|
657
|
-
console.log();
|
|
658
|
-
console.log(
|
|
659
|
-
row({
|
|
660
|
-
glyph: "error",
|
|
661
|
-
label: dotnet.name,
|
|
662
|
-
detail: dotnet.detail ? dim(dotnet.detail) : void 0
|
|
663
|
-
})
|
|
664
|
-
);
|
|
665
|
-
if (dotnet.fix) {
|
|
666
|
-
console.log(fixLine(dotnet.fix));
|
|
667
|
-
}
|
|
668
|
-
return false;
|
|
669
|
-
}
|
|
670
|
-
if (dotnet.status === "unknown") return true;
|
|
671
|
-
if (isMauiWorkloadInstalled()) return true;
|
|
672
|
-
console.log();
|
|
673
|
-
console.log(
|
|
674
|
-
row({
|
|
675
|
-
glyph: "error",
|
|
676
|
-
label: ".NET MAUI workload",
|
|
677
|
-
detail: dim("required but not installed")
|
|
678
|
-
})
|
|
679
|
-
);
|
|
680
|
-
const interactive = opts.interactive ?? isInteractive();
|
|
681
|
-
if (interactive) {
|
|
682
|
-
let install = false;
|
|
683
|
-
try {
|
|
684
|
-
const res = await prompts({
|
|
685
|
-
type: "confirm",
|
|
686
|
-
name: "install",
|
|
687
|
-
message: "Install the .NET MAUI workload now?",
|
|
688
|
-
initial: true
|
|
689
|
-
});
|
|
690
|
-
install = Boolean(res.install);
|
|
691
|
-
} catch {
|
|
692
|
-
install = false;
|
|
693
|
-
}
|
|
694
|
-
if (install) {
|
|
695
|
-
if (installWorkload(opts.csprojPath) && isMauiWorkloadInstalled()) {
|
|
696
|
-
console.log(
|
|
697
|
-
row({
|
|
698
|
-
glyph: "done",
|
|
699
|
-
label: ".NET MAUI workload",
|
|
700
|
-
detail: dim("installed")
|
|
701
|
-
})
|
|
702
|
-
);
|
|
703
|
-
return true;
|
|
704
|
-
}
|
|
705
|
-
return false;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
console.log(fixLine("dotnet workload install maui", "run:"));
|
|
709
|
-
console.log(fixLine(MAUI_DOCS, "docs:"));
|
|
710
|
-
return false;
|
|
711
|
-
};
|
|
712
|
-
var printWorkloadHint = () => {
|
|
713
|
-
console.error();
|
|
714
|
-
console.error(
|
|
715
|
-
row({ glyph: "manual", label: "this looks like a missing .NET MAUI workload." })
|
|
716
|
-
);
|
|
717
|
-
console.error(fixLine("dotnet workload install maui"));
|
|
718
|
-
console.error(fixLine("vidra doctor", "check:"));
|
|
719
|
-
console.error();
|
|
720
|
-
};
|
|
721
|
-
var printXcodeTooOldHint = () => {
|
|
722
|
-
console.error();
|
|
723
|
-
console.error(
|
|
724
|
-
row({
|
|
725
|
-
glyph: "manual",
|
|
726
|
-
label: "your Xcode is older than the SDK this MAUI workload set expects."
|
|
727
|
-
})
|
|
728
|
-
);
|
|
729
|
-
console.error(
|
|
730
|
-
` ${dim("\u2022")} ${dim("update Xcode (App Store), then")} ${lime("sudo xcodebuild -runFirstLaunch")}`
|
|
731
|
-
);
|
|
732
|
-
console.error(
|
|
733
|
-
` ${dim("\u2022")} ${dim("or pin the workloads to your Xcode's era:")} ${lime("dotnet workload update --version <set>")}`
|
|
734
|
-
);
|
|
735
|
-
console.error(fixLine("vidra doctor", "check:"));
|
|
736
|
-
console.error();
|
|
737
|
-
};
|
|
738
|
-
var printXcodeHint = () => {
|
|
739
|
-
console.error();
|
|
740
|
-
console.error(
|
|
741
|
-
row({
|
|
742
|
-
glyph: "manual",
|
|
743
|
-
label: "Mac Catalyst needs the full Xcode app, not just the Command Line Tools."
|
|
744
|
-
})
|
|
745
|
-
);
|
|
746
|
-
console.error(` ${dim("1.")} ${value("install Xcode from the App Store")}`);
|
|
747
|
-
console.error(
|
|
748
|
-
` ${dim("2.")} ${lime(
|
|
749
|
-
"sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
|
|
750
|
-
)}`
|
|
751
|
-
);
|
|
752
|
-
console.error(` ${dim("3.")} ${lime("sudo xcodebuild -runFirstLaunch")}`);
|
|
753
|
-
console.error(fixLine("vidra doctor", "check:"));
|
|
754
|
-
console.error();
|
|
755
|
-
};
|
|
756
|
-
|
|
757
|
-
// src/commands/build.ts
|
|
758
|
-
var TARGETS = {
|
|
759
|
-
macos: macosTarget,
|
|
760
|
-
windows: windowsTarget
|
|
761
|
-
};
|
|
762
|
-
var packageLabel = (target) => target.name === "macos" ? "package DMG" : "package ZIP";
|
|
763
|
-
var artifactName = (project, target) => `${project.projectName}-${project.displayVersion}-${target.name}.${target.name === "macos" ? "dmg" : "zip"}`;
|
|
764
|
-
var buildCommand = async (argv) => {
|
|
765
|
-
const args = parseArgs(["_", "_", ...argv]);
|
|
766
|
-
const verbose = !!args["verbose"];
|
|
767
|
-
const plan = !!args["plan"] || !!args["dry-run"];
|
|
768
|
-
const targetName = args["target"] || detectPlatform();
|
|
769
|
-
const target = TARGETS[targetName];
|
|
770
|
-
if (!target) {
|
|
771
|
-
const supported = Object.keys(TARGETS).join(", ");
|
|
772
|
-
console.error();
|
|
773
|
-
console.error(
|
|
774
|
-
row({
|
|
775
|
-
glyph: "error",
|
|
776
|
-
detail: dim(`unsupported target: ${targetName} \u2014 supported: ${supported}`)
|
|
777
|
-
})
|
|
778
|
-
);
|
|
779
|
-
process.exit(1);
|
|
780
|
-
}
|
|
781
|
-
const project = detectProject(process.cwd());
|
|
782
|
-
console.log();
|
|
783
|
-
console.log(
|
|
784
|
-
header("build", `${target.name} \xB7 Release${plan ? " \xB7 plan" : ""}`)
|
|
785
|
-
);
|
|
786
|
-
console.log(kv("project", project.projectName));
|
|
787
|
-
console.log(kv("target", target.framework));
|
|
788
|
-
console.log();
|
|
789
|
-
if (plan) {
|
|
790
|
-
printBuildPlan(project, target);
|
|
791
|
-
console.log();
|
|
792
|
-
console.log(
|
|
793
|
-
footer(`${dim("nothing has run. re-run without")} ${lime("--plan")} ${dim("to apply.")}`)
|
|
794
|
-
);
|
|
795
|
-
console.log();
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
if (!await ensureMauiWorkload({ csprojPath: project.csprojPath })) {
|
|
799
|
-
process.exit(1);
|
|
800
|
-
}
|
|
801
|
-
stepBuildUi(project, verbose);
|
|
802
|
-
stepCopyAssets(project);
|
|
803
|
-
const publishDir = stepDotnetPublish(project, target, verbose);
|
|
804
|
-
const bundlePath = target.findBundle(publishDir, project.projectName);
|
|
805
|
-
if (!bundlePath) {
|
|
806
|
-
console.error(
|
|
807
|
-
row({
|
|
808
|
-
glyph: "error",
|
|
809
|
-
detail: dim(`could not find build artifact in ${publishDir}`)
|
|
810
|
-
})
|
|
811
|
-
);
|
|
812
|
-
process.exit(1);
|
|
813
|
-
}
|
|
814
|
-
if (target.name === "macos") {
|
|
815
|
-
signMacAppBundleIfPossible(bundlePath, {
|
|
816
|
-
verbose,
|
|
817
|
-
log: console.log,
|
|
818
|
-
warn: console.warn
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
const outputPath = await stepPackage(project, target, bundlePath);
|
|
822
|
-
console.log();
|
|
823
|
-
console.log(
|
|
824
|
-
footer(
|
|
825
|
-
`${dim("done \u2014")} ${value(path5.relative(project.root, outputPath))}`
|
|
826
|
-
)
|
|
827
|
-
);
|
|
828
|
-
console.log();
|
|
829
|
-
};
|
|
830
|
-
var printBuildPlan = (project, target) => {
|
|
831
|
-
console.log(
|
|
832
|
-
row({
|
|
833
|
-
glyph: "done",
|
|
834
|
-
label: "build UI",
|
|
835
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
836
|
-
detail: `${dim("vite \u2192")} ${value("ui/dist")}`
|
|
837
|
-
})
|
|
838
|
-
);
|
|
839
|
-
console.log(
|
|
840
|
-
row({
|
|
841
|
-
glyph: "done",
|
|
842
|
-
label: "copy assets",
|
|
843
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
844
|
-
detail: `${dim("\u2192")} ${value("Resources/Raw/wwwroot")}`
|
|
845
|
-
})
|
|
846
|
-
);
|
|
847
|
-
console.log(
|
|
848
|
-
row({
|
|
849
|
-
glyph: "done",
|
|
850
|
-
label: "publish .NET",
|
|
851
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
852
|
-
detail: `${dim("Release \xB7")} ${value(target.framework)}`
|
|
853
|
-
})
|
|
854
|
-
);
|
|
855
|
-
if (target.name === "macos") {
|
|
856
|
-
console.log(
|
|
857
|
-
row({
|
|
858
|
-
glyph: "done",
|
|
859
|
-
label: "codesign .app",
|
|
860
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
861
|
-
detail: dim("Apple Development, or ad-hoc (-)")
|
|
862
|
-
})
|
|
863
|
-
);
|
|
864
|
-
console.log(
|
|
865
|
-
row({
|
|
866
|
-
glyph: "plan",
|
|
867
|
-
label: "notarize",
|
|
868
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
869
|
-
detail: planBadge()
|
|
870
|
-
})
|
|
871
|
-
);
|
|
872
|
-
console.log(
|
|
873
|
-
row({
|
|
874
|
-
glyph: "active",
|
|
875
|
-
label: "package DMG",
|
|
876
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
877
|
-
detail: `${dim("hdiutil UDZO \u2192")} ${value(artifactName(project, target))}`
|
|
878
|
-
})
|
|
879
|
-
);
|
|
880
|
-
} else {
|
|
881
|
-
console.log(
|
|
882
|
-
row({
|
|
883
|
-
glyph: "active",
|
|
884
|
-
label: "package ZIP",
|
|
885
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
886
|
-
detail: `${dim("self-contained \u2192")} ${value(artifactName(project, target))}`
|
|
887
|
-
})
|
|
888
|
-
);
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
var stepBuildUi = (project, verbose) => {
|
|
892
|
-
const start = Date.now();
|
|
893
|
-
try {
|
|
894
|
-
execSync3("npm run build", {
|
|
895
|
-
cwd: project.uiDir,
|
|
896
|
-
stdio: verbose ? "inherit" : "pipe"
|
|
897
|
-
});
|
|
898
|
-
} catch (e) {
|
|
899
|
-
console.error(
|
|
900
|
-
row({
|
|
901
|
-
glyph: "error",
|
|
902
|
-
label: "build UI",
|
|
903
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
904
|
-
detail: dim("vite build failed")
|
|
905
|
-
})
|
|
906
|
-
);
|
|
907
|
-
console.error(dim(formatBuildError(e)));
|
|
908
|
-
process.exit(1);
|
|
909
|
-
}
|
|
910
|
-
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
911
|
-
console.log(
|
|
912
|
-
row({
|
|
913
|
-
glyph: "done",
|
|
914
|
-
label: "build UI",
|
|
915
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
916
|
-
detail: `${dim("vite \u2192")} ${value("ui/dist")} ${dim(`(${elapsed}s)`)}`
|
|
917
|
-
})
|
|
918
|
-
);
|
|
919
|
-
};
|
|
920
|
-
var stepCopyAssets = (project) => {
|
|
921
|
-
const viteDist = path5.join(project.uiDir, "dist");
|
|
922
|
-
if (!fs4.existsSync(viteDist)) {
|
|
923
|
-
console.error(
|
|
924
|
-
row({
|
|
925
|
-
glyph: "error",
|
|
926
|
-
label: "copy assets",
|
|
927
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
928
|
-
detail: dim("ui/dist not found \u2014 vite build may have failed")
|
|
929
|
-
})
|
|
930
|
-
);
|
|
931
|
-
process.exit(1);
|
|
932
|
-
}
|
|
933
|
-
const wwwroot = path5.join(project.hostDir, "Resources", "Raw", "wwwroot");
|
|
934
|
-
fs4.removeSync(wwwroot);
|
|
935
|
-
fs4.copySync(viteDist, wwwroot);
|
|
936
|
-
const fileCount = countFiles(wwwroot);
|
|
937
|
-
console.log(
|
|
938
|
-
row({
|
|
939
|
-
glyph: "done",
|
|
940
|
-
label: "copy assets",
|
|
941
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
942
|
-
detail: `${dim("\u2192")} ${value("Resources/Raw/wwwroot")} ${dim(`(${fileCount} files)`)}`
|
|
943
|
-
})
|
|
944
|
-
);
|
|
945
|
-
};
|
|
946
|
-
var countFiles = (dir) => {
|
|
947
|
-
let count = 0;
|
|
948
|
-
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
949
|
-
if (entry.isDirectory()) {
|
|
950
|
-
count += countFiles(path5.join(dir, entry.name));
|
|
951
|
-
} else {
|
|
952
|
-
count++;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
return count;
|
|
956
|
-
};
|
|
957
|
-
var stepDotnetPublish = (project, target, verbose) => {
|
|
958
|
-
const start = Date.now();
|
|
959
|
-
const extraArgs = target.extraPublishArgs ?? "-p:CreatePackage=false";
|
|
960
|
-
try {
|
|
961
|
-
execSync3(
|
|
962
|
-
`dotnet publish "${project.csprojPath}" -c Release -f ${target.framework} ${extraArgs}`,
|
|
963
|
-
{
|
|
964
|
-
cwd: project.root,
|
|
965
|
-
stdio: verbose ? "inherit" : "pipe"
|
|
966
|
-
}
|
|
967
|
-
);
|
|
968
|
-
} catch (e) {
|
|
969
|
-
const output = formatBuildError(e);
|
|
970
|
-
console.error(
|
|
971
|
-
row({
|
|
972
|
-
glyph: "error",
|
|
973
|
-
label: "publish .NET",
|
|
974
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
975
|
-
detail: dim("dotnet publish failed")
|
|
976
|
-
})
|
|
977
|
-
);
|
|
978
|
-
console.error(dim(output));
|
|
979
|
-
if (looksLikeMissingWorkload(output)) printWorkloadHint();
|
|
980
|
-
else if (looksLikeMissingXcode(output)) printXcodeHint();
|
|
981
|
-
if (!verbose) {
|
|
982
|
-
console.error(footer(dim("re-run with --verbose for the full build log.")));
|
|
983
|
-
}
|
|
984
|
-
process.exit(1);
|
|
985
|
-
}
|
|
986
|
-
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
987
|
-
console.log(
|
|
988
|
-
row({
|
|
989
|
-
glyph: "done",
|
|
990
|
-
label: "publish .NET",
|
|
991
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
992
|
-
detail: `${dim("Release \xB7")} ${value(target.framework)} ${dim(`(${elapsed}s)`)}`
|
|
993
|
-
})
|
|
994
|
-
);
|
|
995
|
-
return path5.join(project.hostDir, "bin", "Release", target.framework);
|
|
996
|
-
};
|
|
997
|
-
var stepPackage = async (project, target, bundlePath) => {
|
|
998
|
-
const outputDir = path5.join(project.root, "dist");
|
|
999
|
-
fs4.ensureDirSync(outputDir);
|
|
1000
|
-
const start = Date.now();
|
|
1001
|
-
let outputPath;
|
|
1002
|
-
try {
|
|
1003
|
-
outputPath = await target.package(bundlePath, outputDir, {
|
|
1004
|
-
projectName: project.projectName,
|
|
1005
|
-
displayVersion: project.displayVersion
|
|
1006
|
-
});
|
|
1007
|
-
} catch (e) {
|
|
1008
|
-
console.error(
|
|
1009
|
-
row({
|
|
1010
|
-
glyph: "error",
|
|
1011
|
-
label: packageLabel(target),
|
|
1012
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
1013
|
-
detail: dim("packaging failed")
|
|
1014
|
-
})
|
|
1015
|
-
);
|
|
1016
|
-
console.error(dim(formatProcessError(e)));
|
|
1017
|
-
process.exit(1);
|
|
1018
|
-
}
|
|
1019
|
-
const pkgTime = ((Date.now() - start) / 1e3).toFixed(1);
|
|
1020
|
-
const sizeMB = (fs4.statSync(outputPath).size / (1024 * 1024)).toFixed(1);
|
|
1021
|
-
console.log(
|
|
1022
|
-
row({
|
|
1023
|
-
glyph: "done",
|
|
1024
|
-
label: packageLabel(target),
|
|
1025
|
-
labelWidth: STEP_LABEL_WIDTH,
|
|
1026
|
-
detail: `${value(path5.basename(outputPath))} ${dim(`(${sizeMB} MB, ${pkgTime}s)`)}`
|
|
1027
|
-
})
|
|
1028
|
-
);
|
|
1029
|
-
return outputPath;
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
// src/commands/dev.ts
|
|
1033
|
-
import path6 from "path";
|
|
1034
|
-
import fs5 from "fs-extra";
|
|
1035
|
-
import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
|
|
1036
|
-
import { request } from "http";
|
|
1037
|
-
|
|
1038
|
-
// src/dev-port.ts
|
|
1039
|
-
import { createServer } from "net";
|
|
1040
|
-
import prompts2 from "prompts";
|
|
1041
|
-
var MIN_PORT = 1;
|
|
1042
|
-
var MAX_PORT = 65535;
|
|
1043
|
-
var isValidPort = (port) => Number.isInteger(port) && port >= MIN_PORT && port <= MAX_PORT;
|
|
1044
|
-
var isPortAvailable = (port, hostname) => new Promise((resolve, reject) => {
|
|
1045
|
-
const server = createServer();
|
|
1046
|
-
server.unref();
|
|
1047
|
-
server.once("error", (error) => {
|
|
1048
|
-
if (error.code === "EADDRINUSE" || error.code === "EACCES") {
|
|
1049
|
-
resolve(false);
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
reject(error);
|
|
1053
|
-
});
|
|
1054
|
-
server.listen({ host: hostname, port, exclusive: true }, () => {
|
|
1055
|
-
server.close((error) => {
|
|
1056
|
-
if (error) {
|
|
1057
|
-
reject(error);
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
resolve(true);
|
|
1061
|
-
});
|
|
1062
|
-
});
|
|
1063
|
-
});
|
|
1064
|
-
var findNextAvailablePort = async (occupiedPort, hostname, probe = isPortAvailable) => {
|
|
1065
|
-
for (let port = occupiedPort + 1; port <= MAX_PORT; port++) {
|
|
1066
|
-
if (await probe(port, hostname)) return port;
|
|
1067
|
-
}
|
|
1068
|
-
throw new Error(`No available port found after ${occupiedPort}`);
|
|
1069
|
-
};
|
|
1070
|
-
var defaultPrompt = async (question) => {
|
|
1071
|
-
const result = await prompts2(question, { onCancel: () => false });
|
|
1072
|
-
return { port: result.port };
|
|
1073
|
-
};
|
|
1074
|
-
var selectDevServerUrl = async (configuredUrl, options = {}) => {
|
|
1075
|
-
const url = new URL(configuredUrl);
|
|
1076
|
-
const port = Number(url.port);
|
|
1077
|
-
if (!isValidPort(port)) {
|
|
1078
|
-
throw new Error(`Dev server URL must include a valid port: ${configuredUrl}`);
|
|
1079
|
-
}
|
|
1080
|
-
const probe = options.isPortAvailable ?? isPortAvailable;
|
|
1081
|
-
if (await probe(port, url.hostname)) return configuredUrl;
|
|
1082
|
-
const interactive = options.interactive ?? isInteractive();
|
|
1083
|
-
if (!interactive) {
|
|
1084
|
-
throw new Error(
|
|
1085
|
-
`Port ${port} is already in use and no interactive terminal is available`
|
|
1086
|
-
);
|
|
1087
|
-
}
|
|
1088
|
-
const suggestedPort = await findNextAvailablePort(port, url.hostname, probe);
|
|
1089
|
-
const prompt = options.prompt ?? defaultPrompt;
|
|
1090
|
-
const result = await prompt({
|
|
1091
|
-
type: "number",
|
|
1092
|
-
name: "port",
|
|
1093
|
-
message: `Port ${port} is already in use. Choose another port:`,
|
|
1094
|
-
initial: suggestedPort,
|
|
1095
|
-
validate: async (value2) => {
|
|
1096
|
-
if (!isValidPort(value2)) {
|
|
1097
|
-
return `Enter a port between ${MIN_PORT} and ${MAX_PORT}`;
|
|
1098
|
-
}
|
|
1099
|
-
return await probe(value2, url.hostname) || `Port ${value2} is already in use`;
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
const selectedPort = result.port;
|
|
1103
|
-
if (selectedPort === void 0) {
|
|
1104
|
-
throw new Error("Port selection cancelled");
|
|
1105
|
-
}
|
|
1106
|
-
if (!isValidPort(selectedPort) || !await probe(selectedPort, url.hostname)) {
|
|
1107
|
-
throw new Error(`Port ${selectedPort} is not available`);
|
|
1108
|
-
}
|
|
1109
|
-
url.port = String(selectedPort);
|
|
1110
|
-
return url.toString();
|
|
1111
|
-
};
|
|
1112
|
-
|
|
1113
|
-
// src/commands/dev.ts
|
|
1114
|
-
var POLL_INTERVAL_MS = 500;
|
|
1115
|
-
var POLL_TIMEOUT_MS = 3e4;
|
|
1116
|
-
var NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1117
|
-
var DOTNET_COMMAND = process.platform === "win32" ? "dotnet.exe" : "dotnet";
|
|
1118
|
-
var WATCH_OUTPUT_TAIL_CHARS = 8192;
|
|
1119
|
-
var TARGETS2 = {
|
|
1120
|
-
macos: {
|
|
1121
|
-
name: "macos",
|
|
1122
|
-
framework: "net10.0-maccatalyst"
|
|
1123
|
-
},
|
|
1124
|
-
windows: {
|
|
1125
|
-
name: "windows",
|
|
1126
|
-
framework: "net10.0-windows10.0.19041.0"
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
var devCommand = (argv) => startSession(argv, { vite: true, hotReloadDefault: true });
|
|
1130
|
-
var runCommand = (argv) => startSession(argv, { vite: false, hotReloadDefault: false });
|
|
1131
|
-
var startSession = async (argv, opts) => {
|
|
1132
|
-
const args = parseArgs(["_", "_", ...argv]);
|
|
1133
|
-
const targetName = args["target"] || detectPlatform();
|
|
1134
|
-
const verbose = !!args["verbose"];
|
|
1135
|
-
let hotReload = opts.hotReloadDefault && !args["no-hot-reload"];
|
|
1136
|
-
let viteUrl = process.env.VIDRA_DEV_URL || "http://localhost:5173";
|
|
1137
|
-
const target = TARGETS2[targetName];
|
|
1138
|
-
if (!target) {
|
|
1139
|
-
const supported = Object.keys(TARGETS2).join(", ");
|
|
1140
|
-
console.error(
|
|
1141
|
-
row({
|
|
1142
|
-
glyph: "error",
|
|
1143
|
-
detail: dim(`unsupported target: ${targetName} \u2014 supported: ${supported}`)
|
|
1144
|
-
})
|
|
1145
|
-
);
|
|
1146
|
-
process.exit(1);
|
|
1147
|
-
}
|
|
1148
|
-
ensureTargetMatchesHostOs(target.name);
|
|
1149
|
-
const project = detectProject(process.cwd());
|
|
1150
|
-
if (!await ensureMauiWorkload({ csprojPath: project.csprojPath })) {
|
|
1151
|
-
process.exit(1);
|
|
1152
|
-
}
|
|
1153
|
-
if (hotReload && target.name === "macos") {
|
|
1154
|
-
const blocker = macCatalystHotReloadBlocker();
|
|
1155
|
-
if (blocker) {
|
|
1156
|
-
hotReload = false;
|
|
1157
|
-
console.log();
|
|
1158
|
-
console.log(
|
|
1159
|
-
row({
|
|
1160
|
-
glyph: "manual",
|
|
1161
|
-
detail: `${dim(blocker.reason + "; using a classic launch")}`
|
|
1162
|
-
})
|
|
1163
|
-
);
|
|
1164
|
-
console.log(fixLine(blocker.fix));
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
if (opts.vite) {
|
|
1168
|
-
viteUrl = await selectDevServerUrl(viteUrl);
|
|
1169
|
-
}
|
|
1170
|
-
const session = new DevSession(project, target, viteUrl, verbose, {
|
|
1171
|
-
vite: opts.vite,
|
|
1172
|
-
hotReload
|
|
1173
|
-
});
|
|
1174
|
-
await session.run();
|
|
1175
|
-
};
|
|
1176
|
-
var buildDotnetWatchArgs = (opts) => [
|
|
1177
|
-
"watch",
|
|
1178
|
-
"--project",
|
|
1179
|
-
opts.csprojPath,
|
|
1180
|
-
// Never prompt on rude edits (our stdio is piped, so a prompt would hang);
|
|
1181
|
-
// paired with DOTNET_WATCH_RESTART_ON_RUDE_EDIT for older SDKs.
|
|
1182
|
-
"--non-interactive",
|
|
1183
|
-
...opts.verbose ? ["--verbose"] : [],
|
|
1184
|
-
"run",
|
|
1185
|
-
"-f",
|
|
1186
|
-
opts.framework,
|
|
1187
|
-
"-c",
|
|
1188
|
-
opts.buildConfig,
|
|
1189
|
-
// Launch the Mac Catalyst app by exec'ing the binary rather than `open -a`,
|
|
1190
|
-
// so the environment (VIDRA_DEV_URL, DOTNET_STARTUP_HOOKS) propagates and
|
|
1191
|
-
// stdout streams back. Ignored by targets that don't define the property.
|
|
1192
|
-
...opts.targetName === "macos" ? ["--property:RunWithOpen=false"] : []
|
|
1193
|
-
];
|
|
1194
|
-
var dotnetWatchEnv = (devUrl) => ({
|
|
1195
|
-
VIDRA_DEV_URL: devUrl,
|
|
1196
|
-
DOTNET_WATCH_RESTART_ON_RUDE_EDIT: "1",
|
|
1197
|
-
DOTNET_WATCH_SUPPRESS_EMOJIS: "1",
|
|
1198
|
-
DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER: "1"
|
|
1199
|
-
});
|
|
1200
|
-
var buildViteArgs = (devUrl) => [
|
|
1201
|
-
"run",
|
|
1202
|
-
"dev",
|
|
1203
|
-
"--",
|
|
1204
|
-
"--port",
|
|
1205
|
-
new URL(devUrl).port,
|
|
1206
|
-
"--strictPort"
|
|
1207
|
-
];
|
|
1208
|
-
var HOST_READY_SENTINEL = "[vidra] host ready";
|
|
1209
|
-
var classifyWatchLine = (line) => {
|
|
1210
|
-
if (/waiting for a file to change/i.test(line)) return "appWaiting";
|
|
1211
|
-
if (/fix the error to continue/i.test(line)) return "buildBlocked";
|
|
1212
|
-
if (line.includes(HOST_READY_SENTINEL)) return "appStarted";
|
|
1213
|
-
if (/\bwatch\b/i.test(line) && /\b(?:started|launched)\b/i.test(line)) {
|
|
1214
|
-
return "appStarted";
|
|
1215
|
-
}
|
|
1216
|
-
return null;
|
|
1217
|
-
};
|
|
1218
|
-
var DevSession = class {
|
|
1219
|
-
constructor(project, target, viteUrl, verbose, options) {
|
|
1220
|
-
this.project = project;
|
|
1221
|
-
this.target = target;
|
|
1222
|
-
this.viteUrl = viteUrl;
|
|
1223
|
-
this.verbose = verbose;
|
|
1224
|
-
this.vite = options.vite;
|
|
1225
|
-
this.hotReload = options.hotReload;
|
|
1226
|
-
}
|
|
1227
|
-
project;
|
|
1228
|
-
target;
|
|
1229
|
-
viteUrl;
|
|
1230
|
-
verbose;
|
|
1231
|
-
children = [];
|
|
1232
|
-
buildConfig = process.env.VIDRA_BUILD_CONFIG || "Debug";
|
|
1233
|
-
vite;
|
|
1234
|
-
hotReload;
|
|
1235
|
-
shuttingDown = false;
|
|
1236
|
-
// Watch-mode state: `watchChild` is the `dotnet watch` process (a process
|
|
1237
|
-
// group leader on unix, so shutdown can signal it and the app it launched
|
|
1238
|
-
// together); `watchReady` flips once the app has started at least once.
|
|
1239
|
-
watchChild;
|
|
1240
|
-
watchReady = false;
|
|
1241
|
-
watchOutputTail = "";
|
|
1242
|
-
endSession = () => {
|
|
1243
|
-
};
|
|
1244
|
-
sessionDone = new Promise((resolve) => {
|
|
1245
|
-
this.endSession = resolve;
|
|
1246
|
-
});
|
|
1247
|
-
async run() {
|
|
1248
|
-
this.installSignalHandlers();
|
|
1249
|
-
console.log();
|
|
1250
|
-
console.log(header(this.vite ? "dev" : "run", this.target.name));
|
|
1251
|
-
console.log(kv("project", this.project.projectName));
|
|
1252
|
-
console.log(kv("target", this.target.framework));
|
|
1253
|
-
console.log();
|
|
1254
|
-
if (this.vite) {
|
|
1255
|
-
this.startVite();
|
|
1256
|
-
try {
|
|
1257
|
-
await waitForServer(this.viteUrl, POLL_TIMEOUT_MS);
|
|
1258
|
-
} catch (error) {
|
|
1259
|
-
console.error(row({ glyph: "error", detail: dim(error.message) }));
|
|
1260
|
-
this.shutdown(1);
|
|
1261
|
-
}
|
|
1262
|
-
console.log(
|
|
1263
|
-
taggedRow("active", "ui", `${dim("vite ready \u2014")} ${value(this.viteUrl)}`)
|
|
1264
|
-
);
|
|
1265
|
-
} else {
|
|
1266
|
-
console.log(
|
|
1267
|
-
taggedRow(
|
|
1268
|
-
"skip",
|
|
1269
|
-
"ui",
|
|
1270
|
-
`${dim("vite not started \u2014")} ${value("npm run dev:ui")}`
|
|
1271
|
-
)
|
|
1272
|
-
);
|
|
1273
|
-
}
|
|
1274
|
-
this.launchHost();
|
|
1275
|
-
const hostDirLabel = `src/${path6.basename(this.project.hostDir)}`;
|
|
1276
|
-
if (this.vite) {
|
|
1277
|
-
console.log(
|
|
1278
|
-
taggedRow(
|
|
1279
|
-
"active",
|
|
1280
|
-
null,
|
|
1281
|
-
this.hotReload ? `${lime("hot reload active")} ${dim(`\u2014 edit ui/src or ${hostDirLabel} and save`)}` : `${lime("hot reload active")} ${dim("\u2014 edit ui/src and save")}`
|
|
1282
|
-
)
|
|
1283
|
-
);
|
|
1284
|
-
console.log();
|
|
1285
|
-
console.log(
|
|
1286
|
-
footer(
|
|
1287
|
-
this.hotReload ? `${dim("watching")} ${value("ui/")} ${dim("\xB7")} ${value(`${hostDirLabel}/`)} ${dim(
|
|
1288
|
-
"\xB7 hot reload on save \xB7 ctrl-c to stop"
|
|
1289
|
-
)}` : `${dim("watching")} ${value("ui/")} ${dim(
|
|
1290
|
-
"\xB7 hot reload on save \xB7 ctrl-c to stop"
|
|
1291
|
-
)}`
|
|
1292
|
-
)
|
|
1293
|
-
);
|
|
1294
|
-
} else {
|
|
1295
|
-
console.log();
|
|
1296
|
-
console.log(
|
|
1297
|
-
footer(dim("host only \xB7 serve the UI separately \xB7 ctrl-c to stop"))
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
1300
|
-
console.log();
|
|
1301
|
-
await this.sessionDone;
|
|
1302
|
-
}
|
|
1303
|
-
installSignalHandlers() {
|
|
1304
|
-
process.on("SIGINT", () => {
|
|
1305
|
-
console.log("\n" + footer(dim("shutting down\u2026")));
|
|
1306
|
-
this.shutdown(0);
|
|
1307
|
-
});
|
|
1308
|
-
process.on("SIGTERM", () => {
|
|
1309
|
-
this.shutdown(0);
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
startVite() {
|
|
1313
|
-
console.log(taggedRow("active", "ui", dim("starting dev server\u2026")));
|
|
1314
|
-
const vite = spawn2(NPM_COMMAND, buildViteArgs(this.viteUrl), {
|
|
1315
|
-
cwd: this.project.uiDir,
|
|
1316
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1317
|
-
shell: process.platform === "win32"
|
|
1318
|
-
});
|
|
1319
|
-
return this.registerChild(vite, "ui", "Vite dev server");
|
|
1320
|
-
}
|
|
1321
|
-
launchHost() {
|
|
1322
|
-
if (this.hotReload) {
|
|
1323
|
-
return this.launchHostWithWatch();
|
|
1324
|
-
}
|
|
1325
|
-
return this.target.name === "macos" ? this.launchMacosHost() : this.launchWindowsHost();
|
|
1326
|
-
}
|
|
1327
|
-
// --- dotnet watch launch (C# hot reload) -----------------------------------
|
|
1328
|
-
launchHostWithWatch() {
|
|
1329
|
-
console.log(
|
|
1330
|
-
taggedRow(
|
|
1331
|
-
"active",
|
|
1332
|
-
"host",
|
|
1333
|
-
`${dim("dotnet watch \u2014 building")} ${value(this.target.framework)} ${dim("\u2026")}`
|
|
1334
|
-
)
|
|
1335
|
-
);
|
|
1336
|
-
const watch = spawn2(
|
|
1337
|
-
DOTNET_COMMAND,
|
|
1338
|
-
buildDotnetWatchArgs({
|
|
1339
|
-
csprojPath: this.project.csprojPath,
|
|
1340
|
-
framework: this.target.framework,
|
|
1341
|
-
buildConfig: this.buildConfig,
|
|
1342
|
-
verbose: this.verbose,
|
|
1343
|
-
targetName: this.target.name
|
|
1344
|
-
}),
|
|
1345
|
-
{
|
|
1346
|
-
cwd: this.project.root,
|
|
1347
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1348
|
-
env: { ...process.env, ...dotnetWatchEnv(this.viteUrl) },
|
|
1349
|
-
// Unix: make dotnet watch its own process-group leader so shutdown can
|
|
1350
|
-
// SIGTERM the group — taking down both watch and the app it launched.
|
|
1351
|
-
// Windows cleanup goes through `taskkill /T` instead.
|
|
1352
|
-
detached: process.platform !== "win32"
|
|
1353
|
-
}
|
|
1354
|
-
);
|
|
1355
|
-
this.watchChild = watch;
|
|
1356
|
-
this.children.push(watch);
|
|
1357
|
-
const notSentinel = (line) => !line.includes(HOST_READY_SENTINEL);
|
|
1358
|
-
prefixStream(watch.stdout, "host", notSentinel);
|
|
1359
|
-
prefixStream(watch.stderr, "host", notSentinel);
|
|
1360
|
-
scanStream(watch.stdout, (line) => this.onWatchLine(line));
|
|
1361
|
-
scanStream(watch.stderr, (line) => this.onWatchLine(line));
|
|
1362
|
-
watch.on("exit", (code, signal) => {
|
|
1363
|
-
if (this.shuttingDown) return;
|
|
1364
|
-
if (this.watchReady) {
|
|
1365
|
-
const failed = code !== null && code !== 0 || signal !== null;
|
|
1366
|
-
if (failed) {
|
|
1367
|
-
console.error(
|
|
1368
|
-
"\n" + row({
|
|
1369
|
-
glyph: "error",
|
|
1370
|
-
detail: dim(
|
|
1371
|
-
`dotnet watch exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
1372
|
-
)
|
|
1373
|
-
})
|
|
1374
|
-
);
|
|
1375
|
-
}
|
|
1376
|
-
this.shutdown(code ?? (signal ? 1 : 0));
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1379
|
-
this.fallBackToClassicLaunch(code, signal);
|
|
1380
|
-
});
|
|
1381
|
-
watch.on("error", (error) => {
|
|
1382
|
-
if (this.shuttingDown) return;
|
|
1383
|
-
console.error(
|
|
1384
|
-
"\n" + row({
|
|
1385
|
-
glyph: "error",
|
|
1386
|
-
detail: dim(`failed to start dotnet watch: ${error.message}`)
|
|
1387
|
-
})
|
|
1388
|
-
);
|
|
1389
|
-
this.shutdown(1);
|
|
1390
|
-
});
|
|
1391
|
-
return watch;
|
|
1392
|
-
}
|
|
1393
|
-
onWatchLine(line) {
|
|
1394
|
-
this.watchOutputTail = (this.watchOutputTail + line + "\n").slice(
|
|
1395
|
-
-WATCH_OUTPUT_TAIL_CHARS
|
|
1396
|
-
);
|
|
1397
|
-
const event = classifyWatchLine(line);
|
|
1398
|
-
if (event === "appStarted" && !this.watchReady) {
|
|
1399
|
-
this.watchReady = true;
|
|
1400
|
-
console.log(
|
|
1401
|
-
taggedRow(
|
|
1402
|
-
"done",
|
|
1403
|
-
"host",
|
|
1404
|
-
`${dim("launched")} ${value(this.project.projectName)} ${dim("\u2014 C# hot reload active")}`
|
|
1405
|
-
)
|
|
1406
|
-
);
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
if (event !== "appWaiting" && event !== "buildBlocked" || this.shuttingDown) {
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
if (this.watchReady) {
|
|
1413
|
-
console.log(
|
|
1414
|
-
taggedRow(
|
|
1415
|
-
"manual",
|
|
1416
|
-
"host",
|
|
1417
|
-
dim("app not running \u2014 save a C# file to relaunch, or ctrl-c to stop")
|
|
1418
|
-
)
|
|
1419
|
-
);
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1422
|
-
if (!/Build FAILED/i.test(this.watchOutputTail)) {
|
|
1423
|
-
console.log(
|
|
1424
|
-
taggedRow(
|
|
1425
|
-
"manual",
|
|
1426
|
-
"host",
|
|
1427
|
-
dim(
|
|
1428
|
-
"app exited before it was ready \u2014 save a C# file to relaunch, or ctrl-c to stop"
|
|
1429
|
-
)
|
|
1430
|
-
)
|
|
1431
|
-
);
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
if (looksLikeMissingWorkload(this.watchOutputTail)) printWorkloadHint();
|
|
1435
|
-
else if (looksLikeXcodeTooOld(this.watchOutputTail)) printXcodeTooOldHint();
|
|
1436
|
-
else if (looksLikeMissingXcode(this.watchOutputTail)) printXcodeHint();
|
|
1437
|
-
console.log(
|
|
1438
|
-
taggedRow(
|
|
1439
|
-
"manual",
|
|
1440
|
-
"host",
|
|
1441
|
-
dim("build failed \u2014 fix the error and save to retry, or ctrl-c to stop")
|
|
1442
|
-
)
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* `dotnet watch` exited before the app ever started — typically an SDK or
|
|
1447
|
-
* MAUI workload set that predates `dotnet run`/`dotnet watch` support for
|
|
1448
|
-
* this target. Explain why, then launch the host the classic way (one
|
|
1449
|
-
* build + direct spawn) so the dev session still works, minus C# hot reload.
|
|
1450
|
-
*/
|
|
1451
|
-
fallBackToClassicLaunch(code, signal) {
|
|
1452
|
-
console.log();
|
|
1453
|
-
console.log(
|
|
1454
|
-
taggedRow(
|
|
1455
|
-
"manual",
|
|
1456
|
-
"host",
|
|
1457
|
-
`${dim("C# hot reload unavailable \u2014 dotnet watch exited with")} ${value(
|
|
1458
|
-
signal ? `signal ${signal}` : `code ${code ?? "unknown"}`
|
|
1459
|
-
)}`
|
|
1460
|
-
)
|
|
1461
|
-
);
|
|
1462
|
-
if (looksLikeMissingWorkload(this.watchOutputTail)) {
|
|
1463
|
-
printWorkloadHint();
|
|
1464
|
-
} else if (looksLikeXcodeTooOld(this.watchOutputTail)) {
|
|
1465
|
-
printXcodeTooOldHint();
|
|
1466
|
-
} else if (looksLikeMissingXcode(this.watchOutputTail)) {
|
|
1467
|
-
printXcodeHint();
|
|
1468
|
-
} else {
|
|
1469
|
-
console.log(
|
|
1470
|
-
footer(
|
|
1471
|
-
dim(
|
|
1472
|
-
"C# hot reload needs the .NET 10.0.203+ workload set (Mac Catalyst dotnet-watch support) \u2014 try:"
|
|
1473
|
-
)
|
|
1474
|
-
)
|
|
1475
|
-
);
|
|
1476
|
-
console.log(fixLine("dotnet workload update"));
|
|
1477
|
-
if (!this.verbose) {
|
|
1478
|
-
console.log(footer(dim("re-run with --verbose for the full watch log.")));
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
console.log(
|
|
1482
|
-
taggedRow(
|
|
1483
|
-
"active",
|
|
1484
|
-
"host",
|
|
1485
|
-
dim("falling back to a classic launch (frontend hot reload still active)\u2026")
|
|
1486
|
-
)
|
|
1487
|
-
);
|
|
1488
|
-
console.log();
|
|
1489
|
-
if (this.target.name === "macos") {
|
|
1490
|
-
this.launchMacosHost();
|
|
1491
|
-
} else {
|
|
1492
|
-
this.launchWindowsHost();
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
// --- classic launch (one build + direct spawn) ------------------------------
|
|
1496
|
-
// Builds the MAUI host as a discrete step (a plain `dotnet build`, never
|
|
1497
|
-
// MSBuild's `-t:Run`) so the per-OS launch paths can spawn the produced
|
|
1498
|
-
// binary directly. `-t:Run` shells out in ways that break for both locally
|
|
1499
|
-
// signed mac apps and unpackaged Windows apps (see the call sites).
|
|
1500
|
-
buildHostSync() {
|
|
1501
|
-
console.log(
|
|
1502
|
-
taggedRow(
|
|
1503
|
-
"active",
|
|
1504
|
-
"host",
|
|
1505
|
-
`${dim("building")} ${value(this.target.framework)} ${dim("\u2026")}`
|
|
1506
|
-
)
|
|
1507
|
-
);
|
|
1508
|
-
try {
|
|
1509
|
-
execFileSync4(
|
|
1510
|
-
DOTNET_COMMAND,
|
|
1511
|
-
[
|
|
1512
|
-
"build",
|
|
1513
|
-
"-c",
|
|
1514
|
-
this.buildConfig,
|
|
1515
|
-
"-f",
|
|
1516
|
-
this.target.framework,
|
|
1517
|
-
this.project.csprojPath
|
|
1518
|
-
],
|
|
1519
|
-
{
|
|
1520
|
-
cwd: this.project.root,
|
|
1521
|
-
stdio: this.verbose ? "inherit" : "pipe"
|
|
1522
|
-
}
|
|
1523
|
-
);
|
|
1524
|
-
} catch (error) {
|
|
1525
|
-
const output = formatBuildError(error);
|
|
1526
|
-
console.error(taggedRow("error", "host", dim("MAUI build failed")));
|
|
1527
|
-
console.error(dim(output));
|
|
1528
|
-
if (looksLikeMissingWorkload(output)) printWorkloadHint();
|
|
1529
|
-
else if (looksLikeMissingXcode(output)) printXcodeHint();
|
|
1530
|
-
if (!this.verbose) {
|
|
1531
|
-
console.error(footer(dim("re-run with --verbose for the full build log.")));
|
|
1532
|
-
}
|
|
1533
|
-
process.exit(1);
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
launchMacosHost() {
|
|
1537
|
-
this.buildHostSync();
|
|
1538
|
-
const appBundle = findMacAppBundle(
|
|
1539
|
-
this.project.hostDir,
|
|
1540
|
-
this.target.framework,
|
|
1541
|
-
this.buildConfig
|
|
1542
|
-
);
|
|
1543
|
-
if (!appBundle) {
|
|
1544
|
-
console.error(
|
|
1545
|
-
row({
|
|
1546
|
-
glyph: "error",
|
|
1547
|
-
detail: dim(
|
|
1548
|
-
`could not find .app bundle in ${path6.join(this.project.hostDir, "bin", this.buildConfig, this.target.framework)}`
|
|
1549
|
-
)
|
|
1550
|
-
})
|
|
1551
|
-
);
|
|
1552
|
-
process.exit(1);
|
|
1553
|
-
}
|
|
1554
|
-
signMacAppBundleIfPossible(appBundle, {
|
|
1555
|
-
verbose: this.verbose,
|
|
1556
|
-
log: console.log,
|
|
1557
|
-
warn: console.warn
|
|
1558
|
-
});
|
|
1559
|
-
const binary = findMacExecutable(appBundle);
|
|
1560
|
-
if (!binary) {
|
|
1561
|
-
console.error(
|
|
1562
|
-
row({
|
|
1563
|
-
glyph: "error",
|
|
1564
|
-
detail: dim(`could not find the app executable in ${appBundle}`)
|
|
1565
|
-
})
|
|
1566
|
-
);
|
|
1567
|
-
process.exit(1);
|
|
1568
|
-
}
|
|
1569
|
-
console.log(
|
|
1570
|
-
taggedRow("done", "host", `${dim("launched")} ${value(path6.basename(appBundle))}`)
|
|
1571
|
-
);
|
|
1572
|
-
const host = spawn2(binary, [], {
|
|
1573
|
-
cwd: this.project.root,
|
|
1574
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1575
|
-
env: { ...process.env, VIDRA_DEV_URL: this.viteUrl }
|
|
1576
|
-
});
|
|
1577
|
-
return this.registerChild(host, "host", path6.basename(binary));
|
|
1578
|
-
}
|
|
1579
|
-
// Build first, then spawn the produced .exe directly. A single
|
|
1580
|
-
// `dotnet build -t:Run` on an unpackaged MAUI Windows app
|
|
1581
|
-
// (`WindowsPackageType=None`) execs the binary before the WindowsAppSDK
|
|
1582
|
-
// native assets are laid out beside it, so the app can't resolve its deps and
|
|
1583
|
-
// dies with a bare `MSB3073 ... exited with code 3` (ERROR_PATH_NOT_FOUND —
|
|
1584
|
-
// "The system cannot find the path specified"). Building as a discrete step
|
|
1585
|
-
// and then launching the binary is the documented workaround.
|
|
1586
|
-
// See dotnet/maui#13942 and dotnet/maui#5975.
|
|
1587
|
-
launchWindowsHost() {
|
|
1588
|
-
this.buildHostSync();
|
|
1589
|
-
const exe = findWindowsExecutable(
|
|
1590
|
-
this.project.hostDir,
|
|
1591
|
-
this.project.csprojPath,
|
|
1592
|
-
this.target.framework,
|
|
1593
|
-
this.buildConfig
|
|
1594
|
-
);
|
|
1595
|
-
if (!exe) {
|
|
1596
|
-
console.error(
|
|
1597
|
-
row({
|
|
1598
|
-
glyph: "error",
|
|
1599
|
-
detail: dim(
|
|
1600
|
-
`could not find the host .exe under ${path6.join(this.project.hostDir, "bin", this.buildConfig, this.target.framework)}`
|
|
1601
|
-
)
|
|
1602
|
-
})
|
|
1603
|
-
);
|
|
1604
|
-
process.exit(1);
|
|
1605
|
-
}
|
|
1606
|
-
console.log(
|
|
1607
|
-
taggedRow("done", "host", `${dim("launched")} ${value(path6.basename(exe))}`)
|
|
1608
|
-
);
|
|
1609
|
-
const host = spawn2(exe, [], {
|
|
1610
|
-
cwd: path6.dirname(exe),
|
|
1611
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1612
|
-
env: { ...process.env, VIDRA_DEV_URL: this.viteUrl }
|
|
1613
|
-
});
|
|
1614
|
-
return this.registerChild(host, "host", path6.basename(exe));
|
|
1615
|
-
}
|
|
1616
|
-
registerChild(child, tag2, label) {
|
|
1617
|
-
this.children.push(child);
|
|
1618
|
-
const include = tag2 === "host" ? (line) => !line.includes(HOST_READY_SENTINEL) : void 0;
|
|
1619
|
-
prefixStream(child.stdout, tag2, include);
|
|
1620
|
-
prefixStream(child.stderr, tag2, include);
|
|
1621
|
-
child.on("exit", (code, signal) => {
|
|
1622
|
-
if (this.shuttingDown) return;
|
|
1623
|
-
if (tag2 === "ui") {
|
|
1624
|
-
const exitCode = code ?? 1;
|
|
1625
|
-
console.error(
|
|
1626
|
-
"\n" + row({ glyph: "error", detail: dim(`${label} exited with code ${exitCode}`) })
|
|
1627
|
-
);
|
|
1628
|
-
this.shutdown(exitCode);
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1631
|
-
const failed = code !== null && code !== 0 || signal !== null;
|
|
1632
|
-
if (failed) {
|
|
1633
|
-
console.error(
|
|
1634
|
-
"\n" + row({
|
|
1635
|
-
glyph: "error",
|
|
1636
|
-
detail: dim(
|
|
1637
|
-
`${label} exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
1638
|
-
)
|
|
1639
|
-
})
|
|
1640
|
-
);
|
|
1641
|
-
if (tag2 === "host" && this.target.name === "macos") {
|
|
1642
|
-
printMacLaunchHint();
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
this.shutdown(code ?? (signal ? 1 : 0));
|
|
1646
|
-
});
|
|
1647
|
-
child.on("error", (error) => {
|
|
1648
|
-
if (this.shuttingDown) return;
|
|
1649
|
-
console.error(
|
|
1650
|
-
"\n" + row({ glyph: "error", detail: dim(`failed to start ${label}: ${error.message}`) })
|
|
1651
|
-
);
|
|
1652
|
-
if (tag2 === "host" && this.target.name === "macos") {
|
|
1653
|
-
printMacLaunchHint();
|
|
1654
|
-
}
|
|
1655
|
-
this.shutdown(1);
|
|
1656
|
-
});
|
|
1657
|
-
return child;
|
|
1658
|
-
}
|
|
1659
|
-
shutdown(exitCode) {
|
|
1660
|
-
if (this.shuttingDown) return;
|
|
1661
|
-
this.shuttingDown = true;
|
|
1662
|
-
for (const child of this.children) {
|
|
1663
|
-
killChild(child, { processGroup: child === this.watchChild });
|
|
1664
|
-
}
|
|
1665
|
-
this.endSession();
|
|
1666
|
-
process.exit(exitCode);
|
|
1667
|
-
}
|
|
1668
|
-
};
|
|
1669
|
-
var ensureTargetMatchesHostOs = (targetName) => {
|
|
1670
|
-
if (targetName === "macos" && process.platform !== "darwin") {
|
|
1671
|
-
console.error(
|
|
1672
|
-
row({ glyph: "error", detail: dim("the macOS target can only run on macOS") })
|
|
1673
|
-
);
|
|
1674
|
-
process.exit(1);
|
|
1675
|
-
}
|
|
1676
|
-
if (targetName === "windows" && process.platform !== "win32") {
|
|
1677
|
-
console.error(
|
|
1678
|
-
row({ glyph: "error", detail: dim("the Windows target can only run on Windows") })
|
|
1679
|
-
);
|
|
1680
|
-
process.exit(1);
|
|
1681
|
-
}
|
|
1682
|
-
};
|
|
1683
|
-
var prefixStream = (stream, tag2, include = () => true) => {
|
|
1684
|
-
if (!stream) return;
|
|
1685
|
-
const prefix = streamPrefix(tag2);
|
|
1686
|
-
stream.on("data", (chunk) => {
|
|
1687
|
-
const lines = chunk.toString().split("\n");
|
|
1688
|
-
for (const line of lines) {
|
|
1689
|
-
if (line.length > 0 && include(line)) {
|
|
1690
|
-
process.stdout.write(`${prefix} ${line}
|
|
1691
|
-
`);
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
});
|
|
1695
|
-
};
|
|
1696
|
-
var scanStream = (stream, onLine) => {
|
|
1697
|
-
if (!stream) return;
|
|
1698
|
-
let pending = "";
|
|
1699
|
-
stream.on("data", (chunk) => {
|
|
1700
|
-
pending += chunk.toString();
|
|
1701
|
-
const lines = pending.split("\n");
|
|
1702
|
-
pending = lines.pop() ?? "";
|
|
1703
|
-
for (const line of lines) onLine(line);
|
|
1704
|
-
});
|
|
1705
|
-
stream.on("end", () => {
|
|
1706
|
-
if (pending) onLine(pending);
|
|
1707
|
-
});
|
|
1708
|
-
};
|
|
1709
|
-
var waitForServer = (url, timeoutMs) => {
|
|
1710
|
-
const { hostname, port, pathname } = new URL(url);
|
|
1711
|
-
const start = Date.now();
|
|
1712
|
-
return new Promise((resolve, reject) => {
|
|
1713
|
-
const poll = () => {
|
|
1714
|
-
if (Date.now() - start > timeoutMs) {
|
|
1715
|
-
reject(
|
|
1716
|
-
new Error(`Timed out waiting for ${url} after ${timeoutMs}ms`)
|
|
1717
|
-
);
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
|
-
const req = request(
|
|
1721
|
-
{
|
|
1722
|
-
hostname,
|
|
1723
|
-
port,
|
|
1724
|
-
path: pathname,
|
|
1725
|
-
method: "HEAD",
|
|
1726
|
-
timeout: 1e3
|
|
1727
|
-
},
|
|
1728
|
-
() => resolve()
|
|
1729
|
-
);
|
|
1730
|
-
req.on("error", () => setTimeout(poll, POLL_INTERVAL_MS));
|
|
1731
|
-
req.on("timeout", () => {
|
|
1732
|
-
req.destroy();
|
|
1733
|
-
setTimeout(poll, POLL_INTERVAL_MS);
|
|
1734
|
-
});
|
|
1735
|
-
req.end();
|
|
1736
|
-
};
|
|
1737
|
-
poll();
|
|
1738
|
-
});
|
|
1739
|
-
};
|
|
1740
|
-
var findMacAppBundle = (hostDir, framework, buildConfig) => {
|
|
1741
|
-
const outputDir = path6.join(hostDir, "bin", buildConfig, framework);
|
|
1742
|
-
return findAppBundleRecursive(outputDir);
|
|
1743
|
-
};
|
|
1744
|
-
var findAppBundleRecursive = (dir) => {
|
|
1745
|
-
if (!fs5.existsSync(dir)) return null;
|
|
1746
|
-
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
1747
|
-
const fullPath = path6.join(dir, entry.name);
|
|
1748
|
-
if (entry.isDirectory() && entry.name.endsWith(".app")) {
|
|
1749
|
-
return fullPath;
|
|
1750
|
-
}
|
|
1751
|
-
if (entry.isDirectory()) {
|
|
1752
|
-
const nested = findAppBundleRecursive(fullPath);
|
|
1753
|
-
if (nested) return nested;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
return null;
|
|
1757
|
-
};
|
|
1758
|
-
var findMacExecutable = (appBundle) => {
|
|
1759
|
-
const macOsDir = path6.join(appBundle, "Contents", "MacOS");
|
|
1760
|
-
if (!fs5.existsSync(macOsDir)) return null;
|
|
1761
|
-
for (const entry of fs5.readdirSync(macOsDir, { withFileTypes: true })) {
|
|
1762
|
-
if (entry.isFile()) {
|
|
1763
|
-
return path6.join(macOsDir, entry.name);
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
return null;
|
|
1767
|
-
};
|
|
1768
|
-
var findWindowsExecutable = (hostDir, csprojPath, framework, buildConfig) => {
|
|
1769
|
-
const outputDir = path6.join(hostDir, "bin", buildConfig, framework);
|
|
1770
|
-
if (!fs5.existsSync(outputDir)) return null;
|
|
1771
|
-
const exeName = `${path6.basename(csprojPath, ".csproj")}.exe`.toLowerCase();
|
|
1772
|
-
return findFileRecursive(outputDir, (name) => name.toLowerCase() === exeName) ?? findFileRecursive(
|
|
1773
|
-
outputDir,
|
|
1774
|
-
(name) => name.toLowerCase().endsWith(".host.exe")
|
|
1775
|
-
) ?? findFileRecursive(outputDir, (name) => name.toLowerCase().endsWith(".exe"));
|
|
1776
|
-
};
|
|
1777
|
-
var findFileRecursive = (dir, match) => {
|
|
1778
|
-
if (!fs5.existsSync(dir)) return null;
|
|
1779
|
-
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1780
|
-
for (const entry of entries) {
|
|
1781
|
-
if (entry.isFile() && match(entry.name)) {
|
|
1782
|
-
return path6.join(dir, entry.name);
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
for (const entry of entries) {
|
|
1786
|
-
if (entry.isDirectory()) {
|
|
1787
|
-
const found = findFileRecursive(path6.join(dir, entry.name), match);
|
|
1788
|
-
if (found) return found;
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
return null;
|
|
1792
|
-
};
|
|
1793
|
-
var killChild = (child, opts = {}) => {
|
|
1794
|
-
if (!child.pid || child.exitCode !== null) return;
|
|
1795
|
-
if (process.platform === "win32") {
|
|
1796
|
-
try {
|
|
1797
|
-
execFileSync4("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
|
|
1798
|
-
stdio: "ignore"
|
|
1799
|
-
});
|
|
1800
|
-
} catch {
|
|
1801
|
-
child.kill();
|
|
1802
|
-
}
|
|
1803
|
-
return;
|
|
1804
|
-
}
|
|
1805
|
-
if (opts.processGroup) {
|
|
1806
|
-
try {
|
|
1807
|
-
process.kill(-child.pid, "SIGTERM");
|
|
1808
|
-
return;
|
|
1809
|
-
} catch {
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
child.kill("SIGTERM");
|
|
1813
|
-
};
|
|
1814
|
-
var printMacLaunchHint = () => {
|
|
1815
|
-
console.error();
|
|
1816
|
-
console.error(
|
|
1817
|
-
row({ glyph: "manual", label: "the host built but the app couldn't launch." })
|
|
1818
|
-
);
|
|
1819
|
-
console.error(
|
|
1820
|
-
footer(
|
|
1821
|
-
dim(
|
|
1822
|
-
"on macOS this is usually code signing / Gatekeeper for a locally built app:"
|
|
1823
|
-
)
|
|
1824
|
-
)
|
|
1825
|
-
);
|
|
1826
|
-
console.error(
|
|
1827
|
-
` ${dim("\u2022")} ${dim("install full Xcode, then run")} ${lime("vidra doctor")} ${dim("to verify")}`
|
|
1828
|
-
);
|
|
1829
|
-
console.error(
|
|
1830
|
-
` ${dim("\u2022")} ${dim("approve it once in Finder: right-click the")} ${value(".app")} ${dim("and choose")} ${value("Open")}`
|
|
1831
|
-
);
|
|
1832
|
-
console.error(
|
|
1833
|
-
` ${dim("\u2022")} ${dim("or provide a signing identity via")} ${value("VIDRA_MACOS_CODESIGN_KEY")}`
|
|
1834
|
-
);
|
|
1835
|
-
console.error();
|
|
1836
|
-
};
|
|
1837
|
-
|
|
1838
|
-
// src/cli.ts
|
|
1839
|
-
var printHelp = () => {
|
|
1840
|
-
const cmd = (name, desc) => ` ${value(name.padEnd(10))} ${dim(desc)}`;
|
|
1841
|
-
const ex = (args, comment) => ` ${lime("vidra")} ${value(args.padEnd(22))} ${dim(`# ${comment}`)}`;
|
|
1842
|
-
console.log(`
|
|
1843
|
-
${wordmark()} ${dim(`v${CLI_VERSION}`)}
|
|
1844
|
-
|
|
1845
|
-
${dim("usage")}
|
|
1846
|
-
${lime("vidra")} ${dim("<command> [options]")}
|
|
1847
|
-
|
|
1848
|
-
${dim("commands")}
|
|
1849
|
-
${cmd("dev", "start vite + the native host (UI + C# hot reload)")}
|
|
1850
|
-
${cmd("run", "launch the native host only")}
|
|
1851
|
-
${cmd("build", "build & package for distribution")}
|
|
1852
|
-
${cmd("doctor", "check your environment")}
|
|
1853
|
-
${cmd("help", "show this message")}
|
|
1854
|
-
|
|
1855
|
-
${dim("examples")}
|
|
1856
|
-
${ex("dev --target windows", "run the windows host")}
|
|
1857
|
-
${ex("dev --no-hot-reload", "skip dotnet watch, classic launch")}
|
|
1858
|
-
${ex("build --plan", "preview the build, run nothing")}
|
|
1859
|
-
${ex("build --target macos", "build & package a macOS DMG")}
|
|
1860
|
-
${ex("doctor", "verify .NET SDK + MAUI workload")}
|
|
1861
|
-
`);
|
|
1862
|
-
};
|
|
1863
|
-
var main = async () => {
|
|
1864
|
-
const args = process.argv.slice(2);
|
|
1865
|
-
const command = args[0];
|
|
1866
|
-
switch (command) {
|
|
1867
|
-
case "dev":
|
|
1868
|
-
await devCommand(args.slice(1));
|
|
1869
|
-
break;
|
|
1870
|
-
case "run":
|
|
1871
|
-
await runCommand(args.slice(1));
|
|
1872
|
-
break;
|
|
1873
|
-
case "build":
|
|
1874
|
-
await buildCommand(args.slice(1));
|
|
1875
|
-
break;
|
|
1876
|
-
case "doctor":
|
|
1877
|
-
process.exit(await runDoctor());
|
|
1878
|
-
break;
|
|
1879
|
-
case "help":
|
|
1880
|
-
case "--help":
|
|
1881
|
-
case "-h":
|
|
1882
|
-
case void 0:
|
|
1883
|
-
printHelp();
|
|
1884
|
-
break;
|
|
1885
|
-
case "--version":
|
|
1886
|
-
case "-v":
|
|
1887
|
-
console.log(CLI_VERSION);
|
|
1888
|
-
break;
|
|
1889
|
-
default:
|
|
1890
|
-
console.error(row({ glyph: "error", detail: dim(`unknown command: ${command}`) }));
|
|
1891
|
-
printHelp();
|
|
1892
|
-
process.exit(1);
|
|
1893
|
-
}
|
|
1894
|
-
};
|
|
1895
|
-
main().catch((e) => {
|
|
1896
|
-
console.error(row({ glyph: "error", detail: dim(e.message) }));
|
|
1897
|
-
process.exit(1);
|
|
1898
|
-
});
|