@dormer/cli 0.0.0-stage → 0.1.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.
Files changed (74) hide show
  1. package/README.md +194 -2
  2. package/bin/dormer.mjs +26 -0
  3. package/dist/cli.js +1301 -0
  4. package/dist/index.js +676 -0
  5. package/package.json +59 -4
  6. package/src/args.ts +68 -0
  7. package/src/backend.ts +142 -0
  8. package/src/cli.ts +45 -0
  9. package/src/commands/build.ts +105 -0
  10. package/src/commands/create.ts +132 -0
  11. package/src/commands/dev.ts +128 -0
  12. package/src/commands/doctor.ts +141 -0
  13. package/src/commands/inspect.ts +60 -0
  14. package/src/commands/package.ts +159 -0
  15. package/src/config.ts +151 -0
  16. package/src/host.ts +85 -0
  17. package/src/index.ts +8 -0
  18. package/src/resolve.ts +75 -0
  19. package/src/ui.ts +57 -0
  20. package/templates/react/README.md +34 -0
  21. package/templates/react/_gitignore +5 -0
  22. package/templates/react/dormer.config.json +16 -0
  23. package/templates/react/package.json +29 -0
  24. package/templates/react/renderer/index.html +12 -0
  25. package/templates/react/renderer/src/App.tsx +34 -0
  26. package/templates/react/renderer/src/main.tsx +6 -0
  27. package/templates/react/renderer/src/style.css +8 -0
  28. package/templates/react/renderer/src/vite-env.d.ts +2 -0
  29. package/templates/react/renderer/tsconfig.json +15 -0
  30. package/templates/react/renderer/vite.config.ts +7 -0
  31. package/templates/react/src/main.ts +34 -0
  32. package/templates/react/src/shared.ts +10 -0
  33. package/templates/react/tsconfig.json +15 -0
  34. package/templates/svelte/README.md +34 -0
  35. package/templates/svelte/_gitignore +5 -0
  36. package/templates/svelte/dormer.config.json +16 -0
  37. package/templates/svelte/package.json +27 -0
  38. package/templates/svelte/renderer/index.html +12 -0
  39. package/templates/svelte/renderer/src/App.svelte +32 -0
  40. package/templates/svelte/renderer/src/main.ts +5 -0
  41. package/templates/svelte/renderer/src/style.css +8 -0
  42. package/templates/svelte/renderer/src/vite-env.d.ts +2 -0
  43. package/templates/svelte/renderer/tsconfig.json +15 -0
  44. package/templates/svelte/renderer/vite.config.ts +7 -0
  45. package/templates/svelte/src/main.ts +34 -0
  46. package/templates/svelte/src/shared.ts +10 -0
  47. package/templates/svelte/tsconfig.json +15 -0
  48. package/templates/vanilla/README.md +34 -0
  49. package/templates/vanilla/_gitignore +5 -0
  50. package/templates/vanilla/dormer.config.json +16 -0
  51. package/templates/vanilla/package.json +24 -0
  52. package/templates/vanilla/renderer/index.html +12 -0
  53. package/templates/vanilla/renderer/src/main.ts +31 -0
  54. package/templates/vanilla/renderer/src/style.css +8 -0
  55. package/templates/vanilla/renderer/src/vite-env.d.ts +2 -0
  56. package/templates/vanilla/renderer/tsconfig.json +14 -0
  57. package/templates/vanilla/renderer/vite.config.ts +6 -0
  58. package/templates/vanilla/src/main.ts +34 -0
  59. package/templates/vanilla/src/shared.ts +10 -0
  60. package/templates/vanilla/tsconfig.json +15 -0
  61. package/templates/vue/README.md +34 -0
  62. package/templates/vue/_gitignore +5 -0
  63. package/templates/vue/dormer.config.json +16 -0
  64. package/templates/vue/package.json +27 -0
  65. package/templates/vue/renderer/index.html +12 -0
  66. package/templates/vue/renderer/src/App.vue +36 -0
  67. package/templates/vue/renderer/src/main.ts +5 -0
  68. package/templates/vue/renderer/src/style.css +8 -0
  69. package/templates/vue/renderer/src/vite-env.d.ts +8 -0
  70. package/templates/vue/renderer/tsconfig.json +15 -0
  71. package/templates/vue/renderer/vite.config.ts +7 -0
  72. package/templates/vue/src/main.ts +34 -0
  73. package/templates/vue/src/shared.ts +10 -0
  74. package/templates/vue/tsconfig.json +15 -0
