@dormer/cli 0.0.0-stage → 0.1.3

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 +201 -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 +57 -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,202 @@
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
+ **Continuous publishing.** `.github/workflows/publish-npm.yml` publishes on every push to `main` or
28
+ `macos-bringup` that passes the tests (`cargo test`, `npm test`, the template e2e): the repository keeps
29
+ `0.<minor>.0` as its development version and each run publishes `0.<minor>.<run number>`
30
+ (`scripts/set-version.mjs` rewrites every package, template and internal dependency at publish time), so
31
+ `npm create dormer` installs the latest green commit. Binaries are built per platform in the same run.
32
+ It needs the `NPM_TOKEN` repository secret (granular token for the `dormer` org with 2FA bypass).
33
+
34
+ ## App layout and `dormer.config.json`
35
+
36
+ ```
37
+ my-app/
38
+ dormer.config.json
39
+ src/main.ts backend (Node sidecar), uses @dormer/api
40
+ renderer/ Vite project (index.html, vite.config.ts with @dormer/vite-plugin, src/)
41
+ dist/ owned by `dormer build`: renderer/, main.js(.map), dormer.config.json,
42
+ dormer-app.json, bundle.json (after `dormer package`)
43
+ .dormer/dev/main.js backend bundle used by `dormer dev`
44
+ ```
45
+
46
+ ```jsonc
47
+ {
48
+ "name": "my-app", // required: app and package name
49
+ "identifier": "com.example.my-app", // required: reverse-DNS bundle identifier
50
+ "version": "0.1.0", // required: semver
51
+ "description": "…",
52
+ "entry": "src/main.ts", // backend entry (default)
53
+ "renderer": "renderer", // Vite root (default)
54
+ "windows": { "main": { "capabilities": ["app:default"] } }, // per-window capability sets (ADR-0006)
55
+ "firstWindow": { "label": "main", "width": 960, "height": 640, "backgroundColor": "auto" }, // host creates it before Node is ready (RISKS T22); "auto" = system light/dark
56
+ "csp": { "connectSrc": ["https://api.example.com"], "scriptSrc": ["'wasm-unsafe-eval'"], "workerSrc": ["blob:"] }, // page-side network, WebAssembly, blob workers: off by default
57
+ "node": { "version": "24", "binary": "/opt/node24/bin/node", // version pin; binary used by `package`
58
+ "icu": "small", "compileCache": true }, // icu "full" ships locale data for a small-icu Dormer Node (~31 MB); compile cache: default on when packaged
59
+ "backend": { "external": ["some-native-addon"] }, // extra bundling externals
60
+ "dev": { "port": 5173, "passEnv": ["MY_API_KEY"] },
61
+ "bundle": {
62
+ "targets": ["deb", "appimage"],
63
+ "icons": ["icons/icon.png"],
64
+ "sourceMaps": false, // ship *.map from dist/ (default false)
65
+ "linux": { "depends": ["libwebkit2gtk-4.1-0", "libgtk-3-0"] },
66
+ "macos": { "entitlements": "entitlements.plist", "signingIdentity": "Developer ID Application: …", "minimumSystemVersion": "13.0" },
67
+ "windows": { "signCommand": "signtool sign /fd sha256 /a %1" }
68
+ }
69
+ }
70
+ ```
71
+
72
+ Commands run from any directory inside the app (the nearest `dormer.config.json` wins). Worked answers to
73
+ "how do I…" questions (caching, server-rendered pages, which web APIs a page gets) live in `docs/RECIPES.md`
74
+ and the `examples/*-probe` and `examples/cache-recipe` directories, each with a `run.sh` that proves them.
75
+
76
+ ## Commands
77
+
78
+ ### `create`
79
+
80
+ Copies `templates/<name>` (the monorepo's `templates/` directory, or the copy shipped inside the
81
+ package), substitutes `{{name}}`/`{{identifier}}`, renames `_gitignore` and prints next steps.
82
+ Templates depend on `@dormer/api`, `@dormer/renderer`, `@dormer/vite-plugin`, `@dormer/cli` at
83
+ `0.1.0`. `--link` (monorepo only) rewrites those to `file:` paths into the checkout, including the
84
+ transitive `@dormer/node-runtime` and `@dormer/ipc`, so a template installs and builds without
85
+ anything published. Outside the monorepo, `website/public/bootstrap.sh` (served at
86
+ https://dormer-docs.vercel.app/bootstrap.sh) performs the checkout, host build and `create --link` in one
87
+ command.
88
+
89
+ ### `dev`
90
+
91
+ 1. Picks a free port (preferred: `--port`, `dev.port`, 5173) and starts Vite programmatically on
92
+ `renderer/` with `strictPort`, so the URL handed to the host is exactly the one Vite serves.
93
+ 2. Bundles the backend with esbuild in watch mode to `.dormer/dev/main.js`.
94
+ 3. Launches `dormer-host --app-dir <app root> --entry .dormer/dev/main.js --node <current node>
95
+ --name <name> --dev --dev-url <vite url> --pass-env DORMER_DEV_URL --pass-env DORMER_APP_VERSION …`.
96
+ The backend reads `DORMER_DEV_URL` and calls `win.loadURL(devUrl)` (templates do this; in
97
+ production they call `win.loadApp("index.html")`).
98
+ 4. When the backend bundle changes, the host is restarted. Closing the last window or Ctrl-C stops
99
+ Vite, the watcher and the host. Extra variables reach the sidecar through `--pass-env` / `dev.passEnv`;
100
+ `DORMER_SELFTEST` is forwarded automatically when set.
101
+
102
+ ### `build`
103
+
104
+ `vite build` for the renderer → `dist/renderer`; esbuild for the backend → `dist/main.js` (+ map,
105
+ `--minify` optional); copies `dormer.config.json`; writes `dist/dormer-app.json`:
106
+
107
+ ```json
108
+ { "manifestVersion": 1, "name": "…", "identifier": "…", "version": "…", "description": "…",
109
+ "entry": "main.js", "assets": "renderer", "node": { "version": "24" },
110
+ "externals": [], "windows": { "main": { "capabilities": ["app:default"] } },
111
+ "builtAt": "…", "cli": "0.1.0" }
112
+ ```
113
+
114
+ Backend bundling rules (see `src/backend.ts`): one ESM file, `platform: node`, target `node22`.
115
+ **`@dormer/*` packages are bundled** (they are published as TypeScript sources, so bundling is what
116
+ makes the output runnable on plain Node without type stripping). Left external and listed in
117
+ `externals`: `node:*` builtins, `*.node` files, packages that look like native addons (`gypfile`,
118
+ `binary`, `napi` fields or a dependency on `node-gyp-build`, `bindings`, `node-pre-gyp`,
119
+ `prebuild-install`, …) and names in `backend.external`. A `createRequire` banner lets bundled
120
+ CommonJS `require()` calls (for example msgpackr's optional native accelerator) resolve from
121
+ `node_modules` next to the output at runtime; when such a package is absent the libraries fall
122
+ back to pure JavaScript.
123
+
124
+ ### `package`
125
+
126
+ Builds (unless `--skip-build`), writes `dist/bundle.json`, then runs
127
+ `dormer-bundle --config dist/bundle.json --out <dir>` (the Rust tool from `tools/bundler`, found via
128
+ `DORMER_BUNDLE_BIN`, the cargo `target/{release,debug}` directory, then `PATH`). Missing binary:
129
+ instructions are printed and the exit code is 2; `--dry-run` only writes the file.
130
+
131
+ Interface of `bundle.json` (all paths absolute except `entry`, which is relative to `appDir`):
132
+
133
+ ```json
134
+ {
135
+ "name": "my-app", "identifier": "com.example.my-app", "version": "0.1.0", "description": "…",
136
+ "appDir": "/abs/my-app/dist", "entry": "main.js",
137
+ "hostBinary": "/abs/dormer-host", "nodeBinary": "/abs/node",
138
+ "targets": ["deb", "appimage"],
139
+ "icons": ["/abs/my-app/icons/icon.png"],
140
+ "linux": { "depends": ["libwebkit2gtk-4.1-0", "libgtk-3-0"] },
141
+ "macos": { "entitlements": null, "signingIdentity": null, "minimumSystemVersion": "13.0" },
142
+ "windows": { "signCommand": null }
143
+ }
144
+ ```
145
+
146
+ `appDir` contains `renderer/` (served as `dormer://app/`), `main.js`, `dormer.config.json` and
147
+ `dormer-app.json` (whose `externals` lists native packages the bundler must copy from
148
+ `node_modules`). Default targets: Linux `deb, appimage`; macOS `app, dmg`; Windows `nsis`.
149
+ `nodeBinary` defaults to the Node running the CLI (`node.binary` / `DORMER_NODE_BIN` override); a
150
+ warning is printed when its major differs from `node.version`.
151
+
152
+ ### `doctor`
153
+
154
+ Node ≥ 22; host binary resolution; Linux: `pkg-config --exists webkit2gtk-4.1` or
155
+ `ldconfig -p | grep libwebkit2gtk-4.1`, session D-Bus, display, NVIDIA hint
156
+ (`WEBKIT_DISABLE_DMABUF_RENDERER=1`), headless tools; macOS: Xcode CLT, OS version; Windows:
157
+ WebView2 Evergreen runtime registry key (`…\EdgeUpdate\Clients\{F3017226-…}\pv`). macOS and Windows
158
+ checks are **unverified** on those platforms. Prints the capability summary of `dormer.config.json`
159
+ when run inside an app. Exit 1 if any check fails.
160
+
161
+ ## Host binary resolution
162
+
163
+ 1. `DORMER_HOST_BIN`
164
+ 2. `<cargo workspace>/target/{release,debug}/dormer-host`, searching for `Cargo.toml` upward from
165
+ the app root and from the CLI's own location (`CARGO_TARGET_DIR` honoured)
166
+ 3. `node_modules/@dormer/host-<platform>-<arch>/bin/dormer-host` — the planned prebuilt package
167
+ (downloaded with checksums at install time; not published yet)
168
+
169
+ ## Exit codes
170
+
171
+ | code | meaning |
172
+ |---|---|
173
+ | 0 | success |
174
+ | 1 | build failed, host/bundler exited non-zero, doctor found a failing check |
175
+ | 2 | required external binary missing (`dormer-host`, `dormer-bundle`) |
176
+ | 64 | usage error |
177
+ | 65 | `dormer.config.json` missing or invalid |
178
+ | 66 | required input missing (template, entry, renderer directory, Node binary) |
179
+
180
+ ## Limitations
181
+
182
+ * **Dev restart model.** A backend change restarts the whole host process: windows are recreated
183
+ and renderer state is lost (Vite HMR itself keeps working between restarts). Restarting only the
184
+ Node sidecar while keeping windows needs host support (a "reload sidecar" control or restart-on-
185
+ exit with the same entry), which is on the Milestone 2/7 list.
186
+ * The templates still call `loadURL(process.env.DORMER_DEV_URL)` in dev although the host redirects
187
+ `loadApp()` to `--dev-url` itself; with a `firstWindow` the page is already loading and both calls
188
+ are no-ops, so the convention is harmless but redundant.
189
+ * `dormer package` ships the Node that runs the CLI unless `node.binary` (or `DORMER_NODE_BIN`) points
190
+ at another build, such as a small-icu Dormer build from `tools/node-build`; a published Dormer Node
191
+ dist that the CLI fetches by itself is not wired up yet.
192
+ * **Server-rendered pages (SSR)** work at the runtime level: run the framework's Node server inside the
193
+ backend, `win.loadURL("http://127.0.0.1:<port>/")`, and declare the origin in a capability
194
+ (`"origins": ["local", "http://127.0.0.1:*"]`); the page keeps the full bridge (`examples/ssr-probe`,
195
+ 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
196
+ be placed next to `dist/main.js` by the app's own build step.
197
+
198
+ ## Tests
199
+
200
+ `npm test -w @dormer/cli` runs unit tests. `npm run test:templates` (root) runs
201
+ `test/e2e-linux.sh`: creates each template with `--link`, installs, builds, runs the built app
202
+ 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
+ }