create-vidra-app 0.2.1 → 0.3.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/dist/cli.js +141 -15
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/templates/react-vite/src/{{projectName}}.Host/CounterJsContract.cs +10 -0
- package/templates/react-vite/src/{{projectName}}.Host/MainPage.cs +1 -1
- package/templates/react-vite/src/{{projectName}}.Host/{{projectName}}.Host.csproj +1 -0
- package/templates/react-vite/ui/src/App.tsx +14 -6
- package/templates/react-vite/ui/src/generated/counter.ts +10 -0
- package/templates/react-vite/ui/src/generated/index.ts +9 -0
- package/templates/react-vite/ui/src/generated/manifest.json +17 -0
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ import { execSync, spawn } from "child_process";
|
|
|
33
33
|
|
|
34
34
|
// src/theme.ts
|
|
35
35
|
import chalk from "chalk";
|
|
36
|
-
var CLI_VERSION = "0.
|
|
36
|
+
var CLI_VERSION = "0.3.0";
|
|
37
37
|
var STEP_LABEL_WIDTH = 16;
|
|
38
38
|
var lime = chalk.hex("#c8f751");
|
|
39
39
|
var value = chalk.hex("#e8e8ec");
|
|
@@ -400,6 +400,7 @@ var newestNet10Sdk = (listSdksOutput) => splitLines(listSdksOutput).map((line) =
|
|
|
400
400
|
var outputMentionsMaui = (workloadListOutput) => MAUI_WORKLOAD.test(workloadListOutput);
|
|
401
401
|
var workloadSetVersion = (workloadListOutput) => workloadListOutput.match(WORKLOAD_SET_VERSION_LINE)?.[1];
|
|
402
402
|
var MIN_MACCATALYST_WATCH_WORKLOAD_SET = [10, 0, 203];
|
|
403
|
+
var MIN_MACCATALYST_WATCH_SDK = [10, 0, 300];
|
|
403
404
|
var meetsMinimumVersion = (version, minimum) => {
|
|
404
405
|
const segments = version.split(".").map((s) => Number.parseInt(s, 10));
|
|
405
406
|
if (segments.slice(0, minimum.length).some(Number.isNaN)) return false;
|
|
@@ -410,12 +411,25 @@ var meetsMinimumVersion = (version, minimum) => {
|
|
|
410
411
|
return true;
|
|
411
412
|
};
|
|
412
413
|
var workloadSetSupportsCSharpHotReload = (version) => meetsMinimumVersion(version, MIN_MACCATALYST_WATCH_WORKLOAD_SET);
|
|
414
|
+
var sdkSupportsCSharpHotReload = (version) => meetsMinimumVersion(version, MIN_MACCATALYST_WATCH_SDK);
|
|
413
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
|
+
}
|
|
414
425
|
const res = run(DOTNET, ["workload", "list"]);
|
|
415
426
|
if (!res.found) return null;
|
|
416
427
|
const version = workloadSetVersion(res.stdout);
|
|
417
|
-
if (!version) return null;
|
|
418
|
-
return
|
|
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
|
+
};
|
|
419
433
|
};
|
|
420
434
|
var MISSING_WORKLOAD_SIGNATURES = [
|
|
421
435
|
/NETSDK1147/i,
|
|
@@ -487,6 +501,15 @@ var checkCSharpHotReload = (workloadList) => {
|
|
|
487
501
|
if (process.platform !== "darwin") {
|
|
488
502
|
return { name, status: "ok", detail: "dotnet watch supported" };
|
|
489
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
|
+
}
|
|
490
513
|
const version = workloadSetVersion(workloadList.stdout);
|
|
491
514
|
if (!version) {
|
|
492
515
|
return {
|
|
@@ -1011,6 +1034,83 @@ import path6 from "path";
|
|
|
1011
1034
|
import fs5 from "fs-extra";
|
|
1012
1035
|
import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
|
|
1013
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
|
|
1014
1114
|
var POLL_INTERVAL_MS = 500;
|
|
1015
1115
|
var POLL_TIMEOUT_MS = 3e4;
|
|
1016
1116
|
var NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
@@ -1033,7 +1133,7 @@ var startSession = async (argv, opts) => {
|
|
|
1033
1133
|
const targetName = args["target"] || detectPlatform();
|
|
1034
1134
|
const verbose = !!args["verbose"];
|
|
1035
1135
|
let hotReload = opts.hotReloadDefault && !args["no-hot-reload"];
|
|
1036
|
-
|
|
1136
|
+
let viteUrl = process.env.VIDRA_DEV_URL || "http://localhost:5173";
|
|
1037
1137
|
const target = TARGETS2[targetName];
|
|
1038
1138
|
if (!target) {
|
|
1039
1139
|
const supported = Object.keys(TARGETS2).join(", ");
|
|
@@ -1058,12 +1158,15 @@ var startSession = async (argv, opts) => {
|
|
|
1058
1158
|
console.log(
|
|
1059
1159
|
row({
|
|
1060
1160
|
glyph: "manual",
|
|
1061
|
-
detail: `${dim(
|
|
1161
|
+
detail: `${dim(blocker.reason + "; using a classic launch")}`
|
|
1062
1162
|
})
|
|
1063
1163
|
);
|
|
1064
|
-
console.log(fixLine(
|
|
1164
|
+
console.log(fixLine(blocker.fix));
|
|
1065
1165
|
}
|
|
1066
1166
|
}
|
|
1167
|
+
if (opts.vite) {
|
|
1168
|
+
viteUrl = await selectDevServerUrl(viteUrl);
|
|
1169
|
+
}
|
|
1067
1170
|
const session = new DevSession(project, target, viteUrl, verbose, {
|
|
1068
1171
|
vite: opts.vite,
|
|
1069
1172
|
hotReload
|
|
@@ -1094,6 +1197,14 @@ var dotnetWatchEnv = (devUrl) => ({
|
|
|
1094
1197
|
DOTNET_WATCH_SUPPRESS_EMOJIS: "1",
|
|
1095
1198
|
DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER: "1"
|
|
1096
1199
|
});
|
|
1200
|
+
var buildViteArgs = (devUrl) => [
|
|
1201
|
+
"run",
|
|
1202
|
+
"dev",
|
|
1203
|
+
"--",
|
|
1204
|
+
"--port",
|
|
1205
|
+
new URL(devUrl).port,
|
|
1206
|
+
"--strictPort"
|
|
1207
|
+
];
|
|
1097
1208
|
var HOST_READY_SENTINEL = "[vidra] host ready";
|
|
1098
1209
|
var classifyWatchLine = (line) => {
|
|
1099
1210
|
if (/waiting for a file to change/i.test(line)) return "appWaiting";
|
|
@@ -1200,7 +1311,7 @@ var DevSession = class {
|
|
|
1200
1311
|
}
|
|
1201
1312
|
startVite() {
|
|
1202
1313
|
console.log(taggedRow("active", "ui", dim("starting dev server\u2026")));
|
|
1203
|
-
const vite = spawn2(NPM_COMMAND,
|
|
1314
|
+
const vite = spawn2(NPM_COMMAND, buildViteArgs(this.viteUrl), {
|
|
1204
1315
|
cwd: this.project.uiDir,
|
|
1205
1316
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1206
1317
|
shell: process.platform === "win32"
|
|
@@ -1293,7 +1404,12 @@ var DevSession = class {
|
|
|
1293
1404
|
`${dim("launched")} ${value(this.project.projectName)} ${dim("\u2014 C# hot reload active")}`
|
|
1294
1405
|
)
|
|
1295
1406
|
);
|
|
1296
|
-
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (event !== "appWaiting" && event !== "buildBlocked" || this.shuttingDown) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (this.watchReady) {
|
|
1297
1413
|
console.log(
|
|
1298
1414
|
taggedRow(
|
|
1299
1415
|
"manual",
|
|
@@ -1301,20 +1417,30 @@ var DevSession = class {
|
|
|
1301
1417
|
dim("app not running \u2014 save a C# file to relaunch, or ctrl-c to stop")
|
|
1302
1418
|
)
|
|
1303
1419
|
);
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
else if (looksLikeXcodeTooOld(this.watchOutputTail)) printXcodeTooOldHint();
|
|
1308
|
-
else if (looksLikeMissingXcode(this.watchOutputTail)) printXcodeHint();
|
|
1309
|
-
}
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (!/Build FAILED/i.test(this.watchOutputTail)) {
|
|
1310
1423
|
console.log(
|
|
1311
1424
|
taggedRow(
|
|
1312
1425
|
"manual",
|
|
1313
1426
|
"host",
|
|
1314
|
-
dim(
|
|
1427
|
+
dim(
|
|
1428
|
+
"app exited before it was ready \u2014 save a C# file to relaunch, or ctrl-c to stop"
|
|
1429
|
+
)
|
|
1315
1430
|
)
|
|
1316
1431
|
);
|
|
1432
|
+
return;
|
|
1317
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
|
+
);
|
|
1318
1444
|
}
|
|
1319
1445
|
/**
|
|
1320
1446
|
* `dotnet watch` exited before the app ever started — typically an SDK or
|
package/dist/index.js
CHANGED
|
@@ -329,8 +329,8 @@ var VIDRA_REPO_ROOT = path2.resolve(CLI_ROOT, "..", "..", "..");
|
|
|
329
329
|
var LOCAL_FEED_DIR = path2.join(VIDRA_REPO_ROOT, "dist", "packages");
|
|
330
330
|
var LOCAL_CLI_DIR = CLI_ROOT;
|
|
331
331
|
var LOCAL_SDK_DIR = path2.join(VIDRA_REPO_ROOT, "src", "sdk", "vidra-js");
|
|
332
|
-
var VIDRA_VERSION = "0.
|
|
333
|
-
var SDK_VERSION = "0.
|
|
332
|
+
var VIDRA_VERSION = "0.3.0";
|
|
333
|
+
var SDK_VERSION = "0.2.0";
|
|
334
334
|
var main = async () => {
|
|
335
335
|
console.log();
|
|
336
336
|
console.log(` create-${lime("vidra")}-app`);
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ public class MainPage : VidraPage
|
|
|
22
22
|
{
|
|
23
23
|
try
|
|
24
24
|
{
|
|
25
|
-
var count = await Bridge.
|
|
25
|
+
var count = await Bridge.Js().Counter.IncrementAsync();
|
|
26
26
|
System.Diagnostics.Debug.WriteLine($"[MainPage] Counter is now {count}");
|
|
27
27
|
}
|
|
28
28
|
catch (Exception ex)
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
preserved.
|
|
43
43
|
-->
|
|
44
44
|
<MtouchLink Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst' And '$(Configuration)' == 'Debug'">None</MtouchLink>
|
|
45
|
+
<VidraTsOutputDir>$(MSBuildProjectDirectory)/../../ui/src/generated</VidraTsOutputDir>
|
|
45
46
|
</PropertyGroup>
|
|
46
47
|
|
|
47
48
|
<ItemGroup>
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { type MouseEvent, useEffect, useRef, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
app,
|
|
4
|
+
appWindow,
|
|
5
|
+
browser,
|
|
6
|
+
clipboard,
|
|
7
|
+
notifications,
|
|
8
|
+
runtime,
|
|
9
|
+
vidra,
|
|
10
|
+
} from "@vidra-dev/sdk";
|
|
11
|
+
import type { Capabilities, WindowInfo, WindowSupport } from "@vidra-dev/sdk";
|
|
12
|
+
import { counterHandlers } from "./generated/index.js";
|
|
4
13
|
|
|
5
14
|
const describeWindow = (windowInfo: WindowInfo): string => {
|
|
6
15
|
const title = windowInfo.title || "(untitled)";
|
|
@@ -17,22 +26,21 @@ const App = () => {
|
|
|
17
26
|
"Window controls are ready. Click a button to inspect or update the native window.",
|
|
18
27
|
);
|
|
19
28
|
const [windowSupport, setWindowSupport] = useState<WindowSupport | null>(null);
|
|
20
|
-
const [caps, setCaps] = useState<
|
|
29
|
+
const [caps, setCaps] = useState<Capabilities | null>(null);
|
|
21
30
|
const [count, setCount] = useState(0);
|
|
22
31
|
const [csReloaded, setCsReloaded] = useState<string | null>(null);
|
|
23
32
|
const countRef = useRef(0);
|
|
24
33
|
const reloadFlashTimer = useRef<number | undefined>(undefined);
|
|
25
34
|
|
|
26
35
|
useEffect(() => {
|
|
27
|
-
const unsubscribeCounter =
|
|
36
|
+
const unsubscribeCounter = counterHandlers.increment(() => {
|
|
28
37
|
countRef.current += 1;
|
|
29
38
|
setCount(countRef.current);
|
|
30
39
|
return countRef.current;
|
|
31
40
|
});
|
|
32
41
|
|
|
33
42
|
// Emitted by the native host when `vidra dev` hot reloads edited C#.
|
|
34
|
-
const unsubscribeHotReloaded =
|
|
35
|
-
"vidra.hotReloaded",
|
|
43
|
+
const unsubscribeHotReloaded = runtime.onHotReloaded(
|
|
36
44
|
(data) => {
|
|
37
45
|
const typeName = data?.updatedTypes?.[0]?.split(".").pop();
|
|
38
46
|
setCsReloaded(typeName ? `C# reloaded · ${typeName}` : "C# reloaded");
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Auto-generated by vidra-codegen. Do not edit.
|
|
2
|
+
import type { VidraClient } from "@vidra-dev/sdk";
|
|
3
|
+
|
|
4
|
+
export class CounterHandlers {
|
|
5
|
+
constructor(private client: VidraClient) {}
|
|
6
|
+
|
|
7
|
+
increment(handler: () => number | Promise<number>): () => void {
|
|
8
|
+
return this.client.unsafe.handle("counter", "increment", handler);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Auto-generated by vidra-codegen. Do not edit.
|
|
2
|
+
import { vidra } from "@vidra-dev/sdk";
|
|
3
|
+
import { CounterHandlers } from "./counter.js";
|
|
4
|
+
|
|
5
|
+
vidra.registerGeneratedManifest("app", "d3044812d17c42049962d730fced04a6ecd711e287cdd0da7b218b327ee6cd56");
|
|
6
|
+
|
|
7
|
+
export const counterHandlers = new CounterHandlers(vidra);
|
|
8
|
+
|
|
9
|
+
export * from "./counter.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contracts": {
|
|
3
|
+
"counter": {
|
|
4
|
+
"nativeMethods": {},
|
|
5
|
+
"events": {},
|
|
6
|
+
"jsMethods": {
|
|
7
|
+
"increment": {
|
|
8
|
+
"returns": {
|
|
9
|
+
"kind": "primitive",
|
|
10
|
+
"tsType": "number"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"fingerprint": "d3044812d17c42049962d730fced04a6ecd711e287cdd0da7b218b327ee6cd56"
|
|
17
|
+
}
|