create-vidra-app 0.1.2 → 0.1.4
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 +8 -2
- package/dist/cli.js +318 -59
- package/dist/index.js +169 -21
- package/package.json +1 -1
- package/templates/react-vite/README.md +12 -2
- package/templates/react-vite/ui/src/App.tsx +78 -49
- package/templates/react-vite/ui/src/index.css +280 -45
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import prompts2 from "prompts";
|
|
3
|
+
import chalk3 from "chalk";
|
|
4
4
|
import fs2 from "fs-extra";
|
|
5
5
|
import path2 from "path";
|
|
6
6
|
import { randomUUID } from "crypto";
|
|
@@ -112,6 +112,140 @@ var applyReplacements = (str, replacements) => {
|
|
|
112
112
|
return str;
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
// src/doctor.ts
|
|
116
|
+
import { execFileSync } from "child_process";
|
|
117
|
+
import prompts from "prompts";
|
|
118
|
+
import chalk2 from "chalk";
|
|
119
|
+
var DOTNET = process.platform === "win32" ? "dotnet.exe" : "dotnet";
|
|
120
|
+
var MAUI_DOCS = "https://learn.microsoft.com/dotnet/maui/get-started/installation";
|
|
121
|
+
var bufToStr = (v) => v == null ? "" : Buffer.isBuffer(v) ? v.toString() : v;
|
|
122
|
+
var run = (cmd, args) => {
|
|
123
|
+
try {
|
|
124
|
+
const stdout = execFileSync(cmd, args, {
|
|
125
|
+
encoding: "utf-8",
|
|
126
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
127
|
+
});
|
|
128
|
+
return { found: true, ok: true, stdout: stdout ?? "", stderr: "" };
|
|
129
|
+
} catch (e) {
|
|
130
|
+
const err = e;
|
|
131
|
+
return {
|
|
132
|
+
found: err.code !== "ENOENT",
|
|
133
|
+
ok: false,
|
|
134
|
+
stdout: bufToStr(err.stdout),
|
|
135
|
+
stderr: bufToStr(err.stderr)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var hasNet10Sdk = (listSdksOutput) => listSdksOutput.split(/\r?\n/).some((line) => /^10\./.test(line.trim()));
|
|
140
|
+
var newestNet10Sdk = (listSdksOutput) => listSdksOutput.split(/\r?\n/).map((line) => line.trim().split(/\s+/)[0]).filter((v) => /^10\./.test(v)).sort(
|
|
141
|
+
(a, b) => a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" })
|
|
142
|
+
).pop();
|
|
143
|
+
var outputMentionsMaui = (workloadListOutput) => /\bmaui\b/i.test(workloadListOutput);
|
|
144
|
+
var checkDotnetSdk = () => {
|
|
145
|
+
const res = run(DOTNET, ["--list-sdks"]);
|
|
146
|
+
if (!res.found) {
|
|
147
|
+
return {
|
|
148
|
+
name: ".NET SDK",
|
|
149
|
+
status: "missing",
|
|
150
|
+
detail: "`dotnet` was not found on your PATH",
|
|
151
|
+
fix: "Install the .NET 10 SDK \u2014 https://dotnet.microsoft.com/download"
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (!res.ok && !res.stdout) {
|
|
155
|
+
return {
|
|
156
|
+
name: ".NET SDK",
|
|
157
|
+
status: "unknown",
|
|
158
|
+
detail: "could not run `dotnet --list-sdks`"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (hasNet10Sdk(res.stdout)) {
|
|
162
|
+
const newest = newestNet10Sdk(res.stdout);
|
|
163
|
+
return {
|
|
164
|
+
name: ".NET SDK",
|
|
165
|
+
status: "ok",
|
|
166
|
+
detail: newest ? `found ${newest}` : "found 10.x"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
name: ".NET SDK",
|
|
171
|
+
status: "missing",
|
|
172
|
+
detail: "no 10.x SDK installed",
|
|
173
|
+
fix: "Install the .NET 10 SDK \u2014 https://dotnet.microsoft.com/download"
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
var isMauiWorkloadInstalled = () => outputMentionsMaui(run(DOTNET, ["workload", "list"]).stdout);
|
|
177
|
+
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
178
|
+
var installWorkload = (csprojPath) => {
|
|
179
|
+
const args = csprojPath ? ["workload", "restore", csprojPath] : ["workload", "install", "maui"];
|
|
180
|
+
console.log();
|
|
181
|
+
console.log(` ${chalk2.dim(`Running: ${DOTNET} ${args.join(" ")}`)}`);
|
|
182
|
+
console.log(
|
|
183
|
+
` ${chalk2.dim(
|
|
184
|
+
"This can download several hundred MB and take a few minutes."
|
|
185
|
+
)}`
|
|
186
|
+
);
|
|
187
|
+
console.log();
|
|
188
|
+
try {
|
|
189
|
+
execFileSync(DOTNET, args, { stdio: "inherit" });
|
|
190
|
+
return true;
|
|
191
|
+
} catch {
|
|
192
|
+
console.error();
|
|
193
|
+
console.error(` ${chalk2.red("Workload install failed.")}`);
|
|
194
|
+
console.error(
|
|
195
|
+
` ${chalk2.dim(
|
|
196
|
+
"If this is a permissions error, your SDK is in a system location and needs elevation:"
|
|
197
|
+
)}`
|
|
198
|
+
);
|
|
199
|
+
console.error(` ${chalk2.cyan("sudo dotnet workload install maui")}`);
|
|
200
|
+
console.error();
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var ensureMauiWorkload = async (opts = {}) => {
|
|
205
|
+
const dotnet = checkDotnetSdk();
|
|
206
|
+
if (dotnet.status === "missing") {
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(` ${chalk2.yellow("!")} ${dotnet.name} \u2014 ${dotnet.detail}`);
|
|
209
|
+
if (dotnet.fix) {
|
|
210
|
+
console.log(` ${chalk2.dim("fix:")} ${chalk2.cyan(dotnet.fix)}`);
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
if (dotnet.status === "unknown") return true;
|
|
215
|
+
if (isMauiWorkloadInstalled()) return true;
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(
|
|
218
|
+
` ${chalk2.yellow("!")} The .NET MAUI workload is required but not installed.`
|
|
219
|
+
);
|
|
220
|
+
const interactive = opts.interactive ?? isInteractive();
|
|
221
|
+
if (interactive) {
|
|
222
|
+
let install = false;
|
|
223
|
+
try {
|
|
224
|
+
const res = await prompts({
|
|
225
|
+
type: "confirm",
|
|
226
|
+
name: "install",
|
|
227
|
+
message: "Install the .NET MAUI workload now?",
|
|
228
|
+
initial: true
|
|
229
|
+
});
|
|
230
|
+
install = Boolean(res.install);
|
|
231
|
+
} catch {
|
|
232
|
+
install = false;
|
|
233
|
+
}
|
|
234
|
+
if (install) {
|
|
235
|
+
if (installWorkload(opts.csprojPath) && isMauiWorkloadInstalled()) {
|
|
236
|
+
console.log(` ${chalk2.green("\u2713")} MAUI workload installed.`);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
console.log(
|
|
243
|
+
` ${chalk2.dim("run:")} ${chalk2.cyan("dotnet workload install maui")}`
|
|
244
|
+
);
|
|
245
|
+
console.log(` ${chalk2.dim("docs:")} ${chalk2.cyan(MAUI_DOCS)}`);
|
|
246
|
+
return false;
|
|
247
|
+
};
|
|
248
|
+
|
|
115
249
|
// src/index.ts
|
|
116
250
|
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
117
251
|
var CLI_ROOT = path2.resolve(__dirname, "..");
|
|
@@ -124,13 +258,13 @@ var VIDRA_VERSION = "0.1.0";
|
|
|
124
258
|
var SDK_VERSION = "0.1.0";
|
|
125
259
|
var main = async () => {
|
|
126
260
|
console.log();
|
|
127
|
-
console.log(
|
|
128
|
-
console.log(
|
|
261
|
+
console.log(chalk3.bold(" create-vidra-app"));
|
|
262
|
+
console.log(chalk3.dim(" Scaffold a new Vidra application\n"));
|
|
129
263
|
const args = parseArgs(process.argv);
|
|
130
264
|
let projectDir = args._[0];
|
|
131
265
|
let appId = args["app-id"];
|
|
132
266
|
if (!projectDir) {
|
|
133
|
-
const res = await
|
|
267
|
+
const res = await prompts2(
|
|
134
268
|
{
|
|
135
269
|
type: "text",
|
|
136
270
|
name: "projectDir",
|
|
@@ -147,7 +281,7 @@ var main = async () => {
|
|
|
147
281
|
const appTitle = toTitleCase(projectDir);
|
|
148
282
|
const appGuid = randomUUID().toUpperCase();
|
|
149
283
|
if (!appId) {
|
|
150
|
-
const res = await
|
|
284
|
+
const res = await prompts2(
|
|
151
285
|
{
|
|
152
286
|
type: "text",
|
|
153
287
|
name: "appId",
|
|
@@ -162,7 +296,7 @@ var main = async () => {
|
|
|
162
296
|
const root = path2.resolve(projectDir);
|
|
163
297
|
if (fs2.existsSync(root) && fs2.readdirSync(root).length > 0) {
|
|
164
298
|
console.error(
|
|
165
|
-
|
|
299
|
+
chalk3.red(
|
|
166
300
|
`
|
|
167
301
|
Directory "${projectDir}" already exists and is not empty.
|
|
168
302
|
`
|
|
@@ -171,9 +305,9 @@ var main = async () => {
|
|
|
171
305
|
process.exit(1);
|
|
172
306
|
}
|
|
173
307
|
console.log();
|
|
174
|
-
console.log(` ${
|
|
175
|
-
console.log(` ${
|
|
176
|
-
console.log(` ${
|
|
308
|
+
console.log(` ${chalk3.dim("Project:")} ${chalk3.cyan(projectName)}`);
|
|
309
|
+
console.log(` ${chalk3.dim("Directory:")} ${chalk3.cyan(root)}`);
|
|
310
|
+
console.log(` ${chalk3.dim("App ID:")} ${chalk3.cyan(appId)}`);
|
|
177
311
|
console.log();
|
|
178
312
|
const isMonorepo = fs2.existsSync(path2.join(LOCAL_SDK_DIR, "package.json"));
|
|
179
313
|
const localFeedExists = isMonorepo && fs2.existsSync(LOCAL_FEED_DIR);
|
|
@@ -193,51 +327,65 @@ var main = async () => {
|
|
|
193
327
|
};
|
|
194
328
|
const templateDir = path2.join(TEMPLATES_DIR, "react-vite");
|
|
195
329
|
await scaffoldDir(templateDir, root, replacements);
|
|
196
|
-
console.log(
|
|
330
|
+
console.log(chalk3.dim(" Creating solution..."));
|
|
197
331
|
exec(`dotnet new sln -n ${projectName} --force`, root);
|
|
198
332
|
const slnFile = fs2.existsSync(path2.join(root, `${projectName}.slnx`)) ? `${projectName}.slnx` : `${projectName}.sln`;
|
|
199
333
|
exec(
|
|
200
334
|
`dotnet sln ${slnFile} add src/${projectName}.Host/${projectName}.Host.csproj`,
|
|
201
335
|
root
|
|
202
336
|
);
|
|
203
|
-
console.log(
|
|
337
|
+
console.log(chalk3.dim(" Installing npm dependencies..."));
|
|
204
338
|
const uiDir = path2.join(root, "ui");
|
|
205
339
|
const rootNpmOk = tryExec("npm install", root);
|
|
206
340
|
const uiNpmOk = tryExec("npm install", uiDir);
|
|
207
341
|
const npmOk = rootNpmOk && uiNpmOk;
|
|
208
342
|
console.log();
|
|
209
|
-
console.log(
|
|
343
|
+
console.log(chalk3.green(" Done! ") + "Your Vidra app is ready.\n");
|
|
210
344
|
if (localFeedExists) {
|
|
211
345
|
console.log(
|
|
212
|
-
|
|
346
|
+
chalk3.dim(" NuGet:") + " local feed \u2192 " + chalk3.cyan(LOCAL_FEED_DIR)
|
|
213
347
|
);
|
|
214
348
|
} else if (isMonorepo) {
|
|
215
349
|
console.log(
|
|
216
|
-
|
|
350
|
+
chalk3.yellow(" Note: ") + "Local NuGet feed not found. Run " + chalk3.cyan("./pack-local.sh") + " in the Vidra repo, then update NuGet.Config."
|
|
217
351
|
);
|
|
218
352
|
}
|
|
219
353
|
if (isMonorepo) {
|
|
220
354
|
console.log(
|
|
221
|
-
|
|
355
|
+
chalk3.dim(" npm: ") + " @vidra-dev/sdk \u2192 " + chalk3.cyan(LOCAL_SDK_DIR)
|
|
222
356
|
);
|
|
223
357
|
console.log(
|
|
224
|
-
|
|
358
|
+
chalk3.dim(" npm: ") + " create-vidra-app \u2192 " + chalk3.cyan(LOCAL_CLI_DIR)
|
|
225
359
|
);
|
|
226
360
|
}
|
|
227
361
|
console.log();
|
|
228
362
|
if (!npmOk) {
|
|
229
363
|
console.log(
|
|
230
|
-
|
|
364
|
+
chalk3.yellow(" Note: ") + "`npm install` had errors. Re-run " + chalk3.cyan("npm install") + " in the project root and in " + chalk3.cyan("ui/") + " to retry.\n"
|
|
231
365
|
);
|
|
232
366
|
}
|
|
233
|
-
|
|
367
|
+
const hostCsproj = path2.join(
|
|
368
|
+
root,
|
|
369
|
+
"src",
|
|
370
|
+
`${projectName}.Host`,
|
|
371
|
+
`${projectName}.Host.csproj`
|
|
372
|
+
);
|
|
373
|
+
const prereqsReady = await ensureMauiWorkload({ csprojPath: hostCsproj });
|
|
374
|
+
console.log(chalk3.bold(" Next steps:\n"));
|
|
234
375
|
console.log(` cd ${projectDir}`);
|
|
235
376
|
console.log(
|
|
236
|
-
` npm run dev ${
|
|
377
|
+
` npm run dev ${chalk3.dim("# starts Vite + MAUI host together")}`
|
|
237
378
|
);
|
|
379
|
+
if (!prereqsReady) {
|
|
380
|
+
console.log(
|
|
381
|
+
` ${chalk3.dim("# tip: run")} ${chalk3.cyan(
|
|
382
|
+
"vidra doctor"
|
|
383
|
+
)} ${chalk3.dim("to verify your setup first")}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
238
386
|
console.log();
|
|
239
387
|
};
|
|
240
388
|
main().catch((e) => {
|
|
241
|
-
console.error(
|
|
389
|
+
console.error(chalk3.red(e.message));
|
|
242
390
|
process.exit(1);
|
|
243
391
|
});
|
package/package.json
CHANGED
|
@@ -6,9 +6,19 @@ A cross-platform application built with [Vidra](https://github.com/user/vidra)
|
|
|
6
6
|
|
|
7
7
|
### Prerequisites
|
|
8
8
|
|
|
9
|
-
- [.NET 10 SDK](https://dotnet.microsoft.com/download)
|
|
9
|
+
- [.NET 10 SDK](https://dotnet.microsoft.com/download)
|
|
10
|
+
- The .NET MAUI workload: `dotnet workload install maui`
|
|
10
11
|
- [Node.js](https://nodejs.org/) 18+
|
|
11
|
-
-
|
|
12
|
+
- macOS targets require Xcode; Windows targets must be built on Windows
|
|
13
|
+
|
|
14
|
+
Not sure if you're set up? Run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
vidra doctor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
It checks your .NET SDK, the MAUI workload, and (on macOS) Xcode, and prints the
|
|
21
|
+
exact command to fix anything that's missing.
|
|
12
22
|
|
|
13
23
|
### Development
|
|
14
24
|
|
|
@@ -181,58 +181,87 @@ const App = () => {
|
|
|
181
181
|
windowSupport.setFullscreen);
|
|
182
182
|
|
|
183
183
|
return (
|
|
184
|
-
<div className="
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
<
|
|
203
|
-
</
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
<>
|
|
220
|
-
<button onClick={handleMaximizeWindow}>Maximize Window</button>
|
|
221
|
-
<button onClick={handleRestoreWindow}>Restore Window</button>
|
|
222
|
-
</>
|
|
184
|
+
<div className="app">
|
|
185
|
+
<main className="container">
|
|
186
|
+
<header className="hero">
|
|
187
|
+
<span className="badge">
|
|
188
|
+
<span className="dot" />
|
|
189
|
+
React + .NET MAUI
|
|
190
|
+
</span>
|
|
191
|
+
<h1 className="title">{{appTitle}}</h1>
|
|
192
|
+
<p className="tagline">
|
|
193
|
+
A cross-platform desktop app with a native bridge.
|
|
194
|
+
</p>
|
|
195
|
+
</header>
|
|
196
|
+
|
|
197
|
+
<section className="card counter-card">
|
|
198
|
+
<div className="counter-meta">
|
|
199
|
+
<span className="counter-label">Live counter</span>
|
|
200
|
+
<span className="counter-sub">Incremented by .NET every 10s</span>
|
|
201
|
+
</div>
|
|
202
|
+
<span className="counter-value">{count}</span>
|
|
203
|
+
</section>
|
|
204
|
+
|
|
205
|
+
<section className="card">
|
|
206
|
+
<h2 className="card-title">Native modules</h2>
|
|
207
|
+
|
|
208
|
+
<div className="actions">
|
|
209
|
+
<button onClick={handleGetAppInfo}>Get App Info</button>
|
|
210
|
+
<button onClick={handleReadClipboard}>Read Clipboard</button>
|
|
211
|
+
<button onClick={handleNotification}>Send Notification</button>
|
|
212
|
+
<button onClick={handleCapabilities}>List Capabilities</button>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<p className="result">{info}</p>
|
|
216
|
+
|
|
217
|
+
{caps && (
|
|
218
|
+
<pre className="capabilities">{JSON.stringify(caps, null, 2)}</pre>
|
|
223
219
|
)}
|
|
224
|
-
|
|
225
|
-
|
|
220
|
+
</section>
|
|
221
|
+
|
|
222
|
+
<section className="card">
|
|
223
|
+
<h2 className="card-title">Window controls</h2>
|
|
224
|
+
|
|
225
|
+
<div className="actions">
|
|
226
|
+
<button onClick={handleGetWindowInfo}>Get Window Info</button>
|
|
227
|
+
<button onClick={handleRenameWindow}>Rename Window</button>
|
|
228
|
+
<button onClick={handleResizeWindow}>Resize Window</button>
|
|
229
|
+
{windowSupport?.center && (
|
|
230
|
+
<button onClick={handleCenterWindow}>Center Window</button>
|
|
231
|
+
)}
|
|
232
|
+
{windowSupport?.maximize && (
|
|
233
|
+
<>
|
|
234
|
+
<button onClick={handleMaximizeWindow}>Maximize Window</button>
|
|
235
|
+
<button onClick={handleRestoreWindow}>Restore Window</button>
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
238
|
+
{windowSupport?.minimize && (
|
|
239
|
+
<button onClick={handleMinimizeWindow}>Minimize Window</button>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<p className="window-summary">{windowSummary}</p>
|
|
244
|
+
|
|
245
|
+
{!showsAdvancedWindowActions && windowSupport && (
|
|
246
|
+
<p className="note">
|
|
247
|
+
This runtime currently supports title and size updates only.
|
|
248
|
+
Unsupported window actions are hidden automatically based on native
|
|
249
|
+
support metadata.
|
|
250
|
+
</p>
|
|
226
251
|
)}
|
|
227
|
-
</
|
|
252
|
+
</section>
|
|
228
253
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
254
|
+
<footer className="footer">
|
|
255
|
+
Built with{" "}
|
|
256
|
+
<a
|
|
257
|
+
href="https://github.com/rzamfiriu/vidra"
|
|
258
|
+
target="_blank"
|
|
259
|
+
rel="noreferrer"
|
|
260
|
+
>
|
|
261
|
+
Vidra
|
|
262
|
+
</a>
|
|
263
|
+
</footer>
|
|
264
|
+
</main>
|
|
236
265
|
</div>
|
|
237
266
|
);
|
|
238
267
|
};
|