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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import prompts from "prompts";
3
- import chalk2 from "chalk";
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(chalk2.bold(" create-vidra-app"));
128
- console.log(chalk2.dim(" Scaffold a new Vidra application\n"));
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 prompts(
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 prompts(
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
- chalk2.red(
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(` ${chalk2.dim("Project:")} ${chalk2.cyan(projectName)}`);
175
- console.log(` ${chalk2.dim("Directory:")} ${chalk2.cyan(root)}`);
176
- console.log(` ${chalk2.dim("App ID:")} ${chalk2.cyan(appId)}`);
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(chalk2.dim(" Creating solution..."));
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(chalk2.dim(" Installing npm dependencies..."));
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(chalk2.green(" Done! ") + "Your Vidra app is ready.\n");
343
+ console.log(chalk3.green(" Done! ") + "Your Vidra app is ready.\n");
210
344
  if (localFeedExists) {
211
345
  console.log(
212
- chalk2.dim(" NuGet:") + " local feed \u2192 " + chalk2.cyan(LOCAL_FEED_DIR)
346
+ chalk3.dim(" NuGet:") + " local feed \u2192 " + chalk3.cyan(LOCAL_FEED_DIR)
213
347
  );
214
348
  } else if (isMonorepo) {
215
349
  console.log(
216
- chalk2.yellow(" Note: ") + "Local NuGet feed not found. Run " + chalk2.cyan("./pack-local.sh") + " in the Vidra repo, then update NuGet.Config."
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
- chalk2.dim(" npm: ") + " @vidra-dev/sdk \u2192 " + chalk2.cyan(LOCAL_SDK_DIR)
355
+ chalk3.dim(" npm: ") + " @vidra-dev/sdk \u2192 " + chalk3.cyan(LOCAL_SDK_DIR)
222
356
  );
223
357
  console.log(
224
- chalk2.dim(" npm: ") + " create-vidra-app \u2192 " + chalk2.cyan(LOCAL_CLI_DIR)
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
- chalk2.yellow(" Note: ") + "`npm install` had errors. Re-run " + chalk2.cyan("npm install") + " in the project root and in " + chalk2.cyan("ui/") + " to retry.\n"
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
- console.log(chalk2.bold(" Next steps:\n"));
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 ${chalk2.dim("# starts Vite + MAUI host together")}`
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(chalk2.red(e.message));
389
+ console.error(chalk3.red(e.message));
242
390
  process.exit(1);
243
391
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-vidra-app",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Scaffold a new Vidra application (React + .NET MAUI)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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) with MAUI workload
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
- - Windows development must be run from a Windows machine with the MAUI Windows workload installed
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="container">
185
- <h1>{{appTitle}}</h1>
186
- <p className="subtitle">React + .NET MAUI</p>
187
-
188
- <div className="card">
189
- <p className="counter">
190
- Counter: <strong>{count}</strong>
191
- <span className="counter-hint">Incremented by .NET every 10s</span>
192
- </p>
193
- </div>
194
-
195
- <div className="card">
196
- <p className="result">{info}</p>
197
-
198
- <div className="actions">
199
- <button onClick={handleGetAppInfo}>Get App Info</button>
200
- <button onClick={handleReadClipboard}>Read Clipboard</button>
201
- <button onClick={handleNotification}>Send Notification</button>
202
- <button onClick={handleCapabilities}>List Capabilities</button>
203
- </div>
204
-
205
- {caps && (
206
- <pre className="capabilities">{JSON.stringify(caps, null, 2)}</pre>
207
- )}
208
- </div>
209
-
210
- <div className="card">
211
- <p className="window-summary">{windowSummary}</p>
212
-
213
- <div className="actions">
214
- <button onClick={handleGetWindowInfo}>Get Window Info</button>
215
- <button onClick={handleRenameWindow}>Rename Window</button>
216
- <button onClick={handleResizeWindow}>Resize Window</button>
217
- {windowSupport?.center && <button onClick={handleCenterWindow}>Center Window</button>}
218
- {windowSupport?.maximize && (
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
- {windowSupport?.minimize && (
225
- <button onClick={handleMinimizeWindow}>Minimize Window</button>
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
- </div>
252
+ </section>
228
253
 
229
- {!showsAdvancedWindowActions && windowSupport && (
230
- <p className="result">
231
- This runtime currently supports title and size updates only. Unsupported window actions
232
- are hidden automatically based on native support metadata.
233
- </p>
234
- )}
235
- </div>
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
  };