@ubean/cli 0.6.2 → 0.6.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 (4) hide show
  1. package/README.md +676 -0
  2. package/README.zh-CN.md +635 -0
  3. package/dist/cli.js +13 -142
  4. package/package.json +14 -14
package/README.md ADDED
@@ -0,0 +1,676 @@
1
+ # @ubean/cli
2
+
3
+ **English | [中文](./README.zh-CN.md)**
4
+
5
+ The ubean command line: project lifecycle commands, scaffolding, `.env` and config management, plus the side-effect-free library surface for programmatic scaffolding.
6
+
7
+ ```bash
8
+ pnpm add -D ubean @ubean/cli
9
+ pnpm exec ubean dev
10
+ ```
11
+
12
+ ## Table of Contents
13
+
14
+ - [Package Positioning](#package-positioning)
15
+ - [Install](#install)
16
+ - [Quick Start](#quick-start)
17
+ - [Library vs Executable Entry](#library-vs-executable-entry)
18
+ - [Commands](#commands)
19
+ - [`ubean init`](#ubean-init)
20
+ - [`ubean dev`](#ubean-dev)
21
+ - [`ubean build`](#ubean-build)
22
+ - [`ubean preview`](#ubean-preview)
23
+ - [`ubean prepare`](#ubean-prepare)
24
+ - [`ubean analyze`](#ubean-analyze)
25
+ - [`ubean env`](#ubean-env)
26
+ - [`ubean config`](#ubean-config)
27
+ - [`ubean devtools`](#ubean-devtools)
28
+ - [Scaffolding](#scaffolding)
29
+ - [`ubean page`](#ubean-page)
30
+ - [`ubean api` / `layout` / `middleware` / `cron` / `plugin`](#ubean-api--layout--middleware--cron--plugin)
31
+ - [`ubean scaffold`](#ubean-scaffold)
32
+ - [Library API](#library-api)
33
+ - [Scaffold Contract](#scaffold-contract)
34
+ - [Bundle Budget Helpers](#bundle-budget-helpers)
35
+ - [Global Flags](#global-flags)
36
+ - [Type Safety](#type-safety)
37
+ - [Architecture Notes](#architecture-notes)
38
+
39
+ ## Package Positioning
40
+
41
+ `@ubean/cli` owns the **engineering commands** — the parts that are not lifecycle orchestration. The
42
+ dev / build / preview lifecycles themselves live in `@ubean/build`'s Vite plugin (ADR-0012), and the
43
+ CLI commands are thin aliases over `vite dev | build | preview`.
44
+
45
+ | Responsible for | NOT responsible for |
46
+ | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
47
+ | The executable: `dev` / `build` / `preview` / `prepare` / `init` / `page` / `env` / `config` / `devtools` / `analyze` | Dev request routing, dev app bootstrap, build orchestration, preview takeover — `@ubean/build` |
48
+ | Scaffolding (`scaffold` / `deleteScaffold` / `recoverScaffold` / `listScaffoldableFiles`) + file templates | Page/layout scanning and route metadata — `@ubean/scan` / `@ubean/vue` |
49
+ | The machine-readable scaffold catalog (`getScaffoldManifest`, `SCAFFOLD_CONTRACT_VERSION`) | Config shape and resolution — `@ubean/config` |
50
+ | Bundle-budget reporting (`ubean analyze`) and its baseline helpers | The build that produces the manifest — `@ubean/build` |
51
+ | `.env` file read/write and `ubean.config.ts` creation/display | Deployment platform presets — `@ubean/preset` |
52
+ | The `env-runner`-backed dev worker entry wiring | The worker entry source itself — `@ubean/build/vite` |
53
+
54
+ **Runtime dependency whitelist (main entry `@ubean/cli`):** `citty` (command parsing), `kolorist`
55
+ (colors), `pathe` (paths), plus `@ubean/shared` for the logger. The main entry deliberately does
56
+ **not** import Vite, the plugin, or the command implementations — it is a library surface for
57
+ scaffold / fs-ops / templates, and importing it has **no side effects**.
58
+
59
+ Optional peer dependencies: `@ubean/content`, `@ubean/devtools`, `@vitejs/plugin-vue`, `vite`. When
60
+ they are absent, the corresponding capability degrades (content collections are not
61
+ bootstrapped, DevTools is not mounted) rather than failing — but the plain Vite commands require
62
+ `vite` itself, and `vite dev` needs `@vitejs/plugin-vue` unless your config brings its own.
63
+
64
+ `env-runner` lives **only here** and must not be added as a dependency of `@ubean/build` — it triggers
65
+ pnpm peer-variant drift, which surfaces as `Plugin` type identity mismatches.
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pnpm add -D ubean @ubean/cli
71
+ ```
72
+
73
+ The `ubean` aggregator package depends on `@ubean/cli` and ships the `ubean` binary, which forwards
74
+ to `@ubean/cli/cli`. Installing the aggregator is enough — `@ubean/cli` is listed separately here only
75
+ for consumers that want the binary, or the library surface, without the framework.
76
+
77
+ The `@ubean/cli` package's own `bin` entry is temporarily named **`ubean-next`**; the user-facing
78
+ command is `ubean`.
79
+
80
+ ## Quick Start
81
+
82
+ ```bash
83
+ ubean init my-app --name my-app --template starter --preset node -y
84
+ cd my-app
85
+ pnpm install
86
+ pnpm dev
87
+ ```
88
+
89
+ Two conventions to know before running anything:
90
+
91
+ - Options accept `--flag value` and `--flag=value`; booleans can be negated with the `--no-` prefix
92
+ (`--no-ssr`, `--no-install`, `--no-write`).
93
+ - **Always spell long-form option names.** Only `-f`, `-y`, and `--pm` have short forms. citty reads
94
+ `init -n blog -t blog` as `{ n: true, t: true, _: ["blog", "blog"] }` with `template` / `preset` /
95
+ `name` all `undefined` (they fall back to `unify` / `standard` / the directory name), and reads
96
+ `-pm pnpm` as `-p -m pnpm`, silently leaving the package manager on its default. Write `--name` /
97
+ `--template` / `--preset` / `--pm`.
98
+
99
+ ## Library vs Executable Entry
100
+
101
+ The package has two subpaths, and picking the wrong one has a visible consequence.
102
+
103
+ | Subpath | What it is | Side effects |
104
+ | ------------------ | -------------------------------------------------------------------------------- | --------------------------- |
105
+ | `@ubean/cli` (`.`) | Library surface: scaffold, fs-ops, templates, analyze helpers, scaffold manifest | **None** — import it freely |
106
+ | `@ubean/cli/cli` | The executable entry; **importing it runs `runMain(main)` at module top level** | Starts the CLI |
107
+
108
+ `bin/ubean-next.mjs` is a two-line forwarder — `import '../dist/cli.mjs';` — because a bare import of
109
+ the executable entry is all that is required.
110
+
111
+ ```ts
112
+ // ✓ import the library entry from your own tooling
113
+ import { scaffold, getScaffoldManifest, createFsOps } from '@ubean/cli';
114
+
115
+ // ✗ never do this in library code — it launches the CLI
116
+ // import '@ubean/cli/cli';
117
+ ```
118
+
119
+ ## Commands
120
+
121
+ ### `ubean init`
122
+
123
+ Initialize a new ubean project.
124
+
125
+ ```bash
126
+ ubean init [dir]
127
+ ```
128
+
129
+ | Argument | Description | Default |
130
+ | -------- | ----------------------- | ------- |
131
+ | `[dir]` | Directory to initialize | `.` |
132
+
133
+ | Option | Alias | Description | Default |
134
+ | ------------------ | ------ | ---------------------------------------------- | ---------- |
135
+ | `--force` | `-f` | Overwrite existing files | `false` |
136
+ | `--name` | — | Project name | — |
137
+ | `--template` | — | Project template (unify/minimal/starter/blog) | `unify` |
138
+ | `--preset` | — | Target preset (standard/node/cloudflare/…) | `standard` |
139
+ | `--packageManager` | `--pm` | Package manager (npm/pnpm/yarn/bun) | `npm` |
140
+ | `--git` | — | Initialize git repository (`--no-git` to skip) | — |
141
+ | `--yes` | `-y` | Skip prompts and use defaults | `false` |
142
+
143
+ Templates: `unify` (full-stack with islands/i18n/layouts/middleware — the default), `minimal`
144
+ (hello world page), `starter` (pages/api/layouts), `blog` (markdown blog structure).
145
+
146
+ The interactive preset menu offers three choices — `standard` (default), `node`, `cloudflare`. All
147
+ other platform presets must be passed explicitly: `cloudflare-dev`, `vercel`, `vercel-edge`,
148
+ `netlify`, `bun`, `deno`, `aws`, `azure` (11 presets in total).
149
+
150
+ Behavior notes: without `--yes` and in a TTY, the command prompts for name, template, preset, package
151
+ manager, and git initialization. An invalid `--packageManager` falls back to `npm`. Existing files are
152
+ only overwritten with `--force`; in non-interactive mode a non-empty directory aborts unless `--force`
153
+ is passed. Non-interactive mode is forced by `--yes`, a non-TTY stdin, or the `CI` / `NONINTERACTIVE`
154
+ environment variables.
155
+
156
+ ```bash
157
+ # Non-interactive with explicit choices
158
+ ubean init my-app --name my-app --template starter --preset node -y
159
+
160
+ # Use a preset outside the interactive menu
161
+ ubean init my-app --preset vercel-edge -y
162
+
163
+ # Skip git initialization
164
+ ubean init my-app --no-git
165
+ ```
166
+
167
+ ### `ubean dev`
168
+
169
+ Start the development server with hot reload.
170
+
171
+ ```bash
172
+ ubean dev
173
+ ```
174
+
175
+ | Option | Description | Default |
176
+ | --------------- | ---------------------------------------------------------------- | ----------- |
177
+ | `--port` | Port to listen on | `9527` |
178
+ | `--host` | Host to listen on | `localhost` |
179
+ | `--strictPort` | Exit if the port is already in use, instead of auto-incrementing | `false` |
180
+ | `--verbose` | Show detailed startup logs (scan, codegen, diagnostics, reloads) | — |
181
+ | `--logRequests` | Log each request (`GET /path 200 12ms`); ignored in ssg/spa | — |
182
+ | `--open` | Open browser on startup | `false` |
183
+ | `--cwd` | Project root directory | `.` |
184
+
185
+ There is no `--https` option and no `-p` short alias. `--port` / `--host` override the `dev.port` /
186
+ `dev.host` fields in `ubean.config.ts`.
187
+
188
+ Behavior: the project is scanned, type definitions are generated into `.ubean/`, and the dev server
189
+ starts. Watched directories are `api`, `pages`, `middleware`, `layouts`, `plugins`, `app`, and
190
+ `routes` under `srcDir`; a change triggers a rescan and a reload. In fullstack/backend modes the
191
+ banner lists the Scalar UI (`/_scalar`) and OpenAPI schema (`/_openapi.json`) URLs, and OpenAPI types
192
+ are generated asynchronously into `.ubean/`.
193
+
194
+ In `ssg` mode, security response headers (CSP/HSTS/…) are disabled by default in dev — static output
195
+ ships no framework-injected headers (the hosting platform controls them), so dev mirrors that reality.
196
+ Explicitly setting `security.headers` in `ubean.config.ts` re-enables them as a CSP debugging console.
197
+ If the DevTools module is enabled, its URL appears in the banner.
198
+
199
+ ```bash
200
+ ubean dev --port 3000 --host 0.0.0.0
201
+ ubean dev --strictPort
202
+ ```
203
+
204
+ ### `ubean build`
205
+
206
+ Build the application for production.
207
+
208
+ ```bash
209
+ ubean build
210
+ ```
211
+
212
+ | Option | Description | Default |
213
+ | ------------- | -------------------------------------------------------------------------------------------- | ------- |
214
+ | `--preset` | Deployment preset (node, bun, deno, cloudflare, vercel, netlify, standard, ...) | — |
215
+ | `--mode` | App mode (fullstack, spa, ssg, backend); overrides `ubean.config.ts` | — |
216
+ | `--ssr` | Enable SSR in fullstack mode (`--no-ssr` to disable); only effective with `--mode fullstack` | — |
217
+ | `--ssg` | Shortcut for `--mode ssg` | `false` |
218
+ | `--outDir` | Output directory (overrides `ubean.config.ts` `build.outputDir`) | — |
219
+ | `--minify` | Minify output (`--no-minify` to disable) | `true` |
220
+ | `--sourcemap` | Generate sourcemaps | `false` |
221
+ | `--prerender` | Pre-render static pages (`--no-prerender` to disable) | — |
222
+ | `--cwd` | Project root directory | `.` |
223
+
224
+ There is no `--clean` option. `--preset` has no CLI default — it falls back to `build.preset` in
225
+ `ubean.config.ts`, which defaults to `node`.
226
+
227
+ `--mode ssg` (or `--ssg`) enables full prerendering via the **direct render path**: a minimal static
228
+ bundle renders each route without the Hono pipeline. `pages/404.vue` renders to `404.html` for static
229
+ hosts; i18n routes are expanded per strategy (e.g. `/about` + `/zh/about`). The temporary
230
+ `dist/server` bundle is removed afterwards — set `UBEAN_KEEP_SSR` to keep it. Page `loader` is not
231
+ executed in this mode (one-time warning); use fullstack prerendering when loaders are required.
232
+
233
+ `spa` and `backend` modes disable prerendering; fullstack with `ssr: false` also disables it.
234
+ `--prerender` / `--no-prerender` override the resolved value. Preset `build:before` / `build:after`
235
+ hooks are executed.
236
+
237
+ ```bash
238
+ ubean build --preset cloudflare
239
+ ubean build --ssg
240
+ ubean build --no-ssr --sourcemap
241
+ ```
242
+
243
+ `ubean build` ≡ `vite build`, with one difference: `vite build` **ignores `--outDir`** and always
244
+ writes `config.build.outputDir`.
245
+
246
+ ### `ubean preview`
247
+
248
+ Preview the production build locally. Run `ubean build` first.
249
+
250
+ ```bash
251
+ ubean preview
252
+ ```
253
+
254
+ | Option | Description | Default |
255
+ | -------------- | ---------------------------------------------------------------- | ----------- |
256
+ | `--port` | Port to listen on | `9725` |
257
+ | `--host` | Host to listen on | `localhost` |
258
+ | `--strictPort` | Exit if the port is already in use, instead of auto-incrementing | `false` |
259
+ | `--outDir` | Output directory to preview (overrides `build.outputDir`) | — |
260
+ | `--cwd` | Project root directory | `.` |
261
+
262
+ There is no `-p` short alias. `--port` / `--host` override `preview.port` / `preview.host` (defaults
263
+ `9725` / `localhost`).
264
+
265
+ `spa` / `ssg` modes serve `dist/public/` with a built-in static file server (SPA falls back to
266
+ `index.html`; SSG resolves `<path>/index.html` or `<path>.html`). `fullstack` / `backend` modes spawn
267
+ the built `dist/server/server.mjs` with `PORT` and `HOST` environment variables. The `cloudflare`
268
+ preset is not supported for local preview — use `wrangler dev` instead; that platform preview is only
269
+ wired on the `ubean preview` side (via miniflare). Other fetch-handler presets run `handler.mjs` with
270
+ a warning. When the requested port is busy, the next free port is used unless `--strictPort` is set.
271
+
272
+ ### `ubean prepare`
273
+
274
+ Generate type definitions and check project dependencies.
275
+
276
+ ```bash
277
+ ubean prepare
278
+ ```
279
+
280
+ | Option | Description | Default |
281
+ | ----------- | ---------------------------------------------------------------------------- | ------- |
282
+ | `--cwd` | Project root directory | `.` |
283
+ | `--install` | Prompt to install missing project dependencies (`--no-install` to only warn) | `true` |
284
+
285
+ The project is scanned and type definitions are generated into `.ubean/`. The dependency check
286
+ compares the resolved config against what the project must declare:
287
+
288
+ - **required** — core packages (`vue`, `vue-router`, `vue-i18n`), the packages behind every enabled
289
+ built-in module (`ui: true` → `@ubean/integrations` + `@vean/ui`), and those modules' required peers;
290
+ - **optional** — feature-gated packages (`@mdx-js/mdx` for MDX, `satori` + `@resvg/resvg-js` when an
291
+ OG-image convention file exists, `pagefind` for content search, `miniflare` for `cloudflare-dev`,
292
+ `nodemailer` for email).
293
+
294
+ Each missing entry names who requires it and why (`required by \`ui\``), followed by a copy-pasteable
295
+ install command. Build-time peers go to `devDependencies`, runtime peers to `dependencies`. On an
296
+ interactive terminal it asks before installing, using the project's package manager (detected from
297
+ lockfiles: pnpm > yarn > bun > npm), with `@ubean/*`packages pinned to`@^<ubean-version>`.
298
+
299
+ The same check also runs from the Vite plugin's `config` hook, so bare `vite dev` / `vite build`
300
+ (which never call `prepare`) are covered too. Set `UBEAN_SKIP_DEP_CHECK=1` to disable it. There is no
301
+ `--force` flag; missing dependencies and install failures are non-fatal warnings. `--no-install` skips
302
+ auto-installation only — the warning is still printed, which is the right behaviour in CI.
303
+
304
+ ### `ubean analyze`
305
+
306
+ Report the client JS budget from the Vite client manifest. Run after `ubean build`.
307
+
308
+ ```bash
309
+ ubean analyze
310
+ ```
311
+
312
+ | Option | Description | Default |
313
+ | --------------- | ------------------------------------------------------------------------------------- | ----------------------------- |
314
+ | `--cwd` | Project root | `process.cwd()` |
315
+ | `--write` | Write `.ubean/bundle-baseline.json` (`--no-write` to disable) | `true` |
316
+ | `--out` | Baseline JSON output path | `.ubean/bundle-baseline.json` |
317
+ | `--check` | Committed baseline JSON to compare against (fails if gzip grows past `--maxIncrease`) | — |
318
+ | `--maxIncrease` | Allowed relative gzip growth when using `--check` | `0.05` |
319
+
320
+ Requires a client Vite manifest; it errors out (exit code 1) if no build output is found. It reports
321
+ total and entry gzip sizes plus the largest JS chunks. `--check` compares against a committed baseline
322
+ and fails when total or entry gzip size exceeds the allowed relative increase.
323
+
324
+ ```bash
325
+ ubean build && ubean analyze
326
+ ubean analyze --no-write
327
+ ubean analyze --check benchmarks/bundle-baseline.json --no-write
328
+ ```
329
+
330
+ ### `ubean env`
331
+
332
+ Manage `.env` files.
333
+
334
+ ```bash
335
+ ubean env <add|remove|list|init>
336
+ ```
337
+
338
+ | Subcommand | Arguments | Options |
339
+ | ------------ | --------------- | ------------------------------------------------------------ |
340
+ | `env add` | `<key> <value>` | `--file` (default `.env`), `--public`, `-f/--force`, `--dry` |
341
+ | `env remove` | `<key>` | `--file`, `--dry` |
342
+ | `env list` | — | `--file`, `--public` (show only public variables) |
343
+ | `env init` | — | `-f/--force`, `--dry` |
344
+
345
+ `--public` prefixes the key with `UBEAN_PUBLIC_` unless it already starts with `UBEAN_PUBLIC_` /
346
+ `VITE_` / `PUBLIC_`. `env list` marks keys with those prefixes as `[public]`. `env init` creates
347
+ `.env` and `.env.example` from a template.
348
+
349
+ ```bash
350
+ ubean env init
351
+ ubean env add DATABASE_URL postgres://localhost/app
352
+ ubean env add API_URL https://api.example.com --public
353
+ ubean env list --public
354
+ ```
355
+
356
+ ### `ubean config`
357
+
358
+ Manage `ubean.config.ts`.
359
+
360
+ ```bash
361
+ ubean config <init|show|example|path>
362
+ ```
363
+
364
+ | Subcommand | Options | Description |
365
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
366
+ | `config init` | `--preset` (default `standard`; any registered preset name — `vercel-edge`, `aws`, `azure`, …), `-f/--force`, `--dry` | Create a default `ubean.config.ts` |
367
+ | `config show` | — | Show the current config file location/content |
368
+ | `config example` | — | Show a full example configuration |
369
+ | `config path` | — | Print the resolved config path (exit 1 if none) |
370
+
371
+ There are no `--json` or `--env` flags.
372
+
373
+ ```bash
374
+ ubean config init --preset node
375
+ ubean config init --dry
376
+ ubean config path
377
+ ```
378
+
379
+ ### `ubean devtools`
380
+
381
+ Manage DevTools configuration and information. This command neither opens a browser nor starts a
382
+ server — DevTools itself runs inside the dev server.
383
+
384
+ ```bash
385
+ ubean devtools <info|path>
386
+ ```
387
+
388
+ | Subcommand | Options | Description |
389
+ | --------------- | ------------------------- | ----------------------------------------------------------- |
390
+ | `devtools info` | — | Show DevTools information (access, config, features) |
391
+ | `devtools path` | `--port` (default `9527`) | Print the DevTools URL: `http://localhost:<port>/_devtools` |
392
+
393
+ ```bash
394
+ ubean devtools path
395
+ ubean devtools path --port 3000
396
+ ```
397
+
398
+ In the browser, DevTools opens by visiting the URL from the dev server banner (default `/_devtools`)
399
+ while the dev server runs; it redirects to the devframe hub shell, which hosts the panel as an iframe
400
+ devframe.
401
+
402
+ ## Scaffolding
403
+
404
+ Scaffold commands create, delete, recover, and list files. Shared conventions:
405
+
406
+ | Type | Base directory | Extension |
407
+ | ------------ | ----------------- | -------------------- |
408
+ | page / reuse | `src/pages/` | `.vue` / `.reuse.ts` |
409
+ | api | `src/routes/` | `.ts` |
410
+ | layout | `src/layouts/` | `.vue` |
411
+ | middleware | `src/middleware/` | `.ts` |
412
+ | cron | `src/crons/` | `.ts` |
413
+ | plugin | `src/plugins/` | `.ts` |
414
+
415
+ Cron files land in `src/crons/`, matching the scanner default (`DEFAULT_DIRS.crons = 'crons'`).
416
+ Numeric filename prefixes control registration order (`01.cleanup.ts`).
417
+
418
+ Shared semantics:
419
+
420
+ - `add` skips existing files unless `-f/--force` is passed (a `.bak` backup is created before
421
+ overwriting).
422
+ - `delete` creates a `.bak` backup by default; `-f/--force` deletes permanently without backup.
423
+ - `recovery` restores the most recent `.bak` backup; `--dry` only checks whether recovery is possible.
424
+ - `--dry` prints the action without writing files.
425
+ - Component names derive from the route path: dynamic segments become PascalCase (`[id]` → `Id`),
426
+ `[...slug]` → `AllSlug`, `[[page]]` → `PageOptional`.
427
+
428
+ ### `ubean page`
429
+
430
+ Generic scaffold entry for all types.
431
+
432
+ ```bash
433
+ ubean page <add|add-reuse|delete|recovery|list>
434
+ ```
435
+
436
+ | Subcommand | Argument | Options |
437
+ | ----------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
438
+ | `page add <path>` | route path (e.g. `users/[id]`, `api/users`, `crons/daily`) | `--type` (page/api/layout/middleware/cron/plugin, default `page`), `--method` (default `GET`), `--schedule` (default `* * * * *`), `-f/--force`, `--dry` |
439
+ | `page add-reuse <path>` | route path | `-f/--force`, `--dry` |
440
+ | `page delete <path>` | route path | `--type` (… + `reuse`), `-f/--force`, `--dry` |
441
+ | `page recovery <path>` | route path | `--type`, `--dry` |
442
+ | `page list` | — | `--type` |
443
+
444
+ `--type` for `add` does **not** include `reuse` — use `page add-reuse` instead.
445
+
446
+ ```bash
447
+ ubean page add about
448
+ ubean page add "users/[id]"
449
+ ubean page add api/users --type api --method POST
450
+ ubean page add daily-cleanup --type cron --schedule "0 0 * * *"
451
+ ubean page add dashboard --dry
452
+ ```
453
+
454
+ ### `ubean api` / `layout` / `middleware` / `cron` / `plugin`
455
+
456
+ Dedicated commands, each with the subcommands `add`, `delete`, `recovery`, and `list`.
457
+
458
+ | Command | Argument | Extra options | Shared options |
459
+ | ---------------------- | -------- | ---------------------------------- | --------------------- |
460
+ | `ubean api add` | `<path>` | `--method` (default `GET`) | `-f/--force`, `--dry` |
461
+ | `ubean cron add` | `<path>` | `--schedule` (default `* * * * *`) | `-f/--force`, `--dry` |
462
+ | `ubean layout add` | `<path>` | — | `-f/--force`, `--dry` |
463
+ | `ubean middleware add` | `<path>` | — | `-f/--force`, `--dry` |
464
+ | `ubean plugin add` | `<path>` | — | `-f/--force`, `--dry` |
465
+
466
+ `delete <path>` takes `-f/--force` (permanent delete, no backup) and `--dry`; `recovery <path>` takes
467
+ `--dry`; `list` takes no options.
468
+
469
+ ```bash
470
+ ubean api add users/[id] --method DELETE
471
+ ubean layout add admin
472
+ ubean middleware add auth
473
+ ubean cron add daily-cleanup --schedule "0 0 * * *"
474
+ ubean plugin add my-plugin
475
+ ubean api list
476
+ ubean api delete users/[id]
477
+ ubean api recovery users/[id]
478
+ ```
479
+
480
+ ### `ubean scaffold`
481
+
482
+ Machine-readable scaffold catalog for studio and IDE integrations.
483
+
484
+ ```bash
485
+ ubean scaffold describe
486
+ ```
487
+
488
+ `describe` prints the scaffold JSON manifest to stdout. It contains `contractVersion` and a `types`
489
+ array, each entry with `type`, `cli`, `baseDir`, `extensions`, and `args`.
490
+
491
+ ```bash
492
+ ubean scaffold describe
493
+ ubean scaffold describe | jq '.types[].type'
494
+ ```
495
+
496
+ ## Library API
497
+
498
+ Import from `@ubean/cli` — no side effects, no CLI startup.
499
+
500
+ ```ts
501
+ import {
502
+ scaffold,
503
+ deleteScaffold,
504
+ recoverScaffold,
505
+ listScaffoldableFiles,
506
+ getScaffoldManifest,
507
+ SCAFFOLD_CONTRACT_VERSION,
508
+ createFsOps
509
+ } from '@ubean/cli';
510
+
511
+ const result = await scaffold({
512
+ cwd: process.cwd(),
513
+ type: 'api', // 'page' | 'api' | 'layout' | 'middleware' | 'reuse' | 'cron' | 'plugin'
514
+ path: 'users/[id]',
515
+ method: 'POST',
516
+ force: false,
517
+ dry: false // baseDir?: string overrides the default directory for this type
518
+ });
519
+ // → { created, deleted, restored, skipped, errors }
520
+ ```
521
+
522
+ | Export | Description |
523
+ | ---------------------------------------------- | ---------------------------------------------------------------------------------- |
524
+ | `scaffold(options)` / `ScaffoldOptions` | Create files; `ScaffoldResult` = `{ created, deleted, restored, skipped, errors }` |
525
+ | `deleteScaffold(options)` | Delete with a `.bak` backup unless `force` |
526
+ | `recoverScaffold(options)` | Restore from the most recent `.bak` |
527
+ | `listScaffoldableFiles(cwd, type, baseDir?)` | List existing files of a type |
528
+ | `ScaffoldType` | `'page' \| 'api' \| 'layout' \| 'middleware' \| 'reuse' \| 'cron' \| 'plugin'` |
529
+ | `createFsOps(cwd)` / `FsOps` | Filesystem helpers bound to a project root (see below) |
530
+ | `toKebabCase` / `toPascalCase` / `toCamelCase` | Name casing used by the templates |
531
+
532
+ Template rendering, exported for tools that generate their own files:
533
+
534
+ ```ts
535
+ import { renderTemplate, renderApiTemplate, API_TEMPLATE } from '@ubean/cli';
536
+
537
+ renderTemplate('Hello {{name}}', { variables: { name: 'ubean' } });
538
+ ```
539
+
540
+ `renderPageTemplate`, `renderApiTemplate`, `renderMiddlewareTemplate`, `renderLayoutTemplate`,
541
+ `renderCronTemplate`, `renderPluginTemplate` each take their data interface (`PageTemplateData`,
542
+ `ApiTemplateData`, `MiddlewareTemplateData`, `LayoutTemplateData`, `CronTemplateData`,
543
+ `PluginTemplateData`); the raw `PAGE_TEMPLATE` / `API_TEMPLATE` / `MIDDLEWARE_TEMPLATE` /
544
+ `LAYOUT_TEMPLATE` / `CRON_TEMPLATE` / `PLUGIN_TEMPLATE` strings are exported too, along with
545
+ `TemplateVariables` and `TemplateOptions`.
546
+
547
+ `FsOps` (from `createFsOps(cwd)`) bundles `resolvePath`, `ensureDir`, `exists`, `readFile`,
548
+ `writeFile`, `appendFile`, `remove`, `copyFile`, `copyDir`, `readDir`, `readJson`, `writeJson`,
549
+ `createBackup`, `restoreBackup`, `removeBackup`, and `listFiles`. `FsOpOptions` (re-exported as
550
+ `FsOpsOptions`) and `BackupOptions` configure it.
551
+
552
+ ## Scaffold Contract
553
+
554
+ `getScaffoldManifest()` returns the catalog that `ubean scaffold describe` prints — the integration
555
+ point for studio and IDE plugins.
556
+
557
+ ```ts
558
+ import { getScaffoldManifest, SCAFFOLD_CONTRACT_VERSION } from '@ubean/cli';
559
+
560
+ const manifest = getScaffoldManifest();
561
+ // {
562
+ // contractVersion: 1,
563
+ // types: [{ type, cli, baseDir, extensions, args: [{ name, required, description, default? }] }]
564
+ // }
565
+ ```
566
+
567
+ `SCAFFOLD_CONTRACT_VERSION` is `1` and is typed as the literal, so the manifest's own
568
+ `contractVersion` field is checked against it. Each `ScaffoldTypeManifest` records whether a dedicated
569
+ CLI command exists (`cli: true` for api/layout/middleware/cron/plugin; **`false` for `reuse`**, which
570
+ is library-only. Reuse routes are created with `scaffold({ type: 'reuse' })` or `ubean page add-reuse`).
571
+
572
+ ## Bundle Budget Helpers
573
+
574
+ The `ubean analyze` implementation is exportable, so CI scripts can report or gate without spawning
575
+ the CLI.
576
+
577
+ ```ts
578
+ import {
579
+ findClientManifest,
580
+ readViteManifest,
581
+ summarizeBundle,
582
+ compareBundleBaseline,
583
+ writeBundleBaseline
584
+ } from '@ubean/cli';
585
+
586
+ const manifest = findClientManifest(process.cwd());
587
+ const baseline = summarizeBundle(outDir, readViteManifest(manifest.path));
588
+ const violations = compareBundleBaseline(baseline, committedBaseline, { maxIncrease: 0.05 });
589
+ ```
590
+
591
+ | Export | Description |
592
+ | ----------------------------------------------------- | -------------------------------------------------------------------- |
593
+ | `findClientManifest(cwd)` | Locate the client Vite manifest |
594
+ | `readViteManifest(manifestPath)` | Parse it → `Record<string, ManifestChunk>` |
595
+ | `summarizeBundle(outDir, manifest)` | → `BundleBaseline` (total + entry gzip) |
596
+ | `compareBundleBaseline(current, committed, options?)` | → budget violations; `options` carries the allowed relative increase |
597
+ | `writeBundleBaseline(filePath, baseline)` | Persist a baseline |
598
+
599
+ `BundleBaseline`, `BundleBudgetEntry`, and `ManifestChunk` are exported from the main entry. The
600
+ reported figures are gzip sizes; the relative-growth ceiling and the absolute `--max-total-kb` /
601
+ `--max-entry-kb` / `--max-chunk-kb` ceilings are separate gates.
602
+
603
+ ## Global Flags
604
+
605
+ ```bash
606
+ ubean --help
607
+ ubean <command> --help
608
+ ubean --version
609
+ ```
610
+
611
+ `--debug` enables verbose logging (timestamps, call sites, log levels) and works whether it is placed
612
+ before or after the subcommand:
613
+
614
+ ```bash
615
+ ubean --debug dev
616
+ ubean dev --debug
617
+ ```
618
+
619
+ The CLI does **not** read `UBEAN_PORT` / `UBEAN_HOST` — use `--port` / `--host` or the `dev` /
620
+ `preview` fields in `ubean.config.ts`.
621
+
622
+ | Variable | Used by | Description | Default |
623
+ | --------------------------- | ----------------- | --------------------------------------------------------------- | --------- |
624
+ | `PORT` | production server | Server port in the generated production server entry | `9527` |
625
+ | `HOST` | production server | Server host in the generated production server entry | `0.0.0.0` |
626
+ | `CI` / `NONINTERACTIVE` | init | Forces non-interactive mode | — |
627
+ | `UBEAN_KEEP_SSR` | build (SSG mode) | Keeps the temporary `dist/server` bundle instead of deleting it | — |
628
+ | `UBEAN_SKIP_DEP_CHECK` | dependency check | Skips the project dependency check | — |
629
+ | `LOG_LEVEL` / `TSLOG_LEVEL` | logging | Logger minimum level (silly/trace/debug/info/warn/error/fatal) | `info` |
630
+ | `TSLOG_TYPE` | logging | Logger output format (json/pretty/hidden) | — |
631
+
632
+ Exit codes: `0` success, `1` general error. `config path` also exits `1` when no config file exists.
633
+
634
+ ## Type Safety
635
+
636
+ - `ScaffoldType` is a string-literal union, so `scaffold({ type })` and `--type` values are checked at
637
+ compile time rather than at file-write time.
638
+ - `FsOps` is `ReturnType<typeof createFsOps>`, i.e. the helpers' shapes are derived from the factory
639
+ rather than hand-declared — they cannot drift.
640
+ - The template renderers are typed per template (`PageTemplateData`, `ApiTemplateData`, …), so a
641
+ missing variable is a type error, not a `{{placeholder}}` left in the output.
642
+ - `ScaffoldManifest` / `ScaffoldTypeManifest` / `ScaffoldArgManifest` describe the JSON the CLI emits;
643
+ `SCAFFOLD_CONTRACT_VERSION` is a literal type (`1`), not `number`, so `contractVersion` in the
644
+ manifest is pinned.
645
+ - Library exports are all declared in `dist/index.d.ts`; the executable entry ships an empty
646
+ declaration file (`cli.d.ts`) precisely because it has no API surface.
647
+
648
+ ## Architecture Notes
649
+
650
+ - **The CLI does not own the dev/build/preview lifecycles.** ADR-0012 moved them into the Vite plugin:
651
+ `ubeanPlugin()` registers `client` and `ubean` environments plus `builder.buildApp` in its `config`
652
+ hook, so `vite dev | build | preview` is the complete toolchain and the CLI commands are thin
653
+ aliases. Both paths produce item-for-item identical output
654
+ (`packages/cli/test/build-contracts.test.ts`).
655
+ - **Two entry points with different side-effect contracts.** The main entry is a library; `./cli` runs
656
+ `runMain(main)` at module top level and must only ever be imported by a binary. This is why
657
+ `bin/ubean-next.mjs` is a bare `import '../dist/cli.mjs';`.
658
+ - **Projects without a user `vite.config.ts` are still supported.** The CLI injects the builtin plugin
659
+ in that case; the plugin does not register itself twice. When `UBEAN_BUILD_DRIVEN_BY_CLI` is set, the
660
+ plugin must not register its own `buildApp`, or the same build is orchestrated twice.
661
+ - **Codegen ownership is negotiated through environment variables.** `ubean dev` / `build` /
662
+ `prepare` set `UBEAN_CODEGEN_BY_CLI=1` (and the build path sets
663
+ `UBEAN_BUILD_DRIVEN_BY_CLI=1`), which makes the Vite plugin yield — without it, a bare `vite build`
664
+ would produce no `.ubean/*.d.ts` at all and user type-checks would fail.
665
+ - **`env-runner` must stay in this package.** It is not a dependency of `@ubean/build`; adding it there
666
+ triggers pnpm peer-variant drift, which appears as `Plugin` type identity mismatches.
667
+ - **The `ubean-next` binary name is temporary.** The user-facing command is `ubean`; renaming the
668
+ package's own `bin` entry is pending.
669
+ - **Cron scaffolding targets `src/crons/`** to match the scanner's `DEFAULT_DIRS.crons`, not
670
+ `src/tasks/` or any other plausible name.
671
+ - **There is deliberately no in-config rollback for the lifecycle migration.** `experimental.viteBuilder`
672
+ was deleted with the legacy two-`viteBuild` orchestration; rollback is a version rollback only.
673
+ - **Test suites run serially in this package.** Several suites build into the same shared example
674
+ directory (`examples/ubean-test`'s `dist/` and `.ubean/`) — building and previewing the same output
675
+ directory concurrently fails — so the package's Vitest config disables file parallelism. The fix is
676
+ per-suite directory isolation, not rewriting example sources.