create-vidra-app 0.4.0 → 0.5.1
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 +46 -85
- package/package.json +13 -10
- package/templates/react-vite/README.md +22 -74
- package/templates/react-vite/_gitignore +2 -0
- package/templates/react-vite/package.json +2 -1
- package/templates/react-vite/src/{{projectName}}.Host/MainPage.cs +13 -2
- package/templates/react-vite/src/{{projectName}}.Host/MauiProgram.cs +17 -13
- package/templates/react-vite/src/{{projectName}}.Host/Platforms/MacCatalyst/Program.cs +8 -11
- package/templates/react-vite/src/{{projectName}}.Host/Platforms/Windows/Program.cs +8 -12
- package/templates/react-vite/src/{{projectName}}.Host/{{projectName}}.Host.csproj +19 -4
- 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 +33 -0
- package/bin/vidra.mjs +0 -2
- package/dist/cli.js +0 -4320
package/README.md
CHANGED
|
@@ -1,53 +1,46 @@
|
|
|
1
1
|
# create-vidra-app
|
|
2
2
|
|
|
3
|
-
Scaffold a
|
|
4
|
-
|
|
3
|
+
Scaffold a [Vidra](https://vidra.build) desktop app with a web UI and a
|
|
4
|
+
C#/.NET native host.
|
|
5
5
|
|
|
6
|
-
> **Alpha
|
|
6
|
+
> **Alpha:** APIs and templates may change between 0.x releases.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Create an app
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
npm create vidra-app@latest
|
|
12
12
|
# or
|
|
13
13
|
npx create-vidra-app my-app
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
You'll be prompted for a project name and an app ID (reverse-domain). Then:
|
|
17
14
|
|
|
18
|
-
```bash
|
|
19
15
|
cd my-app
|
|
20
|
-
npm run dev
|
|
16
|
+
npm run dev
|
|
21
17
|
```
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
or
|
|
19
|
+
The generated project depends on [`vidra-cli`](https://www.npmjs.com/package/vidra-cli),
|
|
20
|
+
which provides the `vidra` command. This package is only needed to create the
|
|
21
|
+
app; nothing depends on it afterwards. Use the provided npm scripts or run
|
|
22
|
+
`vidra` through `npx`; no global install is required.
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
npm run dev # start Vite + native host
|
|
29
|
-
npm run build # build + package for distribution
|
|
30
|
-
npm run doctor # check your .NET / MAUI / Xcode setup
|
|
31
|
-
npx vidra dev --target windows # run a specific desktop target
|
|
32
|
-
npx vidra build --target macos # build + package a macOS .dmg
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Prerequisites
|
|
24
|
+
## Requirements
|
|
36
25
|
|
|
37
26
|
- .NET 10 SDK
|
|
38
|
-
-
|
|
39
|
-
- Node.js
|
|
40
|
-
-
|
|
27
|
+
- .NET MAUI workload: `dotnet workload install maui`
|
|
28
|
+
- Node.js 22 or newer
|
|
29
|
+
- Xcode for macOS builds
|
|
30
|
+
|
|
31
|
+
Windows targets must be built on Windows.
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
scaffolding and offer to install it for you. You can re-check at any time with
|
|
44
|
-
`npm run doctor`.
|
|
33
|
+
## Common commands
|
|
45
34
|
|
|
46
|
-
|
|
35
|
+
```bash
|
|
36
|
+
npm run dev
|
|
37
|
+
npm run build
|
|
38
|
+
npm run doctor
|
|
39
|
+
npx vidra --help
|
|
40
|
+
```
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- npm: [create-vidra-app](https://www.npmjs.com/package/create-vidra-app)
|
|
42
|
+
See the [getting started guide](https://vidra.build/docs/getting-started/) for
|
|
43
|
+
project structure, development, and build options.
|
|
51
44
|
|
|
52
45
|
## License
|
|
53
46
|
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import prompts2 from "prompts";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import fs2 from "fs-extra";
|
|
4
|
+
import path2 from "path";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
|
-
// src/utils.ts
|
|
8
|
+
// ../shared/src/utils.ts
|
|
9
9
|
var toPascalCase = (str) => {
|
|
10
10
|
return str.replace(
|
|
11
11
|
/[-_]+(.)?/g,
|
|
@@ -42,11 +42,13 @@ var parseArgs = (argv) => {
|
|
|
42
42
|
}
|
|
43
43
|
return args;
|
|
44
44
|
};
|
|
45
|
+
var splitLines = (text) => text.split(/\r?\n/);
|
|
46
|
+
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
45
47
|
|
|
46
|
-
// src/exec.ts
|
|
47
|
-
import { execSync, spawn } from "child_process";
|
|
48
|
+
// ../shared/src/exec.ts
|
|
49
|
+
import { execFileSync, execSync, spawn } from "child_process";
|
|
48
50
|
|
|
49
|
-
// src/theme.ts
|
|
51
|
+
// ../shared/src/theme.ts
|
|
50
52
|
import chalk from "chalk";
|
|
51
53
|
var lime = chalk.hex("#c8f751");
|
|
52
54
|
var value = chalk.hex("#e8e8ec");
|
|
@@ -88,11 +90,28 @@ var row = (opts) => {
|
|
|
88
90
|
var footer = (content) => ` ${content}`;
|
|
89
91
|
var fixLine = (cmd, label = "fix:") => ` ${amber(label)} ${lime(cmd)}`;
|
|
90
92
|
|
|
91
|
-
// src/exec.ts
|
|
93
|
+
// ../shared/src/exec.ts
|
|
92
94
|
var toText = (value2) => {
|
|
93
95
|
if (value2 == null) return "";
|
|
94
96
|
return Buffer.isBuffer(value2) ? value2.toString() : value2;
|
|
95
97
|
};
|
|
98
|
+
var run = (cmd, args) => {
|
|
99
|
+
try {
|
|
100
|
+
const stdout = execFileSync(cmd, args, {
|
|
101
|
+
encoding: "utf-8",
|
|
102
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
103
|
+
});
|
|
104
|
+
return { found: true, ok: true, stdout: stdout ?? "", stderr: "" };
|
|
105
|
+
} catch (e) {
|
|
106
|
+
const err = e;
|
|
107
|
+
return {
|
|
108
|
+
found: err.code !== "ENOENT",
|
|
109
|
+
ok: false,
|
|
110
|
+
stdout: toText(err.stdout),
|
|
111
|
+
stderr: toText(err.stderr)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
96
115
|
var formatProcessError = (error) => {
|
|
97
116
|
const err = error;
|
|
98
117
|
const combined = [toText(err.stderr), toText(err.stdout)].map((s) => s.trim()).filter((s) => s.length > 0).join("\n").trim();
|
|
@@ -169,69 +188,12 @@ var applyReplacements = (str, replacements) => {
|
|
|
169
188
|
return str;
|
|
170
189
|
};
|
|
171
190
|
|
|
172
|
-
// src/
|
|
173
|
-
import { execFileSync as execFileSync5 } from "child_process";
|
|
174
|
-
import fs8 from "fs";
|
|
175
|
-
import path9 from "path";
|
|
176
|
-
import prompts from "prompts";
|
|
177
|
-
|
|
178
|
-
// src/signing.ts
|
|
179
|
-
import path2 from "path";
|
|
180
|
-
import fs2 from "fs-extra";
|
|
181
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
182
|
-
|
|
183
|
-
// src/notarize.ts
|
|
184
|
-
import path3 from "path";
|
|
191
|
+
// ../shared/src/dotnet-toolchain.ts
|
|
185
192
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
186
|
-
|
|
187
|
-
// src/windows-signing.ts
|
|
188
|
-
import path4 from "path";
|
|
189
|
-
import fs3 from "fs-extra";
|
|
190
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
191
|
-
|
|
192
|
-
// src/velopack.ts
|
|
193
|
-
import path5 from "path";
|
|
194
|
-
import os from "os";
|
|
195
|
-
import fs4 from "fs-extra";
|
|
196
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
197
|
-
|
|
198
|
-
// src/update-config.ts
|
|
199
|
-
import path6 from "path";
|
|
200
|
-
import fs5 from "fs-extra";
|
|
201
|
-
|
|
202
|
-
// src/project.ts
|
|
203
|
-
import path8 from "path";
|
|
204
|
-
|
|
205
|
-
// src/version.ts
|
|
206
|
-
import fs6 from "fs-extra";
|
|
207
|
-
import path7 from "path";
|
|
208
|
-
|
|
209
|
-
// src/project.ts
|
|
210
|
-
import fs7 from "fs-extra";
|
|
211
|
-
|
|
212
|
-
// src/doctor.ts
|
|
193
|
+
import prompts from "prompts";
|
|
213
194
|
var DOTNET = process.platform === "win32" ? "dotnet.exe" : "dotnet";
|
|
214
195
|
var MAUI_DOCS = "https://learn.microsoft.com/dotnet/maui/get-started/installation";
|
|
215
196
|
var INSTALL_NET_10_FIX = "Install the .NET 10 SDK \u2014 https://dotnet.microsoft.com/download";
|
|
216
|
-
var bufToStr = (v) => v == null ? "" : Buffer.isBuffer(v) ? v.toString() : v;
|
|
217
|
-
var run = (cmd, args) => {
|
|
218
|
-
try {
|
|
219
|
-
const stdout = execFileSync5(cmd, args, {
|
|
220
|
-
encoding: "utf-8",
|
|
221
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
222
|
-
});
|
|
223
|
-
return { found: true, ok: true, stdout: stdout ?? "", stderr: "" };
|
|
224
|
-
} catch (e) {
|
|
225
|
-
const err = e;
|
|
226
|
-
return {
|
|
227
|
-
found: err.code !== "ENOENT",
|
|
228
|
-
ok: false,
|
|
229
|
-
stdout: bufToStr(err.stdout),
|
|
230
|
-
stderr: bufToStr(err.stderr)
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
var splitLines = (text) => text.split(/\r?\n/);
|
|
235
197
|
var NET_10_VERSION = /^10\./;
|
|
236
198
|
var MAUI_WORKLOAD = /\bmaui\b/i;
|
|
237
199
|
var hasNet10Sdk = (listSdksOutput) => splitLines(listSdksOutput).some((line) => NET_10_VERSION.test(line.trim()));
|
|
@@ -265,7 +227,6 @@ var checkDotnetSdk = () => {
|
|
|
265
227
|
};
|
|
266
228
|
};
|
|
267
229
|
var isMauiWorkloadInstalled = () => outputMentionsMaui(run(DOTNET, ["workload", "list"]).stdout);
|
|
268
|
-
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
269
230
|
var installWorkload = (csprojPath) => {
|
|
270
231
|
const args = csprojPath ? ["workload", "restore", csprojPath] : ["workload", "install", "maui"];
|
|
271
232
|
console.log();
|
|
@@ -282,7 +243,7 @@ var installWorkload = (csprojPath) => {
|
|
|
282
243
|
);
|
|
283
244
|
console.log();
|
|
284
245
|
try {
|
|
285
|
-
|
|
246
|
+
execFileSync2(DOTNET, args, { stdio: "inherit" });
|
|
286
247
|
return true;
|
|
287
248
|
} catch {
|
|
288
249
|
console.error();
|
|
@@ -360,15 +321,15 @@ var ensureMauiWorkload = async (opts = {}) => {
|
|
|
360
321
|
|
|
361
322
|
// src/index.ts
|
|
362
323
|
var note = (label, body) => ` ${dim(label.padEnd(7))} ${body}`;
|
|
363
|
-
var __dirname =
|
|
364
|
-
var CLI_ROOT =
|
|
365
|
-
var TEMPLATES_DIR =
|
|
366
|
-
var VIDRA_REPO_ROOT =
|
|
367
|
-
var LOCAL_FEED_DIR =
|
|
368
|
-
var LOCAL_CLI_DIR =
|
|
369
|
-
var LOCAL_SDK_DIR =
|
|
324
|
+
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
325
|
+
var CLI_ROOT = path2.resolve(__dirname, "..");
|
|
326
|
+
var TEMPLATES_DIR = path2.join(CLI_ROOT, "templates");
|
|
327
|
+
var VIDRA_REPO_ROOT = path2.resolve(CLI_ROOT, "..", "..", "..");
|
|
328
|
+
var LOCAL_FEED_DIR = path2.join(VIDRA_REPO_ROOT, "dist", "packages");
|
|
329
|
+
var LOCAL_CLI_DIR = path2.join(VIDRA_REPO_ROOT, "src", "cli", "vidra-cli");
|
|
330
|
+
var LOCAL_SDK_DIR = path2.join(VIDRA_REPO_ROOT, "src", "sdk", "vidra-js");
|
|
370
331
|
var VIDRA_VERSION = JSON.parse(
|
|
371
|
-
|
|
332
|
+
fs2.readFileSync(path2.join(CLI_ROOT, "package.json"), "utf-8")
|
|
372
333
|
).version;
|
|
373
334
|
var SDK_VERSION = VIDRA_VERSION;
|
|
374
335
|
var main = async () => {
|
|
@@ -409,8 +370,8 @@ var main = async () => {
|
|
|
409
370
|
);
|
|
410
371
|
appId = res.appId;
|
|
411
372
|
}
|
|
412
|
-
const root =
|
|
413
|
-
if (
|
|
373
|
+
const root = path2.resolve(projectDir);
|
|
374
|
+
if (fs2.existsSync(root) && fs2.readdirSync(root).length > 0) {
|
|
414
375
|
console.error();
|
|
415
376
|
console.error(
|
|
416
377
|
row({
|
|
@@ -426,8 +387,8 @@ var main = async () => {
|
|
|
426
387
|
console.log(kv("directory", root));
|
|
427
388
|
console.log(kv("app id", appId));
|
|
428
389
|
console.log();
|
|
429
|
-
const isMonorepo =
|
|
430
|
-
const localFeedExists = isMonorepo &&
|
|
390
|
+
const isMonorepo = fs2.existsSync(path2.join(LOCAL_SDK_DIR, "package.json"));
|
|
391
|
+
const localFeedExists = isMonorepo && fs2.existsSync(LOCAL_FEED_DIR);
|
|
431
392
|
const localFeedSource = localFeedExists ? ` <add key="vidra-local" value="${toTextPath(LOCAL_FEED_DIR)}" />` : "";
|
|
432
393
|
const cliRef = isMonorepo ? `file:${toTextPath(LOCAL_CLI_DIR)}` : `^${VIDRA_VERSION}`;
|
|
433
394
|
const sdkRef = isMonorepo ? `file:${toTextPath(LOCAL_SDK_DIR)}` : `^${SDK_VERSION}`;
|
|
@@ -442,17 +403,17 @@ var main = async () => {
|
|
|
442
403
|
"{{sdkVersion}}": sdkRef,
|
|
443
404
|
"{{localFeedSource}}": localFeedSource
|
|
444
405
|
};
|
|
445
|
-
const templateDir =
|
|
406
|
+
const templateDir = path2.join(TEMPLATES_DIR, "react-vite");
|
|
446
407
|
await scaffoldDir(templateDir, root, replacements);
|
|
447
408
|
console.log(row({ glyph: "active", detail: dim("creating solution\u2026") }));
|
|
448
409
|
exec(`dotnet new sln -n ${projectName} --force`, root);
|
|
449
|
-
const slnFile =
|
|
410
|
+
const slnFile = fs2.existsSync(path2.join(root, `${projectName}.slnx`)) ? `${projectName}.slnx` : `${projectName}.sln`;
|
|
450
411
|
exec(
|
|
451
412
|
`dotnet sln ${slnFile} add src/${projectName}.Host/${projectName}.Host.csproj`,
|
|
452
413
|
root
|
|
453
414
|
);
|
|
454
415
|
console.log(row({ glyph: "active", detail: dim("installing dependencies\u2026") }));
|
|
455
|
-
const uiDir =
|
|
416
|
+
const uiDir = path2.join(root, "ui");
|
|
456
417
|
const [rootNpmOk, uiNpmOk] = await Promise.all([
|
|
457
418
|
tryExecAsync("npm install", root),
|
|
458
419
|
tryExecAsync("npm install", uiDir)
|
|
@@ -478,7 +439,7 @@ var main = async () => {
|
|
|
478
439
|
}
|
|
479
440
|
if (isMonorepo) {
|
|
480
441
|
console.log(note("npm", `${dim("@vidra-dev/sdk \u2192")} ${value(LOCAL_SDK_DIR)}`));
|
|
481
|
-
console.log(note("npm", `${dim("
|
|
442
|
+
console.log(note("npm", `${dim("vidra-cli \u2192")} ${value(LOCAL_CLI_DIR)}`));
|
|
482
443
|
}
|
|
483
444
|
console.log();
|
|
484
445
|
if (!npmOk) {
|
|
@@ -490,7 +451,7 @@ var main = async () => {
|
|
|
490
451
|
);
|
|
491
452
|
console.log();
|
|
492
453
|
}
|
|
493
|
-
const hostCsproj =
|
|
454
|
+
const hostCsproj = path2.join(
|
|
494
455
|
root,
|
|
495
456
|
"src",
|
|
496
457
|
`${projectName}.Host`,
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-vidra-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Scaffold a new Vidra application (React + .NET MAUI)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-vidra-app": "
|
|
8
|
-
"vidra": "./bin/vidra.mjs"
|
|
7
|
+
"create-vidra-app": "bin/create-vidra-app.mjs"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
10
|
"bin/",
|
|
@@ -15,6 +14,7 @@
|
|
|
15
14
|
"scripts": {
|
|
16
15
|
"build": "tsup",
|
|
17
16
|
"dev": "tsup --watch",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
18
|
"test": "vitest run",
|
|
19
19
|
"test:watch": "vitest",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
"desktop",
|
|
34
34
|
"electron-alternative",
|
|
35
35
|
"tauri-alternative",
|
|
36
|
-
"scaffold"
|
|
37
|
-
"cli"
|
|
36
|
+
"scaffold"
|
|
38
37
|
],
|
|
39
38
|
"repository": {
|
|
40
39
|
"type": "git",
|
|
@@ -47,19 +46,23 @@
|
|
|
47
46
|
},
|
|
48
47
|
"license": "MIT",
|
|
49
48
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
51
53
|
},
|
|
52
54
|
"dependencies": {
|
|
53
|
-
"chalk": "^
|
|
55
|
+
"chalk": "^6.0.0",
|
|
54
56
|
"fs-extra": "^11.2.0",
|
|
55
57
|
"prompts": "^2.4.2"
|
|
56
58
|
},
|
|
57
59
|
"devDependencies": {
|
|
60
|
+
"@vidra-dev/cli-shared": "*",
|
|
58
61
|
"@types/fs-extra": "^11.0.4",
|
|
59
|
-
"@types/node": "^22.
|
|
62
|
+
"@types/node": "^22.20.0",
|
|
60
63
|
"@types/prompts": "^2.4.9",
|
|
61
64
|
"tsup": "^8.5.1",
|
|
62
|
-
"typescript": "^
|
|
63
|
-
"vitest": "^
|
|
65
|
+
"typescript": "^7.0.2",
|
|
66
|
+
"vitest": "^4.1.10"
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -1,97 +1,45 @@
|
|
|
1
1
|
# {{appTitle}}
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A desktop application built with [Vidra](https://vidra.build): a React UI and a
|
|
4
|
+
C#/.NET native host.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
### Prerequisites
|
|
6
|
+
## Requirements
|
|
8
7
|
|
|
9
8
|
- [.NET 10 SDK](https://dotnet.microsoft.com/download)
|
|
10
|
-
-
|
|
11
|
-
- [Node.js](https://nodejs.org/)
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
Not sure if you're set up? Run:
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm run doctor
|
|
18
|
-
```
|
|
9
|
+
- .NET MAUI workload: `dotnet workload install maui`
|
|
10
|
+
- [Node.js](https://nodejs.org/) 22 or newer
|
|
11
|
+
- Xcode for macOS builds
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
exact command to fix anything that's missing.
|
|
13
|
+
Windows targets must be built on Windows.
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
> through npm (`npm run dev`, `npm run doctor`) or with `npx vidra <command>` —
|
|
25
|
-
> there is no global `vidra` command to install.
|
|
26
|
-
|
|
27
|
-
### Development
|
|
15
|
+
## Development
|
|
28
16
|
|
|
29
17
|
```bash
|
|
18
|
+
npm run doctor
|
|
30
19
|
npm run dev
|
|
31
20
|
```
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **UI**: edit anything in `ui/src` — Vite HMR updates the WebView instantly.
|
|
37
|
-
- **C#**: edit the host (for example `OnTickAsync` in `MainPage.cs`) and save —
|
|
38
|
-
the session puts the change in front of you in seconds. *How* depends on the
|
|
39
|
-
platform:
|
|
40
|
-
- **Windows**: supported edits apply to the *running* app and the UI flashes a
|
|
41
|
-
"C# reloaded" badge. Edits hot reload can't express (new fields, changed
|
|
42
|
-
signatures, …) trigger an automatic rebuild and relaunch.
|
|
43
|
-
- **macOS**: the same, when the toolchain cooperates. Mac Catalyst's
|
|
44
|
-
hot-reload agent often drops its connection mid-session (dotnet/sdk#55488);
|
|
45
|
-
a dropped agent applies nothing while `dotnet watch` still reports success,
|
|
46
|
-
so `vidra dev` watches for it and switches the session to rebuild +
|
|
47
|
-
relaunch on save. You'll see it say so — after that, edits arrive with a
|
|
48
|
-
restart and no badge.
|
|
49
|
-
`npm run doctor` says which loop you get.
|
|
50
|
-
|
|
51
|
-
To skip `dotnet watch` entirely and do a single build and launch:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
npx vidra dev --no-hot-reload
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
To target a specific desktop platform explicitly:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
npx vidra dev --target macos
|
|
61
|
-
npx vidra dev --target windows
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
If you want to run the pieces separately:
|
|
22
|
+
`npm run dev` starts Vite and the native host together. Changes to the web UI
|
|
23
|
+
reload through Vite; supported C# changes reload through the .NET development
|
|
24
|
+
loop.
|
|
65
25
|
|
|
66
|
-
|
|
67
|
-
npm run dev:ui
|
|
68
|
-
npm run dev:host:macos
|
|
69
|
-
npm run dev:host:windows
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Production Build
|
|
26
|
+
## Build
|
|
73
27
|
|
|
74
28
|
```bash
|
|
75
29
|
npm run build
|
|
76
30
|
```
|
|
77
31
|
|
|
78
|
-
|
|
32
|
+
The `vidra` CLI is a local project dependency. Run `npx vidra --help` to see
|
|
33
|
+
target, packaging, and update options.
|
|
79
34
|
|
|
80
|
-
|
|
35
|
+
## Project structure
|
|
36
|
+
|
|
37
|
+
```text
|
|
81
38
|
{{projectNameKebab}}/
|
|
82
39
|
├── src/
|
|
83
|
-
│ └── {{projectName}}.Host/
|
|
84
|
-
|
|
85
|
-
│ ├── MainPage.cs # Main page (extends VidraPage)
|
|
86
|
-
│ └── Platforms/ # Platform-specific code
|
|
87
|
-
└── ui/ # React frontend
|
|
88
|
-
├── src/
|
|
89
|
-
│ ├── App.tsx # Main React component
|
|
90
|
-
│ └── main.tsx # Entry point
|
|
91
|
-
├── vite.config.ts
|
|
92
|
-
└── package.json
|
|
40
|
+
│ └── {{projectName}}.Host/ # .NET MAUI host and C# contracts
|
|
41
|
+
└── ui/ # React application and generated TypeScript
|
|
93
42
|
```
|
|
94
43
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Built with [Vidra](https://vidra.build) — a C#/.NET native core with a web UI.
|
|
44
|
+
Read the [Vidra documentation](https://vidra.build/docs/) for bridge guides,
|
|
45
|
+
capabilities, distribution, and updates.
|
|
@@ -23,11 +23,22 @@ public class MainPage : VidraPage
|
|
|
23
23
|
try
|
|
24
24
|
{
|
|
25
25
|
var count = await Bridge.Js().Counter.IncrementAsync();
|
|
26
|
-
|
|
26
|
+
Console.WriteLine($"[MainPage] Counter is now {count}");
|
|
27
27
|
}
|
|
28
28
|
catch (Exception ex)
|
|
29
29
|
{
|
|
30
|
-
|
|
30
|
+
// Console, which is what Vidra.Hosting itself logs through, and the
|
|
31
|
+
// only one of the three that a Release build actually emits.
|
|
32
|
+
// Debug.WriteLine is compiled out of Release entirely, and Release is
|
|
33
|
+
// exactly the build where someone is looking at a counter stuck at
|
|
34
|
+
// zero. Trace.TraceError survives the compile but goes to
|
|
35
|
+
// DefaultTraceListener, which writes nothing unless a debugger is
|
|
36
|
+
// attached, and this template registers no listener.
|
|
37
|
+
//
|
|
38
|
+
// The whole exception, not ex.Message: the bridge's answer to "why"
|
|
39
|
+
// is the error code on it, and JS_HANDLER_NOT_FOUND, JS_HANDLER_ERROR
|
|
40
|
+
// and JS_RESPONSE_INVALID are three different problems.
|
|
41
|
+
Console.Error.WriteLine($"[MainPage] Counter increment failed: {ex}");
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
44
|
}
|
|
@@ -9,28 +9,32 @@ public static class MauiProgram
|
|
|
9
9
|
{
|
|
10
10
|
var builder = MauiApp.CreateBuilder();
|
|
11
11
|
|
|
12
|
-
// Updates
|
|
13
|
-
//
|
|
12
|
+
// Updates are wired up and doing nothing, which is the intended state
|
|
13
|
+
// until this app has a feed to check. The switch is already in
|
|
14
|
+
// vidra.config.ts, empty — filling it in is the whole opt-in:
|
|
14
15
|
//
|
|
15
|
-
//
|
|
16
|
+
// updates: { feed: "https://updates.example.com/notes/" }
|
|
16
17
|
//
|
|
17
|
-
//
|
|
18
|
+
// Type a URL there, or run `npx vidra updates init --feed <url>`, then
|
|
19
|
+
// publish with `npx vidra build` (both tiers) or `npx vidra build --web`
|
|
20
|
+
// (just the UI, no compile).
|
|
18
21
|
//
|
|
19
|
-
//
|
|
20
|
-
// contract fingerprints match this build, so JS can never call a bridge
|
|
21
|
-
// the installed binary lacks.
|
|
22
|
+
// One directory serves both tiers. To split them across two hosts:
|
|
22
23
|
//
|
|
23
|
-
//
|
|
24
|
-
// `.UseVidraNativeUpdates()`, uncomment the line in Platforms/*/Program.cs,
|
|
25
|
-
// and add
|
|
24
|
+
// "feed": { "web": "https://cdn/notes/", "app": "https://dl/notes/" }
|
|
26
25
|
//
|
|
27
|
-
//
|
|
26
|
+
// Web bundle: your `ui/` build, applied on the next launch, no reinstall.
|
|
27
|
+
// A bundle only installs when its contract fingerprints match this build,
|
|
28
|
+
// so JS can never call a bridge the installed binary lacks.
|
|
28
29
|
//
|
|
29
|
-
//
|
|
30
|
-
// `
|
|
30
|
+
// Whole app: native code included, via Velopack. Its other half is the
|
|
31
|
+
// `VelopackApp` line in Platforms/*/Program.cs, which has to run before
|
|
32
|
+
// the UI framework starts.
|
|
31
33
|
builder
|
|
32
34
|
.UseMauiApp<App>()
|
|
33
35
|
.UseVidra()
|
|
36
|
+
.UseVidraUpdates()
|
|
37
|
+
.UseVidraNativeUpdates()
|
|
34
38
|
.ConfigureFonts(fonts =>
|
|
35
39
|
{
|
|
36
40
|
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
|
@@ -6,20 +6,17 @@
|
|
|
6
6
|
// also inspects the assembly statically and warns when
|
|
7
7
|
// `VelopackApp.Build()...Run()` is anywhere other than the entry point.
|
|
8
8
|
//
|
|
9
|
+
// It is live from the first scaffold and does nothing at all until this app is
|
|
10
|
+
// installed from a Velopack release — which it is not, until `vidra.config.ts`
|
|
11
|
+
// names an updates feed and `vidra build` packs
|
|
12
|
+
// a release into it. Filling in that URL is the whole opt-in.
|
|
13
|
+
//
|
|
9
14
|
// `UseVidraLocator()` is the Mac Catalyst part. Velopack's client picks its
|
|
10
15
|
// locator from `RuntimeInformation.IsOSPlatform`, which answers false for OSX
|
|
11
16
|
// here, so without it, `Run()` throws before any update logic executes.
|
|
12
|
-
//
|
|
13
|
-
// To turn native updates on:
|
|
14
|
-
// 1. add the Vidra.Updates.Native package reference to this project
|
|
15
|
-
// 2. uncomment the two lines below, and the ones in MauiProgram.cs
|
|
16
|
-
// 3. add a `vidra.updates.native.feedUrl` to package.json
|
|
17
|
-
// 4. build with `npx vidra build --target macos --native-update`
|
|
18
|
-
//
|
|
19
|
-
// `npx vidra doctor` checks all four and names whichever is missing.
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
using Velopack;
|
|
19
|
+
using Vidra.Hosting;
|
|
23
20
|
using UIKit;
|
|
24
21
|
|
|
25
22
|
namespace {{projectName}};
|
|
@@ -28,7 +25,7 @@ public static class Program
|
|
|
28
25
|
{
|
|
29
26
|
static void Main(string[] args)
|
|
30
27
|
{
|
|
31
|
-
|
|
28
|
+
VelopackApp.Build().UseVidraLocator().Run();
|
|
32
29
|
|
|
33
30
|
UIApplication.Main(args, null, typeof(AppDelegate));
|
|
34
31
|
}
|
|
@@ -9,19 +9,15 @@
|
|
|
9
9
|
// `--veloapp-*` argument and expects it to do that work and exit without ever
|
|
10
10
|
// showing a window. `VelopackApp.Build()...Run()` is what handles those launches,
|
|
11
11
|
// and `vpk pack` statically inspects the assembly and warns when it finds the
|
|
12
|
-
// call anywhere other than the entry point.
|
|
13
|
-
// means an app that later enables updates needs no migration into generated code.
|
|
12
|
+
// call anywhere other than the entry point.
|
|
14
13
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// 4. build with `npx vidra build --target windows --native-update`
|
|
20
|
-
//
|
|
21
|
-
// `npx vidra doctor` checks all four and names whichever is missing.
|
|
14
|
+
// It is live from the first scaffold and does nothing at all until this app is
|
|
15
|
+
// installed from a Velopack release — which it is not, until `vidra.config.ts`
|
|
16
|
+
// names an updates feed and `vidra build` packs
|
|
17
|
+
// a release into it. Filling in that URL is the whole opt-in.
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
using Velopack;
|
|
20
|
+
using Vidra.Hosting;
|
|
25
21
|
|
|
26
22
|
namespace {{projectName}}.WinUI;
|
|
27
23
|
|
|
@@ -30,7 +26,7 @@ public static class Program
|
|
|
30
26
|
[STAThread]
|
|
31
27
|
static void Main(string[] args)
|
|
32
28
|
{
|
|
33
|
-
|
|
29
|
+
VelopackApp.Build().UseVidraLocator().Run();
|
|
34
30
|
|
|
35
31
|
WinRT.ComWrappersSupport.InitializeComWrappers();
|
|
36
32
|
|