package/README.md CHANGED
@@ -1,3 +1,195 @@
1
- # Temporary Holding Version
1
+ # @dormer/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The `dormer` command: scaffold, run, build and package Dormer apps (Node.js backend + system
4
+ WebView renderer). TypeScript executed directly by Node's type stripping (Node 22.18+/24; on Node
5
+ 22.6–22.17 the shim re-executes itself with `--experimental-strip-types`). Dependencies: `esbuild`
6
+ (backend bundling); `vite` is resolved from the user's project so their plugins share the instance.
7
+
8
+ ```
9
+ dormer create <dir> --template vanilla|react|vue|svelte [--pm npm] [--link] [--force]
10
+ dormer dev [--port <n>] [--host <name>] [--pass-env <VAR>]...
11
+ dormer build [--minify] [--no-sourcemap] [--quiet]
12
+ dormer package [--target deb|rpm|appimage|app|dmg|nsis|msi]... [--out <dir>] [--skip-build] [--dry-run]
13
+ dormer doctor
14
+ ```
15
+
16
+ Every command has `--help`. Errors carry a fix hint; `DORMER_CLI_DEBUG=1` adds stack traces.
17
+
18
+ ## Installing
19
+
20
+ `npm create dormer my-app -- --template react` (package `create-dormer`) scaffolds an app and installs
21
+ `@dormer/cli`, which carries the compiled CLI (`dist/cli.js`, built by `build.mjs` on `prepare`; Node does
22
+ not type-strip `.ts` under `node_modules`) and the templates (copied in by `prepack`). The native binaries
23
+ come as optional dependencies `@dormer/host-<platform>-<arch>` and `@dormer/bundle-<platform>-<arch>`,
24
+ generated by `scripts/npm-platform-packages.mjs`; `scripts/publish-npm.sh` publishes everything in
25
+ dependency order (rehearse against a local registry with `--registry`).
26
+
27
+ ## App layout and `dormer.config.json`
28
+
29
+ ```
30
+ my-app/
31
+ dormer.config.json
32
+ src/main.ts backend (Node sidecar), uses @dormer/api
33
+ renderer/ Vite project (index.html, vite.config.ts with @dormer/vite-plugin, src/)
34
+ dist/ owned by `dormer build`: renderer/, main.js(.map), dormer.config.json,
35
+ dormer-app.json, bundle.json (after `dormer package`)
36
+ .dormer/dev/main.js backend bundle used by `dormer dev`
37
+ ```
38
+
39
+ ```jsonc
40
+ {
41
+ "name": "my-app", // required: app and package name
42
+ "identifier": "com.example.my-app", // required: reverse-DNS bundle identifier
43
+ "version": "0.1.0", // required: semver
44
+ "description": "…",
45
+ "entry": "src/main.ts", // backend entry (default)
46
+ "renderer": "renderer", // Vite root (default)
47
+ "windows": { "main": { "capabilities": ["app:default"] } }, // per-window capability sets (ADR-0006)
48
+ "firstWindow": { "label": "main", "width": 960, "height": 640, "backgroundColor": "auto" }, // host creates it before Node is ready (RISKS T22); "auto" = system light/dark
49
+ "csp": { "connectSrc": ["https://api.example.com"], "scriptSrc": ["'wasm-unsafe-eval'"], "workerSrc": ["blob:"] }, // page-side network, WebAssembly, blob workers: off by default
50
+ "node": { "version": "24", "binary": "/opt/node24/bin/node", // version pin; binary used by `package`
51
+ "icu": "small", "compileCache": true }, // icu "full" ships locale data for a small-icu Dormer Node (~31 MB); compile cache: default on when packaged
52
+ "backend": { "external": ["some-native-addon"] }, // extra bundling externals
53
+ "dev": { "port": 5173, "passEnv": ["MY_API_KEY"] },
54
+ "bundle": {
55
+ "targets": ["deb", "appimage"],
56
+ "icons": ["icons/icon.png"],
57
+ "sourceMaps": false, // ship *.map from dist/ (default false)
58
+ "linux": { "depends": ["libwebkit2gtk-4.1-0", "libgtk-3-0"] },
59
+ "macos": { "entitlements": "entitlements.plist", "signingIdentity": "Developer ID Application: …", "minimumSystemVersion": "13.0" },
60
+ "windows": { "signCommand": "signtool sign /fd sha256 /a %1" }
61
+ }
62
+ }
63
+ ```
64
+
65
+ Commands run from any directory inside the app (the nearest `dormer.config.json` wins). Worked answers to
66
+ "how do I…" questions (caching, server-rendered pages, which web APIs a page gets) live in `docs/RECIPES.md`
67
+ and the `examples/*-probe` and `examples/cache-recipe` directories, each with a `run.sh` that proves them.
68
+
69
+ ## Commands
70
+
71
+ ### `create`
72
+
73
+ Copies `templates/<name>` (the monorepo's `templates/` directory, or the copy shipped inside the
74
+ package), substitutes `{{name}}`/`{{identifier}}`, renames `_gitignore` and prints next steps.
75
+ Templates depend on `@dormer/api`, `@dormer/renderer`, `@dormer/vite-plugin`, `@dormer/cli` at
76
+ `0.1.0`. `--link` (monorepo only) rewrites those to `file:` paths into the checkout, including the
77
+ transitive `@dormer/node-runtime` and `@dormer/ipc`, so a template installs and builds without
78
+ anything published. Outside the monorepo, `website/public/bootstrap.sh` (served at
79
+ https://dormer-docs.vercel.app/bootstrap.sh) performs the checkout, host build and `create --link` in one
80
+ command.
81
+
82
+ ### `dev`
83
+
84
+ 1. Picks a free port (preferred: `--port`, `dev.port`, 5173) and starts Vite programmatically on
85
+ `renderer/` with `strictPort`, so the URL handed to the host is exactly the one Vite serves.
86
+ 2. Bundles the backend with esbuild in watch mode to `.dormer/dev/main.js`.
87
+ 3. Launches `dormer-host --app-dir <app root> --entry .dormer/dev/main.js --node <current node>
88
+ --name <name> --dev --dev-url <vite url> --pass-env DORMER_DEV_URL --pass-env DORMER_APP_VERSION …`.
89
+ The backend reads `DORMER_DEV_URL` and calls `win.loadURL(devUrl)` (templates do this; in
90
+ production they call `win.loadApp("index.html")`).
91
+ 4. When the backend bundle changes, the host is restarted. Closing the last window or Ctrl-C stops
92
+ Vite, the watcher and the host. Extra variables reach the sidecar through `--pass-env` / `dev.passEnv`;
93
+ `DORMER_SELFTEST` is forwarded automatically when set.
94
+
95
+ ### `build`
96
+
97
+ `vite build` for the renderer → `dist/renderer`; esbuild for the backend → `dist/main.js` (+ map,
98
+ `--minify` optional); copies `dormer.config.json`; writes `dist/dormer-app.json`:
99
+
100
+ ```json
101
+ { "manifestVersion": 1, "name": "…", "identifier": "…", "version": "…", "description": "…",
102
+ "entry": "main.js", "assets": "renderer", "node": { "version": "24" },
103
+ "externals": [], "windows": { "main": { "capabilities": ["app:default"] } },
104
+ "builtAt": "…", "cli": "0.1.0" }
105
+ ```
106
+
107
+ Backend bundling rules (see `src/backend.ts`): one ESM file, `platform: node`, target `node22`.
108
+ **`@dormer/*` packages are bundled** (they are published as TypeScript sources, so bundling is what
109
+ makes the output runnable on plain Node without type stripping). Left external and listed in
110
+ `externals`: `node:*` builtins, `*.node` files, packages that look like native addons (`gypfile`,
111
+ `binary`, `napi` fields or a dependency on `node-gyp-build`, `bindings`, `node-pre-gyp`,
112
+ `prebuild-install`, …) and names in `backend.external`. A `createRequire` banner lets bundled
113
+ CommonJS `require()` calls (for example msgpackr's optional native accelerator) resolve from
114
+ `node_modules` next to the output at runtime; when such a package is absent the libraries fall
115
+ back to pure JavaScript.
116
+
117
+ ### `package`
118
+
119
+ Builds (unless `--skip-build`), writes `dist/bundle.json`, then runs
120
+ `dormer-bundle --config dist/bundle.json --out <dir>` (the Rust tool from `tools/bundler`, found via
121
+ `DORMER_BUNDLE_BIN`, the cargo `target/{release,debug}` directory, then `PATH`). Missing binary:
122
+ instructions are printed and the exit code is 2; `--dry-run` only writes the file.
123
+
124
+ Interface of `bundle.json` (all paths absolute except `entry`, which is relative to `appDir`):
125
+
126
+ ```json
127
+ {
128
+ "name": "my-app", "identifier": "com.example.my-app", "version": "0.1.0", "description": "…",
129
+ "appDir": "/abs/my-app/dist", "entry": "main.js",
130
+ "hostBinary": "/abs/dormer-host", "nodeBinary": "/abs/node",
131
+ "targets": ["deb", "appimage"],
132
+ "icons": ["/abs/my-app/icons/icon.png"],
133
+ "linux": { "depends": ["libwebkit2gtk-4.1-0", "libgtk-3-0"] },
134
+ "macos": { "entitlements": null, "signingIdentity": null, "minimumSystemVersion": "13.0" },
135
+ "windows": { "signCommand": null }
136
+ }
137
+ ```
138
+
139
+ `appDir` contains `renderer/` (served as `dormer://app/`), `main.js`, `dormer.config.json` and
140
+ `dormer-app.json` (whose `externals` lists native packages the bundler must copy from
141
+ `node_modules`). Default targets: Linux `deb, appimage`; macOS `app, dmg`; Windows `nsis`.
142
+ `nodeBinary` defaults to the Node running the CLI (`node.binary` / `DORMER_NODE_BIN` override); a
143
+ warning is printed when its major differs from `node.version`.
144
+
145
+ ### `doctor`
146
+
147
+ Node ≥ 22; host binary resolution; Linux: `pkg-config --exists webkit2gtk-4.1` or
148
+ `ldconfig -p | grep libwebkit2gtk-4.1`, session D-Bus, display, NVIDIA hint
149
+ (`WEBKIT_DISABLE_DMABUF_RENDERER=1`), headless tools; macOS: Xcode CLT, OS version; Windows:
150
+ WebView2 Evergreen runtime registry key (`…\EdgeUpdate\Clients\{F3017226-…}\pv`). macOS and Windows
151
+ checks are **unverified** on those platforms. Prints the capability summary of `dormer.config.json`
152
+ when run inside an app. Exit 1 if any check fails.
153
+
154
+ ## Host binary resolution
155
+
156
+ 1. `DORMER_HOST_BIN`
157
+ 2. `<cargo workspace>/target/{release,debug}/dormer-host`, searching for `Cargo.toml` upward from
158
+ the app root and from the CLI's own location (`CARGO_TARGET_DIR` honoured)
159
+ 3. `node_modules/@dormer/host-<platform>-<arch>/bin/dormer-host` — the planned prebuilt package
160
+ (downloaded with checksums at install time; not published yet)
161
+
162
+ ## Exit codes
163
+
164
+ | code | meaning |
165
+ |---|---|
166
+ | 0 | success |
167
+ | 1 | build failed, host/bundler exited non-zero, doctor found a failing check |
168
+ | 2 | required external binary missing (`dormer-host`, `dormer-bundle`) |
169
+ | 64 | usage error |
170
+ | 65 | `dormer.config.json` missing or invalid |
171
+ | 66 | required input missing (template, entry, renderer directory, Node binary) |
172
+
173
+ ## Limitations
174
+
175
+ * **Dev restart model.** A backend change restarts the whole host process: windows are recreated
176
+ and renderer state is lost (Vite HMR itself keeps working between restarts). Restarting only the
177
+ Node sidecar while keeping windows needs host support (a "reload sidecar" control or restart-on-
178
+ exit with the same entry), which is on the Milestone 2/7 list.
179
+ * The templates still call `loadURL(process.env.DORMER_DEV_URL)` in dev although the host redirects
180
+ `loadApp()` to `--dev-url` itself; with a `firstWindow` the page is already loading and both calls
181
+ are no-ops, so the convention is harmless but redundant.
182
+ * `dormer package` ships the Node that runs the CLI unless `node.binary` (or `DORMER_NODE_BIN`) points
183
+ at another build, such as a small-icu Dormer build from `tools/node-build`; a published Dormer Node
184
+ dist that the CLI fetches by itself is not wired up yet.
185
+ * **Server-rendered pages (SSR)** work at the runtime level: run the framework's Node server inside the
186
+ backend, `win.loadURL("http://127.0.0.1:<port>/")`, and declare the origin in a capability
187
+ (`"origins": ["local", "http://127.0.0.1:*"]`); the page keeps the full bridge (`examples/ssr-probe`,
188
+ verified 2026-11-08 with plain HTML and with React 19 `renderToPipeableStream` + `hydrateRoot`). `dormer build` only knows the Vite single-page renderer, so a server build has to
189
+ be placed next to `dist/main.js` by the app's own build step.
190
+
191
+ ## Tests
192
+
193
+ `npm test -w @dormer/cli` runs unit tests. `npm run test:templates` (root) runs
194
+ `test/e2e-linux.sh`: creates each template with `--link`, installs, builds, runs the built app
195
+ headless (Xvfb + session D-Bus) with `DORMER_SELFTEST=1`, and exercises `dormer dev` once.
package/bin/dormer.mjs ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ // Entry shim: the CLI is TypeScript executed by Node's built-in type stripping (Node 22.18+ / 24+).
3
+ // On Node 22.6–22.17 type stripping exists behind a flag, so re-execute with it once.
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const [major, minor] = process.versions.node.split(".").map(Number);
8
+ if (major < 22 || (major === 22 && minor < 6)) {
9
+ console.error(`dormer needs Node.js 22.6 or newer (22.18+/24 recommended); found ${process.version}`);
10
+ process.exit(1);
11
+ }
12
+ // A published copy (installed under node_modules) runs the compiled CLI, dist/cli.js (see build.mjs); a
13
+ // source checkout keeps running src/cli.ts so edits take effect without a rebuild.
14
+ import { existsSync } from "node:fs";
15
+ const dist = new URL("../dist/cli.js", import.meta.url);
16
+ const installed = /[\\/]node_modules[\\/]/.test(fileURLToPath(import.meta.url));
17
+ if (installed && existsSync(dist)) { await import(dist.href); }
18
+ else {
19
+ if (!process.features?.typescript && !process.env.DORMER_CLI_REEXEC) {
20
+ const r = spawnSync(process.execPath, ["--experimental-strip-types", "--no-warnings", fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
21
+ stdio: "inherit", env: { ...process.env, DORMER_CLI_REEXEC: "1" },
22
+ });
23
+ process.exit(r.status ?? 1);
24
+ }
25
+ await import("../src/cli.ts");
26
+ }