create-vidra-app 0.2.2 → 0.3.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/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.1.5";
36
+ var CLI_VERSION = "0.3.1";
37
37
  var STEP_LABEL_WIDTH = 16;
38
38
  var lime = chalk.hex("#c8f751");
39
39
  var value = chalk.hex("#e8e8ec");
@@ -1034,6 +1034,83 @@ import path6 from "path";
1034
1034
  import fs5 from "fs-extra";
1035
1035
  import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
1036
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
1037
1114
  var POLL_INTERVAL_MS = 500;
1038
1115
  var POLL_TIMEOUT_MS = 3e4;
1039
1116
  var NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
@@ -1056,7 +1133,7 @@ var startSession = async (argv, opts) => {
1056
1133
  const targetName = args["target"] || detectPlatform();
1057
1134
  const verbose = !!args["verbose"];
1058
1135
  let hotReload = opts.hotReloadDefault && !args["no-hot-reload"];
1059
- const viteUrl = process.env.VIDRA_DEV_URL || "http://localhost:5173";
1136
+ let viteUrl = process.env.VIDRA_DEV_URL || "http://localhost:5173";
1060
1137
  const target = TARGETS2[targetName];
1061
1138
  if (!target) {
1062
1139
  const supported = Object.keys(TARGETS2).join(", ");
@@ -1087,6 +1164,9 @@ var startSession = async (argv, opts) => {
1087
1164
  console.log(fixLine(blocker.fix));
1088
1165
  }
1089
1166
  }
1167
+ if (opts.vite) {
1168
+ viteUrl = await selectDevServerUrl(viteUrl);
1169
+ }
1090
1170
  const session = new DevSession(project, target, viteUrl, verbose, {
1091
1171
  vite: opts.vite,
1092
1172
  hotReload
@@ -1117,6 +1197,14 @@ var dotnetWatchEnv = (devUrl) => ({
1117
1197
  DOTNET_WATCH_SUPPRESS_EMOJIS: "1",
1118
1198
  DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER: "1"
1119
1199
  });
1200
+ var buildViteArgs = (devUrl) => [
1201
+ "run",
1202
+ "dev",
1203
+ "--",
1204
+ "--port",
1205
+ new URL(devUrl).port,
1206
+ "--strictPort"
1207
+ ];
1120
1208
  var HOST_READY_SENTINEL = "[vidra] host ready";
1121
1209
  var classifyWatchLine = (line) => {
1122
1210
  if (/waiting for a file to change/i.test(line)) return "appWaiting";
@@ -1223,7 +1311,7 @@ var DevSession = class {
1223
1311
  }
1224
1312
  startVite() {
1225
1313
  console.log(taggedRow("active", "ui", dim("starting dev server\u2026")));
1226
- const vite = spawn2(NPM_COMMAND, ["run", "dev"], {
1314
+ const vite = spawn2(NPM_COMMAND, buildViteArgs(this.viteUrl), {
1227
1315
  cwd: this.project.uiDir,
1228
1316
  stdio: ["ignore", "pipe", "pipe"],
1229
1317
  shell: process.platform === "win32"
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.2.2";
333
- var SDK_VERSION = "0.1.0";
332
+ var VIDRA_VERSION = "0.3.1";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-vidra-app",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Scaffold a new Vidra application (React + .NET MAUI)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,10 @@
1
+ using Vidra.Bridge;
2
+
3
+ namespace {{projectName}};
4
+
5
+ [JsContract("counter")]
6
+ public interface ICounterJs
7
+ {
8
+ [JsMethod("increment")]
9
+ Task<int> IncrementAsync();
10
+ }
@@ -22,7 +22,7 @@ public class MainPage : VidraPage
22
22
  {
23
23
  try
24
24
  {
25
- var count = await Bridge.CallJsAsync<int>("counter.increment");
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 { app, appWindow, browser, clipboard, notifications, vidra } from "@vidra-dev/sdk";
3
- import type { WindowInfo, WindowSupport } from "@vidra-dev/sdk";
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<Record<string, string[]> | null>(null);
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 = vidra.handle<void, number>("counter.increment", () => {
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 = vidra.on<{ updatedTypes?: string[] }>(
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
+ }