@streetui/cli 1.0.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.
@@ -0,0 +1,354 @@
1
+ import { IncomingMessage, ServerResponse, Server } from 'node:http';
2
+
3
+ interface Logger {
4
+ info(message: string): void;
5
+ success(message: string): void;
6
+ warn(message: string): void;
7
+ error(message: string): void;
8
+ plain(message: string): void;
9
+ }
10
+ /** The default logger writes to stdout/stderr with a `streetui` prefix. */
11
+ declare function createLogger(prefix?: string): Logger;
12
+
13
+ /**
14
+ * A tiny, dependency-free argument parser tailored to the StreetUI CLI.
15
+ *
16
+ * It intentionally supports only what the CLI actually uses — a leading command
17
+ * word, positional arguments, boolean flags, and a handful of value options
18
+ * (`--port`, `--host`, `--template`, `--dir`). Unknown flags are collected so a
19
+ * command can reject them with a useful message rather than silently ignoring
20
+ * them (Phase 14: no options that are ignored).
21
+ */
22
+ interface ParsedArgs {
23
+ /** The command word, e.g. `create` / `dev` / `build` / `start`. */
24
+ readonly command: string | undefined;
25
+ /** Positional arguments after the command (e.g. the project name). */
26
+ readonly positionals: readonly string[];
27
+ /** `--help` / `-h` anywhere. */
28
+ readonly help: boolean;
29
+ /** `--version` / `-v` anywhere. */
30
+ readonly version: boolean;
31
+ /** `--port <n>` (validated as an integer, else `undefined`). */
32
+ readonly port: number | undefined;
33
+ /** `--host <h>`. */
34
+ readonly host: string | undefined;
35
+ /** `--template <name>` (project template for `create`). */
36
+ readonly template: string | undefined;
37
+ /** `--dir <path>` project directory override. */
38
+ readonly dir: string | undefined;
39
+ /** Any flags we do not recognise, reported verbatim (without leading `--`). */
40
+ readonly unknown: readonly string[];
41
+ }
42
+ /** Parse `process.argv.slice(2)`-style tokens into a `ParsedArgs`. */
43
+ declare function parseArgs(argv: readonly string[]): ParsedArgs;
44
+
45
+ /**
46
+ * StreetUI project configuration (Phase 9). The config is intentionally tiny:
47
+ * every field has a sensible default so `streetui.config.ts` is optional. A
48
+ * project with no config file still builds and runs.
49
+ *
50
+ * The file is authored as TypeScript (`streetui.config.ts`) and compiled with
51
+ * esbuild to a temporary ESM module before import, so we never depend on the
52
+ * host having a TS loader registered.
53
+ */
54
+ /** User-facing configuration shape (all fields optional). */
55
+ interface StreetUIConfig {
56
+ /** Dev server / preview port. Default 3000. */
57
+ readonly port?: number;
58
+ /** Host to bind. Default 'localhost'. */
59
+ readonly host?: string;
60
+ /** Client/browser entry, relative to project root. Default 'src/main.ts'. */
61
+ readonly clientEntry?: string;
62
+ /** Server entry used for SSR, relative to project root. Default 'src/server.ts'. */
63
+ readonly serverEntry?: string;
64
+ /** Output directory for `build`. Default 'dist'. */
65
+ readonly outDir?: string;
66
+ /** Static assets directory copied verbatim. Default 'public'. */
67
+ readonly publicDir?: string;
68
+ }
69
+ /** Fully-resolved config: every field present, all paths absolute. */
70
+ interface ResolvedConfig {
71
+ readonly root: string;
72
+ readonly port: number;
73
+ readonly host: string;
74
+ readonly clientEntry: string;
75
+ readonly serverEntry: string;
76
+ readonly outDir: string;
77
+ readonly publicDir: string;
78
+ }
79
+ /**
80
+ * Identity helper that gives config authors type-checking and autocomplete.
81
+ * It returns its argument unchanged — the value matters, not the call.
82
+ */
83
+ declare function defineConfig(config: StreetUIConfig): StreetUIConfig;
84
+ /** Absolute path of the first config file present in `root`, or undefined. */
85
+ declare function findConfigFile(root: string): string | undefined;
86
+ /**
87
+ * Load and fully resolve configuration for the project rooted at `root`.
88
+ * Missing config file → all defaults. Every returned path is absolute.
89
+ */
90
+ declare function loadConfig(root: string): Promise<ResolvedConfig>;
91
+
92
+ /**
93
+ * Environment variables (Phase 10). The rule is simple and safe by default:
94
+ * only variables whose names begin with `STREETUI_PUBLIC_` are exposed to the
95
+ * browser bundle. Everything else stays on the server, so secrets in the
96
+ * process environment cannot leak into client-side JavaScript.
97
+ *
98
+ * `NODE_ENV` is always defined (as the build mode) so app code can branch on
99
+ * development vs production.
100
+ */
101
+ /** Prefix that marks an env var as safe to ship to the browser. */
102
+ declare const PUBLIC_ENV_PREFIX = "STREETUI_PUBLIC_";
103
+ /**
104
+ * Build the esbuild `define` map for the CLIENT bundle: `NODE_ENV` plus every
105
+ * `STREETUI_PUBLIC_*` variable, each stringified as a compile-time constant.
106
+ * Server-only variables are deliberately excluded.
107
+ */
108
+ declare function clientEnvDefine(mode: 'development' | 'production', env?: NodeJS.ProcessEnv): Record<string, string>;
109
+ /** Names of the public variables currently visible (for logging/diagnostics). */
110
+ declare function publicEnvNames(env?: NodeJS.ProcessEnv): string[];
111
+
112
+ /**
113
+ * Project resolution and validation (Phase 17). Before `dev`, `build`, or
114
+ * `start` do any real work, we confirm the working directory actually looks
115
+ * like a StreetUI project and fail with a clear, actionable message otherwise —
116
+ * never a cryptic stack trace.
117
+ */
118
+
119
+ /** A validated StreetUI project ready for a command to act on. */
120
+ interface ResolvedProject {
121
+ /** Absolute project root. */
122
+ readonly root: string;
123
+ /** Parsed package.json. */
124
+ readonly packageJson: PackageJson;
125
+ /** Fully-resolved configuration (defaults applied). */
126
+ readonly config: ResolvedConfig;
127
+ }
128
+ interface PackageJson {
129
+ readonly name?: string;
130
+ readonly version?: string;
131
+ readonly type?: string;
132
+ readonly dependencies?: Record<string, string>;
133
+ readonly devDependencies?: Record<string, string>;
134
+ readonly scripts?: Record<string, string>;
135
+ readonly [key: string]: unknown;
136
+ }
137
+ /**
138
+ * Resolve + validate the project rooted at `cwd` (or `--dir`). Throws a
139
+ * `CliError` with a helpful suggestion for every failure mode Phase 17 lists:
140
+ * missing package.json, not a StreetUI project, invalid config, missing entry.
141
+ */
142
+ declare function resolveProject(cwd: string, options?: {
143
+ requireEntry?: boolean;
144
+ }): Promise<ResolvedProject>;
145
+
146
+ /**
147
+ * Production build (Phase 7). Two esbuild passes over the project's real
148
+ * entries — a browser bundle for hydration and a Node bundle for SSR — plus a
149
+ * copy of the public directory. No separate production rendering system: the
150
+ * same DSL → compile → renderer pipeline the app already uses is bundled as-is.
151
+ */
152
+
153
+ /** Where each artifact lands under the configured `outDir`. */
154
+ interface BuildOutput {
155
+ readonly clientDir: string;
156
+ readonly serverDir: string;
157
+ readonly clientBundle: string;
158
+ readonly serverBundle: string;
159
+ }
160
+ /**
161
+ * Run the production build for `project`. Returns the output layout on success;
162
+ * throws a `CliError` carrying formatted diagnostics on failure. When
163
+ * `serverEntry` is absent the server pass is skipped (client-only project).
164
+ */
165
+ declare function buildProject(project: ResolvedProject, mode?: 'development' | 'production'): Promise<BuildOutput>;
166
+
167
+ /**
168
+ * `streetui dev` (Phase 5). Builds the project once, then watches for changes
169
+ * with esbuild's incremental context API and rebuilds only what changed —
170
+ * avoiding a full cold build per keystroke (Phase 27). On each successful
171
+ * rebuild connected browsers are told to reload; build errors are printed with
172
+ * real source positions and never crash the server.
173
+ */
174
+
175
+ interface DevOptions {
176
+ readonly project: ResolvedProject;
177
+ readonly logger: Logger;
178
+ readonly host?: string;
179
+ readonly port?: number;
180
+ }
181
+ /** Handle returned so callers (and tests) can shut the dev server down. */
182
+ interface DevServer {
183
+ readonly url: string;
184
+ stop(): Promise<void>;
185
+ }
186
+ /** Start the dev server. Resolves once it is listening; keep the handle to stop. */
187
+ declare function runDev(options: DevOptions): Promise<DevServer>;
188
+
189
+ /**
190
+ * The StreetUI HTTP server (Phases 5 & 8). Built on Node's standard `node:http`
191
+ * — no Express, no third-party server. It serves the built client assets as
192
+ * static files and delegates every other request to the project's server
193
+ * bundle, which exports a `render(request)` function producing full HTML.
194
+ *
195
+ * The same server backs both `dev` (with live-reload injection) and `start`
196
+ * (production). Dev-only behaviour is gated behind the `reload` option.
197
+ */
198
+
199
+ /** The contract a project's server entry must satisfy. */
200
+ interface RenderRequest {
201
+ readonly url: string;
202
+ readonly method: string;
203
+ readonly headers: Record<string, string | string[] | undefined>;
204
+ }
205
+ interface RenderResult {
206
+ readonly html: string;
207
+ readonly status?: number;
208
+ readonly headers?: Record<string, string>;
209
+ }
210
+ type RenderFn = (request: RenderRequest) => RenderResult | Promise<RenderResult>;
211
+ interface ServeOptions {
212
+ readonly clientDir: string;
213
+ readonly serverBundle: string;
214
+ readonly host: string;
215
+ readonly port: number;
216
+ /** When set, HTML responses get a live-reload snippet + an SSE endpoint. */
217
+ readonly reload?: ReloadHub;
218
+ /**
219
+ * Dev mode: re-import the server bundle on every request so edits are picked
220
+ * up without restarting. In production the bundle is loaded once.
221
+ */
222
+ readonly devMode?: boolean;
223
+ }
224
+ /** A running server plus the resolved address and a stop handle. */
225
+ interface RunningServer {
226
+ readonly server: Server;
227
+ readonly url: string;
228
+ close(): Promise<void>;
229
+ }
230
+ /** Live-reload coordination for dev: tracks SSE clients and pushes events. */
231
+ declare class ReloadHub {
232
+ private readonly clients;
233
+ static readonly PATH = "/__streetui_reload";
234
+ /** The snippet injected before `</body>` so the page listens for reloads. */
235
+ static readonly snippet: string;
236
+ handle(_req: IncomingMessage, res: ServerResponse): void;
237
+ /** Tell every connected browser to reload. */
238
+ triggerReload(): void;
239
+ closeAll(): void;
240
+ }
241
+ /** Start the HTTP server and resolve once it is actually listening. */
242
+ declare function startServer(options: ServeOptions): Promise<RunningServer>;
243
+
244
+ /**
245
+ * `streetui start` (Phase 8). Serves an existing production build. If the build
246
+ * output is missing we build it first, so `start` on a fresh checkout still
247
+ * works. Uses the standard Node HTTP server from `serve.ts`.
248
+ */
249
+
250
+ interface StartOptions {
251
+ readonly project: ResolvedProject;
252
+ readonly logger: Logger;
253
+ /** Overrides for the configured host/port (from --host/--port). */
254
+ readonly host?: string;
255
+ readonly port?: number;
256
+ }
257
+ /** Build (if needed) and serve the production output. Resolves once listening. */
258
+ declare function runStart(options: StartOptions): Promise<RunningServer>;
259
+
260
+ /**
261
+ * Template registry (Phases 3, 11, 12). Templates are real files shipped inside
262
+ * the CLI package under `templates/<name>/`. They are copied verbatim at
263
+ * scaffold time, with two transforms: a small set of placeholder tokens are
264
+ * substituted, and files prefixed `_` are un-prefixed (so `_gitignore` becomes
265
+ * `.gitignore` and `_package.json` becomes `package.json` — npm would otherwise
266
+ * mangle those names on publish).
267
+ */
268
+ /** Available starter templates. */
269
+ type TemplateName = 'basic' | 'ssr';
270
+
271
+ /**
272
+ * `streetui create` / `npm create streetui` (Phase 3). Scaffolds a real,
273
+ * working StreetUI project from a shipped template. No network access, no
274
+ * post-install magic — just a recursive copy with placeholder substitution.
275
+ */
276
+
277
+ interface CreateOptions {
278
+ /** Target directory (relative or absolute). */
279
+ readonly targetDir: string;
280
+ /** Template to use; defaults to the SSR starter. */
281
+ readonly template?: string;
282
+ /** StreetUI package version the generated project should depend on. */
283
+ readonly frameworkVersion: string;
284
+ readonly logger: Logger;
285
+ }
286
+ interface CreateResult {
287
+ readonly root: string;
288
+ readonly template: TemplateName;
289
+ readonly files: readonly string[];
290
+ }
291
+ /**
292
+ * Scaffold a new project. Validates the template and the (empty) target, copies
293
+ * the tree, and returns the created root + file list. Throws `CliError` on any
294
+ * user-facing problem.
295
+ */
296
+ declare function createProject(options: CreateOptions): Promise<CreateResult>;
297
+
298
+ /**
299
+ * Developer-facing diagnostics. Two rules govern everything here (Phase 6, 19,
300
+ * 22): be USEFUL and be TRUTHFUL. We only print a source position when the
301
+ * underlying tool (esbuild / Node) actually gives us one, and we never dress up
302
+ * a failure as anything other than what it is.
303
+ */
304
+ /**
305
+ * A CLI-level error carrying a human-readable explanation and, optionally, a
306
+ * concrete suggestion. Throwing this (instead of a bare `Error`) lets the top
307
+ * level render a clean message rather than a raw stack trace for expected
308
+ * user mistakes (Phase 17).
309
+ */
310
+ declare class CliError extends Error {
311
+ readonly suggestion: string | undefined;
312
+ /** Process exit code to use when this error reaches the top level. */
313
+ readonly exitCode: number;
314
+ constructor(message: string, options?: {
315
+ suggestion?: string;
316
+ exitCode?: number;
317
+ });
318
+ }
319
+
320
+ /**
321
+ * `@streetui/cli` public entry. Exposes the programmatic API used by the
322
+ * executables and the tests, and implements `runCli` — the command dispatcher
323
+ * that turns argv into one of `create` / `dev` / `build` / `start` (plus
324
+ * `--help` / `--version`). The CLI only orchestrates the existing StreetUI
325
+ * pipeline; it is not a framework layer of its own.
326
+ */
327
+
328
+ /** The CLI version, read from the compiled package. Kept in one place. */
329
+ declare const CLI_VERSION = "1.0.0";
330
+ /** Options for `runCli`, all injectable so tests can drive it in-process. */
331
+ interface RunCliOptions {
332
+ /** Working directory the command acts on. Defaults to `process.cwd()`. */
333
+ readonly cwd?: string;
334
+ /** Logger sink. Defaults to the branded stdout logger. */
335
+ readonly logger?: Logger;
336
+ /**
337
+ * When true, `dev` and `start` return their running handle instead of
338
+ * blocking forever. Tests set this; the real binary leaves it false.
339
+ */
340
+ readonly returnServer?: boolean;
341
+ }
342
+ /** Result of a command: an exit code plus any long-lived handle for tests. */
343
+ interface RunCliResult {
344
+ readonly exitCode: number;
345
+ readonly server?: {
346
+ url: string;
347
+ stop: () => Promise<void>;
348
+ };
349
+ }
350
+ /** Dispatch a parsed command line. Never throws for expected errors — it maps
351
+ * `CliError` to an exit code and a logged message instead. */
352
+ declare function runCli(argv: readonly string[], options?: RunCliOptions): Promise<RunCliResult>;
353
+
354
+ export { type BuildOutput, CLI_VERSION, CliError, type CreateResult, type DevServer, type Logger, PUBLIC_ENV_PREFIX, ReloadHub, type RenderFn, type RenderRequest, type RenderResult, type ResolvedConfig, type ResolvedProject, type RunCliOptions, type RunCliResult, type StreetUIConfig, buildProject, clientEnvDefine, createLogger, createProject, defineConfig, findConfigFile, loadConfig, parseArgs, publicEnvNames, resolveProject, runCli, runDev, runStart, startServer };