@dirwell/cli 0.0.1-alpha → 0.0.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/README.md CHANGED
@@ -1,19 +1,91 @@
1
1
  # @dirwell/cli
2
2
 
3
- Unreleased alpha workspace. Install a theme separately; no default theme is selected.
3
+ Requires Node.js 22.12 or later. [Read the guide](https://vp-tw.github.io/dirwell/getting-started/).
4
+
5
+ Build a static file explorer or serve a live folder. Plain is included as the
6
+ minimal default: no browser JavaScript, framework, fonts or share images.
7
+ Other themes remain separate installs.
8
+
9
+ The CLI supports short theme names and automatic `dirwell.config.mjs` discovery.
10
+
11
+ ## Open or publish a folder
12
+
13
+ ```sh
14
+ npx @dirwell/cli@0.0.1 server ./files
15
+ npx @dirwell/cli@0.0.1 build ./files --out ./generated
16
+ ```
17
+
18
+ No project, configuration file or global installation is required. Open the URL
19
+ printed by `server`; Ctrl+C stops it. New filesystem data appears on subsequent
20
+ requests. `build` replaces a dedicated static output tree: keep unrelated files
21
+ out of that directory. Both commands require an explicit source folder.
22
+
23
+ Server defaults to loopback, an ephemeral port and safe media previews. HTML,
24
+ SVG and other active formats download. Use `--port`, `--host` and `--mount`
25
+ explicitly when needed. `PORT` is accepted for proxies such as Portless. Port precedence is `--port`, then
26
+ `PORT`, then `config.server.port`, then an ephemeral port. Config can also set
27
+ `server.host`; `--host` overrides it.
28
+ `--live` enables automatic Svelte/Vue updates through shared SSE notifications;
29
+ Plain remains script-free. Server mode does not generate a tree or need a
30
+ filesystem watcher. Theme-source HMR belongs to the optional authoring package.
31
+
32
+ ## Stable selection and output controls
33
+
34
+ These controls are included in `0.0.1`.
35
+
36
+ The CLI reads an optional `.gitignore` in cwd by default. Use repeatable `--ignore-file <path>` to replace that list, or `--no-ignore` to disable it. Config supports shared `ignoreFiles`, overridden by `build.ignoreFiles` or `server.ignoreFiles`; `[]` disables defaults. Paths resolve from cwd; rules use the file's directory as their base. Use `{ path, baseDir, optional }` for shared rules or optional files. Files sharing a pattern base are concatenated in order; deeper scopes override parent scopes. Glob exclusions always win, and include globs do not bypass ignore rules.
37
+
38
+ Builds replace missing, empty or Dirwell-marked output directories. A nonempty unmarked directory requires `--force` / `-f`; force never permits replacing source or a symlink/file destination. `--dry-run` reports selection and planned directory pages without writing output. `defineConfig` from `@dirwell/cli` provides editor types and runtime validation; nested option typos fail before build/server startup.
39
+
40
+ ## Theme selection and customization
41
+
42
+ For repeated project use, install the CLI and the theme you want:
4
43
 
5
44
  ```sh
6
- npm install @dirwell/cli@alpha @dirwell/theme-plain@alpha
7
- npx dirwell build ./files --out ./generated --theme '@dirwell/theme-plain#createPlainTheme'
8
- npx dirwell server ./files --theme '@dirwell/theme-plain#createPlainTheme'
45
+ npm install --save-dev @dirwell/cli@0.0.1 @dirwell/theme-ledger@0.0.1
46
+ npx dirwell server ./files --theme ledger
9
47
  ```
10
48
 
11
- `--live` enables shared SSE revision notifications in Svelte/Vue clients. Plain remains script-free.
49
+ `--theme plain`, `--theme ledger` and `--theme crosswave` select the official
50
+ factories. Custom packages or local modules use `--theme 'module#factory'`, or a
51
+ module's default theme/factory export. Resolution prefers the working project,
52
+ then packages installed beside the CLI by `npm exec --package=...`. The CLI
53
+ never downloads a theme itself or falls back to Plain when an explicit theme fails.
54
+
55
+ For theme options, write `dirwell.config.mjs` in the working directory:
56
+
57
+ ```js
58
+ import { createLedgerTheme } from "@dirwell/theme-ledger";
59
+
60
+ export default {
61
+ theme: createLedgerTheme({ title: "Downloads" }),
62
+ build: { exclude: ["drafts/**"] },
63
+ server: { exclude: ["drafts/**"] },
64
+ };
65
+ ```
12
66
 
13
- `build` writes a dedicated static output tree. `server` renders HTML and reads listings/files live without generating a tree or using a watcher. It defaults to loopback, an ephemeral port, and safe media previews; HTML, SVG and other files download. Use `--port`, `--host` and `--mount` explicitly when needed. `PORT` is accepted for development proxies such as Portless.
67
+ Then run either command without `--theme`. `--config ./other.mjs` selects another
68
+ JavaScript module. Configuration is executable trusted project code, loaded by
69
+ Node without a config loader or TypeScript compiler. Theme precedence is
70
+ `--theme`, then `config.theme`, then Plain. A missing default file is optional;
71
+ an explicit missing file or a broken config fails visibly. If only an unsupported
72
+ `dirwell.config.ts`, `.mts`, `.js` or `.cjs` exists, the CLI stops and asks for
73
+ renaming or explicit `--config` selection rather than ignoring selection rules.
74
+ Automatic discovery uses only `.mjs`; explicit config must run as native JavaScript
75
+ on your supported Node version.
14
76
 
15
- A JavaScript module passed through `--config` exports `{ theme, listing, build, server }`. Build options retain core filtering, sorting, metadata, symlinks and output-name policies. Server options provide filtering, sorting, authorization and header hooks. This is executable project configuration. For theme factories, use `--theme 'package#exportName'`; the package resolves from the working project. Unsupported legacy dev/daemon commands do not silently change behavior.
77
+ The config exports only `{ theme, listing, build, server }`; unknown top-level
78
+ keys fail instead of silently dropping options. Put `exclude`/`include`
79
+ in `build` and/or `server`. Export a resolved object, using top-level await
80
+ for asynchronous setup. Shared `listing` accepts
81
+ fixed plans or per-directory callbacks. `build` retains core filtering, sorting,
82
+ metadata, symlinks and output-name policies. `server` provides filtering, sorting,
83
+ authorization and header hooks; HTTP cache defaults to no-store. Build metadata
84
+ and archive search are not live-server features.
16
85
 
17
- `--page-size` and `--block-size` override the corresponding static policy as a pair. When only one is given, the other publication strategy is `all`. HTTP cache defaults to no-store; headers are configurable through the server config.
86
+ `--page-size` and `--block-size` override the corresponding static policy as a
87
+ pair. When only one is given, the other publication strategy is `all`.
88
+ Mode-specific flags fail when used with the other mode. Legacy dev/daemon
89
+ commands are unsupported; the CLI does not silently translate them.
18
90
 
19
- Server mode replaces file-data rebuild/watch loops. Editing theme source still requires its framework compiler and a server restart; this is not framework HMR. Farm is not part of this server runtime.
91
+ The 0.0.x API may evolve. Pin your qualified version and upgrade the official family together. See [support and verification](https://vp-tw.github.io/dirwell/support/) for host, browser and device boundaries.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runCli } from "./src-7nVpwBzA.mjs";
2
+ import { t as runCli } from "./src-DapzDTGv.mjs";
3
3
  //#region src/bin.ts
4
4
  try {
5
5
  const server = await runCli(process.argv.slice(2));
package/dist/index.d.mts CHANGED
@@ -1,11 +1,16 @@
1
1
  import { ExplorerTheme, GenerateOptions, ListingPolicy } from "@dirwell/core";
2
- import { ExplorerHandlerOptions } from "@dirwell/server";
2
+ import { startExplorerServer } from "@dirwell/server/node";
3
+ //#region src/config.d.ts
4
+ /** Preserve literal types and validate JavaScript configuration at definition time. */
5
+ export declare function defineConfig<const T extends CliConfig>(config: T): T;
6
+ //#endregion
3
7
  //#region src/index.d.ts
4
8
  export interface CliConfig {
9
+ readonly ignoreFiles?: GenerateOptions["ignoreFiles"];
5
10
  readonly build?: Omit<GenerateOptions, "sourceDir" | "outputDir" | "theme" | "listing">;
6
11
  readonly theme?: ExplorerTheme;
7
12
  readonly listing?: ListingPolicy;
8
- readonly server?: Omit<ExplorerHandlerOptions, "root" | "theme" | "listing">;
13
+ readonly server?: Omit<Parameters<typeof startExplorerServer>[0], "root" | "theme" | "listing">;
9
14
  }
10
15
  /** Parse explicit commands; return server ownership to the caller for graceful shutdown. */
11
16
  export declare function runCli(args: readonly string[], cwd?: string, log?: (value: string) => void): Promise<{
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as runCli } from "./src-7nVpwBzA.mjs";
2
- export { runCli };
1
+ import { n as defineConfig, t as runCli } from "./src-DapzDTGv.mjs";
2
+ export { defineConfig, runCli };
@@ -0,0 +1,411 @@
1
+ import path from "node:path";
2
+ import { access, stat } from "node:fs/promises";
3
+ import { pathToFileURL } from "node:url";
4
+ import { resolve } from "import-meta-resolve";
5
+ import { parseArgs } from "node:util";
6
+ import { generateExplorer, resolveIgnoreFiles } from "@dirwell/core";
7
+ import { createPlainTheme } from "@dirwell/theme-plain";
8
+ import { startExplorerServer } from "@dirwell/server/node";
9
+ //#region src/config.ts
10
+ function object(value, name) {
11
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${name} must be an object`);
12
+ return value;
13
+ }
14
+ function distance(left, right) {
15
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
16
+ for (let i = 1; i <= left.length; i++) {
17
+ const current = [i];
18
+ for (let j = 1; j <= right.length; j++) current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, previous[j - 1] + (left[i - 1] === right[j - 1] ? 0 : 1));
19
+ previous = current;
20
+ }
21
+ return previous[right.length];
22
+ }
23
+ function keys(value, allowed, name) {
24
+ for (const key of Object.keys(value)) {
25
+ if (allowed.includes(key)) continue;
26
+ const suggestion = allowed.map((candidate) => ({
27
+ candidate,
28
+ distance: distance(key, candidate)
29
+ })).sort((left, right) => left.distance - right.distance).find((item) => item.distance <= 2)?.candidate;
30
+ throw new TypeError(`Unsupported config key "${name}.${key}".${suggestion ? ` Did you mean "${name}.${suggestion}"?` : ` Supported keys: ${allowed.join(", ")}.`}`);
31
+ }
32
+ }
33
+ function scalar(value, type, name) {
34
+ if (value !== void 0 && (typeof value !== type || type === "number" && !Number.isFinite(value))) throw new TypeError(`${name} must be a ${type}`);
35
+ }
36
+ function patterns(value, name) {
37
+ if (value !== void 0 && typeof value !== "string" && (!Array.isArray(value) || value.some((item) => typeof item !== "string"))) throw new TypeError(`${name} must be a string or an array of strings`);
38
+ }
39
+ function sort(value, name) {
40
+ if (value === void 0) return;
41
+ const options = object(value, name);
42
+ keys(options, [
43
+ "direction",
44
+ "directoriesFirst",
45
+ "field",
46
+ "nameMode"
47
+ ], name);
48
+ scalar(options.directoriesFirst, "boolean", `${name}.directoriesFirst`);
49
+ for (const [key, allowed] of Object.entries({
50
+ direction: ["asc", "desc"],
51
+ field: [
52
+ "name",
53
+ "size",
54
+ "modified"
55
+ ],
56
+ nameMode: [
57
+ "natural",
58
+ "unicode",
59
+ "locale"
60
+ ]
61
+ })) if (options[key] !== void 0 && !allowed.includes(String(options[key]))) throw new TypeError(`${name}.${key} must be one of ${allowed.join(", ")}`);
62
+ }
63
+ function listing(value, name) {
64
+ if (value === void 0 || typeof value === "function") return;
65
+ const plan = object(value, name);
66
+ keys(plan, ["html", "data"], name);
67
+ for (const [key, sizedStrategy, sizeKey] of [[
68
+ "html",
69
+ "paged",
70
+ "pageSize"
71
+ ], [
72
+ "data",
73
+ "blocks",
74
+ "blockSize"
75
+ ]]) {
76
+ const strategy = object(plan[key], `${name}.${key}`);
77
+ keys(strategy, strategy.strategy === sizedStrategy ? ["strategy", sizeKey] : ["strategy"], `${name}.${key}`);
78
+ if (strategy.strategy !== "all" && strategy.strategy !== sizedStrategy) throw new TypeError(`${name}.${key}.strategy must be all or ${sizedStrategy}`);
79
+ if (strategy.strategy === sizedStrategy && (!Number.isSafeInteger(strategy[sizeKey]) || Number(strategy[sizeKey]) < 1)) throw new TypeError(`${name}.${key}.${sizeKey} must be a positive integer`);
80
+ }
81
+ }
82
+ const buildKeys = {
83
+ fileHref: true,
84
+ entryData: true,
85
+ dryRun: true,
86
+ outputPolicy: true,
87
+ ignoreFiles: true,
88
+ metadata: true,
89
+ base: true,
90
+ include: true,
91
+ exclude: true,
92
+ mode: true,
93
+ mirror: true,
94
+ outputName: true,
95
+ urlStrategy: true,
96
+ symlinks: true,
97
+ sort: true
98
+ };
99
+ const serverKeys = {
100
+ entryData: true,
101
+ ignoreFiles: true,
102
+ live: true,
103
+ blockSize: true,
104
+ mountPath: true,
105
+ files: true,
106
+ pageSize: true,
107
+ maxPageSize: true,
108
+ fileHref: true,
109
+ headers: true,
110
+ authorize: true,
111
+ include: true,
112
+ exclude: true,
113
+ sort: true,
114
+ host: true,
115
+ port: true
116
+ };
117
+ /** Validate both modes before filesystem publication or server startup. */
118
+ function validateCliConfig(value) {
119
+ const config = object(value, "Config");
120
+ for (const key of Object.keys(config)) if (![
121
+ "theme",
122
+ "listing",
123
+ "build",
124
+ "server",
125
+ "ignoreFiles"
126
+ ].includes(key)) throw new TypeError(`Unsupported config key "${key}". Use { theme, listing, build, server }; selection rules belong in build/server.`);
127
+ listing(config.listing, "listing");
128
+ resolveIgnoreFiles(config.ignoreFiles);
129
+ for (const mode of ["build", "server"]) {
130
+ if (config[mode] === void 0) continue;
131
+ const options = object(config[mode], mode);
132
+ keys(options, Object.keys(mode === "build" ? buildKeys : serverKeys), mode);
133
+ patterns(options.include, `${mode}.include`);
134
+ patterns(options.exclude, `${mode}.exclude`);
135
+ resolveIgnoreFiles(options.ignoreFiles);
136
+ sort(options.sort, `${mode}.sort`);
137
+ scalar(options.entryData, "function", `${mode}.entryData`);
138
+ if (mode === "build") {
139
+ scalar(options.fileHref, "function", "build.fileHref");
140
+ for (const key of [
141
+ "base",
142
+ "mode",
143
+ "urlStrategy"
144
+ ]) scalar(options[key], "string", `${mode}.${key}`);
145
+ scalar(options.mirror, "boolean", "build.mirror");
146
+ scalar(options.dryRun, "boolean", "build.dryRun");
147
+ if (options.outputPolicy !== void 0 && options.outputPolicy !== "owned" && options.outputPolicy !== "replace") throw new TypeError("build.outputPolicy must be owned or replace");
148
+ scalar(options.metadata, "function", "build.metadata");
149
+ if (options.outputName !== void 0 && typeof options.outputName !== "string" && typeof options.outputName !== "function") throw new TypeError("build.outputName must be a string or function");
150
+ if (options.symlinks !== void 0) {
151
+ const links = object(options.symlinks, "build.symlinks");
152
+ keys(links, [
153
+ "follow",
154
+ "boundary",
155
+ "onCycle"
156
+ ], "build.symlinks");
157
+ scalar(links.follow, "boolean", "build.symlinks.follow");
158
+ for (const [key, allowed] of Object.entries({
159
+ boundary: ["root", "anywhere"],
160
+ onCycle: ["error", "skip"]
161
+ })) if (links[key] !== void 0 && !allowed.includes(String(links[key]))) throw new TypeError(`build.symlinks.${key} must be one of ${allowed.join(", ")}`);
162
+ }
163
+ } else {
164
+ for (const key of ["host", "mountPath"]) scalar(options[key], "string", `server.${key}`);
165
+ for (const key of [
166
+ "port",
167
+ "pageSize",
168
+ "maxPageSize",
169
+ "blockSize"
170
+ ]) scalar(options[key], "number", `server.${key}`);
171
+ for (const key of [
172
+ "fileHref",
173
+ "headers",
174
+ "authorize"
175
+ ]) scalar(options[key], "function", `server.${key}`);
176
+ if (options.files !== void 0 && options.files !== false && ![
177
+ "media",
178
+ "attachment",
179
+ "inline"
180
+ ].includes(String(options.files))) throw new TypeError("server.files must be false, media, attachment or inline");
181
+ if (options.live !== void 0 && options.live !== false) {
182
+ const live = object(options.live, "server.live");
183
+ keys(live, ["intervalMs", "maxClients"], "server.live");
184
+ for (const key of ["intervalMs", "maxClients"]) scalar(live[key], "number", `server.live.${key}`);
185
+ }
186
+ }
187
+ }
188
+ }
189
+ /** Preserve literal types and validate JavaScript configuration at definition time. */
190
+ function defineConfig(config) {
191
+ validateCliConfig(config);
192
+ return config;
193
+ }
194
+ //#endregion
195
+ //#region src/index.ts
196
+ const help = `Usage:
197
+ dirwell build <source> --out <output>
198
+ dirwell server <source> [--port 3000]
199
+
200
+ Plain is included and selected by default. Install other themes separately.
201
+ Official themes: --theme plain|ledger|crosswave
202
+ Custom themes: --theme <module>#<factory>, or a module's default export.
203
+ Reads dirwell.config.mjs in the working folder; --config <file.mjs> selects another file.
204
+ Config exports theme, listing, build options, and server hooks.
205
+ Build options: --mode ssg|mpa, --urls relative|base|html-base, --base <path>
206
+ Build safety: --dry-run, --force (-f) replaces an unowned nonempty output.
207
+ Selection: --ignore-file <path> (repeatable), --no-ignore disables ignore files.
208
+ Server options: --host <host>, --port <port>, --mount <path>, --live
209
+ Both modes: --page-size <n>, --block-size <n>
210
+ Live server reads on request. It does not build an output tree or require watch mode.
211
+ `;
212
+ const positive = (value, name) => {
213
+ if (value === void 0) return void 0;
214
+ const number = Number(value);
215
+ if (!Number.isSafeInteger(number) || number < 1) throw new TypeError(`${name} must be a positive integer`);
216
+ return number;
217
+ };
218
+ function assertTheme(candidate) {
219
+ if (typeof candidate !== "object" || candidate === null || !("name" in candidate) || typeof candidate.name !== "string" || !("render" in candidate) || typeof candidate.render !== "function") throw new TypeError("Theme must be an object with name and render(context)");
220
+ }
221
+ async function loadTheme(specifier, cwd) {
222
+ const official = {
223
+ plain: "@dirwell/theme-plain#createPlainTheme",
224
+ ledger: "@dirwell/theme-ledger#createLedgerTheme",
225
+ crosswave: "@dirwell/theme-crosswave#createCrosswaveTheme"
226
+ };
227
+ if (Object.hasOwn(official, specifier)) specifier = official[specifier];
228
+ const delimiter = specifier.lastIndexOf("#"), name = delimiter < 0 ? specifier : specifier.slice(0, delimiter), exported = delimiter < 0 ? "default" : specifier.slice(delimiter + 1);
229
+ if (!name || !exported) throw new TypeError("Theme needs a module and export name");
230
+ let moduleUrl;
231
+ if (name.startsWith(".") || path.isAbsolute(name)) moduleUrl = pathToFileURL(path.resolve(cwd, name)).href;
232
+ else try {
233
+ moduleUrl = resolve(name, pathToFileURL(path.join(cwd, "package.json")).href);
234
+ } catch (error) {
235
+ if (!(error instanceof Error && "code" in error && error.code === "ERR_MODULE_NOT_FOUND")) throw error;
236
+ try {
237
+ moduleUrl = resolve(name, import.meta.url);
238
+ } catch (fallbackError) {
239
+ if (fallbackError instanceof Error && "code" in fallbackError && fallbackError.code === "ERR_MODULE_NOT_FOUND") throw new TypeError(`Theme package "${name}" was not found. Install it in your project or include it with npm exec --package=${name}.`);
240
+ throw fallbackError;
241
+ }
242
+ }
243
+ const module = await import(moduleUrl);
244
+ const candidate = typeof module[exported] === "function" ? await module[exported]() : module[exported];
245
+ assertTheme(candidate);
246
+ return candidate;
247
+ }
248
+ /** Parse explicit commands; return server ownership to the caller for graceful shutdown. */
249
+ async function runCli(args, cwd = process.cwd(), log = console.log) {
250
+ const parsed = parseArgs({
251
+ args: [...args],
252
+ allowPositionals: true,
253
+ options: {
254
+ help: {
255
+ type: "boolean",
256
+ short: "h"
257
+ },
258
+ force: {
259
+ type: "boolean",
260
+ short: "f"
261
+ },
262
+ "dry-run": { type: "boolean" },
263
+ "ignore-file": {
264
+ type: "string",
265
+ multiple: true
266
+ },
267
+ "no-ignore": { type: "boolean" },
268
+ live: { type: "boolean" },
269
+ theme: { type: "string" },
270
+ config: { type: "string" },
271
+ out: { type: "string" },
272
+ mode: { type: "string" },
273
+ urls: { type: "string" },
274
+ base: { type: "string" },
275
+ host: { type: "string" },
276
+ port: { type: "string" },
277
+ mount: { type: "string" },
278
+ "page-size": { type: "string" },
279
+ "block-size": { type: "string" }
280
+ }
281
+ });
282
+ if (parsed.values.help) {
283
+ log(help);
284
+ return null;
285
+ }
286
+ const [command, source] = parsed.positionals;
287
+ if (!["build", "server"].includes(command ?? "") || source === void 0 || source === "" || parsed.positionals.length !== 2) throw new TypeError(help);
288
+ const otherModeOptions = command === "build" ? [
289
+ "host",
290
+ "port",
291
+ "mount",
292
+ "live"
293
+ ] : [
294
+ "out",
295
+ "mode",
296
+ "urls",
297
+ "base",
298
+ "force",
299
+ "dry-run"
300
+ ];
301
+ for (const option of otherModeOptions) if (Object.hasOwn(parsed.values, option)) throw new TypeError(`--${option} is not supported in ${command} mode`);
302
+ let config = {};
303
+ const configPath = path.resolve(cwd, parsed.values.config ?? "dirwell.config.mjs");
304
+ let hasConfig = parsed.values.config !== void 0;
305
+ if (!hasConfig) try {
306
+ await access(configPath);
307
+ hasConfig = true;
308
+ } catch (error) {
309
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
310
+ }
311
+ if (!hasConfig) for (const filename of [
312
+ "dirwell.config.ts",
313
+ "dirwell.config.js",
314
+ "dirwell.config.mts",
315
+ "dirwell.config.cjs"
316
+ ]) {
317
+ try {
318
+ await access(path.resolve(cwd, filename));
319
+ } catch (error) {
320
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
321
+ throw error;
322
+ }
323
+ throw new TypeError(`Found ${filename}. This CLI discovers dirwell.config.mjs only. Migrate the config or select a native JavaScript module with --config; existing rules will not be silently ignored.`);
324
+ }
325
+ if (hasConfig) {
326
+ const module = await import(pathToFileURL(configPath).href);
327
+ if (typeof module.default !== "object" || module.default === null || Array.isArray(module.default)) throw new TypeError("Config must export an object");
328
+ for (const key of Object.keys(module.default)) if (![
329
+ "theme",
330
+ "listing",
331
+ "build",
332
+ "server",
333
+ "ignoreFiles"
334
+ ].includes(key)) throw new TypeError(`Unsupported config key "${key}". Use { theme, listing, build, server }; selection rules belong in build/server.`);
335
+ if (typeof module.default.then === "function") throw new TypeError("Config must export a resolved object; use top-level await for async setup");
336
+ validateCliConfig(module.default);
337
+ config = module.default;
338
+ }
339
+ const theme = parsed.values.theme !== void 0 ? await loadTheme(parsed.values.theme, cwd) : config.theme === void 0 ? createPlainTheme() : config.theme;
340
+ assertTheme(theme);
341
+ const pageSize = positive(parsed.values["page-size"], "page-size"), blockSize = positive(parsed.values["block-size"], "block-size");
342
+ const listing = pageSize !== void 0 || blockSize !== void 0 ? {
343
+ html: pageSize === void 0 ? { strategy: "all" } : {
344
+ strategy: "paged",
345
+ pageSize
346
+ },
347
+ data: blockSize === void 0 ? { strategy: "all" } : {
348
+ strategy: "blocks",
349
+ blockSize
350
+ }
351
+ } : config.listing;
352
+ const root = path.resolve(cwd, source);
353
+ if (!(await stat(root)).isDirectory()) throw new TypeError("Source must be an existing directory");
354
+ if (parsed.values["no-ignore"] && parsed.values["ignore-file"]) throw new TypeError("--no-ignore cannot be combined with --ignore-file");
355
+ const modeOptions = command === "build" ? config.build : config.server;
356
+ const ignoreFiles = resolveIgnoreFiles(parsed.values["no-ignore"] ? [] : parsed.values["ignore-file"] ?? modeOptions?.ignoreFiles ?? config.ignoreFiles ?? [{
357
+ path: ".gitignore",
358
+ optional: true
359
+ }], cwd);
360
+ if (command === "build") {
361
+ if (parsed.values.out === void 0) throw new TypeError("Build requires --out pointing to a dedicated generated output tree");
362
+ const mode = parsed.values.mode ?? config.build?.mode ?? "ssg", urlStrategy = parsed.values.urls ?? config.build?.urlStrategy ?? "relative";
363
+ if (mode !== "ssg" && mode !== "mpa") throw new TypeError("mode must be ssg or mpa");
364
+ if (![
365
+ "relative",
366
+ "base",
367
+ "html-base"
368
+ ].includes(urlStrategy)) throw new TypeError("Unknown URL strategy");
369
+ const report = await generateExplorer({
370
+ ...config.build,
371
+ sourceDir: root,
372
+ outputDir: path.resolve(cwd, parsed.values.out),
373
+ theme,
374
+ ignoreFiles,
375
+ ...parsed.values.force ? { outputPolicy: "replace" } : {},
376
+ ...parsed.values["dry-run"] ? { dryRun: true } : {},
377
+ mode,
378
+ urlStrategy,
379
+ ...parsed.values.base === void 0 ? {} : { base: parsed.values.base },
380
+ ...listing === void 0 ? {} : { listing }
381
+ });
382
+ if (report.dryRun) log(JSON.stringify(report, null, 2));
383
+ else {
384
+ log(`Built ${parsed.values.out}`);
385
+ log(`${report.selectedPaths.length} selected paths; ${report.pages.filter((page) => page.outputName !== null).length} directory pages; ${report.pages.filter((page) => page.outputName === null).length} skipped pages.`);
386
+ for (const page of report.pages) if (page.directory === "" || page.outputName === null) log(page.outputName === null ? `Skipped explorer page: ${page.directory || "/"}` : `Explorer entry: ${path.join(report.outputDir, page.outputName)}`);
387
+ }
388
+ return null;
389
+ }
390
+ const port = Number(parsed.values.port ?? process.env.PORT ?? config.server?.port ?? 0);
391
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65535) throw new TypeError("port must be between 0 and 65535");
392
+ const server = await startExplorerServer({
393
+ ...config.server,
394
+ ...parsed.values.live ? { live: config.server?.live || {} } : {},
395
+ root,
396
+ theme,
397
+ ignoreFiles,
398
+ port,
399
+ ...listing === void 0 ? {} : { listing },
400
+ ...pageSize === void 0 ? {} : { pageSize },
401
+ ...blockSize === void 0 ? {} : { blockSize },
402
+ ...parsed.values.host === void 0 ? {} : { host: parsed.values.host },
403
+ ...parsed.values.mount === void 0 ? {} : { mountPath: parsed.values.mount }
404
+ });
405
+ log(`Dirwell server: ${server.url}${parsed.values.mount ?? config.server?.mountPath ?? ""}/browse/`);
406
+ return server;
407
+ }
408
+ //#endregion
409
+ export { defineConfig as n, runCli as t };
410
+
411
+ //# sourceMappingURL=src-DapzDTGv.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-DapzDTGv.mjs","names":["resolveModule"],"sources":["../src/config.ts","../src/index.ts"],"sourcesContent":["import type { CliConfig } from \"./index.ts\";\nimport { resolveIgnoreFiles } from \"@dirwell/core\";\n\ntype RecordValue = Record<string, unknown>;\nfunction object(value: unknown, name: string): RecordValue {\n if (typeof value !== \"object\" || value === null || Array.isArray(value))\n throw new TypeError(`${name} must be an object`);\n return value as RecordValue;\n}\nfunction distance(left: string, right: string): number {\n let previous = Array.from({ length: right.length + 1 }, (_, index) => index);\n for (let i = 1; i <= left.length; i++) {\n const current = [i];\n for (let j = 1; j <= right.length; j++)\n current[j] = Math.min(\n current[j - 1]! + 1,\n previous[j]! + 1,\n previous[j - 1]! + (left[i - 1] === right[j - 1] ? 0 : 1),\n );\n previous = current;\n }\n return previous[right.length]!;\n}\nfunction keys(value: RecordValue, allowed: readonly string[], name: string): void {\n for (const key of Object.keys(value)) {\n if (allowed.includes(key)) continue;\n const suggestion = allowed\n .map((candidate) => ({ candidate, distance: distance(key, candidate) }))\n .sort((left, right) => left.distance - right.distance)\n .find((item) => item.distance <= 2)?.candidate;\n throw new TypeError(\n `Unsupported config key \"${name}.${key}\".${suggestion ? ` Did you mean \"${name}.${suggestion}\"?` : ` Supported keys: ${allowed.join(\", \")}.`}`,\n );\n }\n}\nfunction scalar(value: unknown, type: \"string\" | \"boolean\" | \"number\" | \"function\", name: string) {\n if (\n value !== undefined &&\n (typeof value !== type || (type === \"number\" && !Number.isFinite(value)))\n )\n throw new TypeError(`${name} must be a ${type}`);\n}\nfunction patterns(value: unknown, name: string): void {\n if (\n value !== undefined &&\n typeof value !== \"string\" &&\n (!Array.isArray(value) || value.some((item) => typeof item !== \"string\"))\n )\n throw new TypeError(`${name} must be a string or an array of strings`);\n}\nfunction sort(value: unknown, name: string): void {\n if (value === undefined) return;\n const options = object(value, name);\n keys(options, [\"direction\", \"directoriesFirst\", \"field\", \"nameMode\"], name);\n scalar(options.directoriesFirst, \"boolean\", `${name}.directoriesFirst`);\n for (const [key, allowed] of Object.entries({\n direction: [\"asc\", \"desc\"],\n field: [\"name\", \"size\", \"modified\"],\n nameMode: [\"natural\", \"unicode\", \"locale\"],\n }))\n if (options[key] !== undefined && !allowed.includes(String(options[key])))\n throw new TypeError(`${name}.${key} must be one of ${allowed.join(\", \")}`);\n}\nfunction listing(value: unknown, name: string): void {\n if (value === undefined || typeof value === \"function\") return;\n const plan = object(value, name);\n keys(plan, [\"html\", \"data\"], name);\n for (const [key, sizedStrategy, sizeKey] of [\n [\"html\", \"paged\", \"pageSize\"],\n [\"data\", \"blocks\", \"blockSize\"],\n ] as const) {\n const strategy = object(plan[key], `${name}.${key}`);\n keys(\n strategy,\n strategy.strategy === sizedStrategy ? [\"strategy\", sizeKey] : [\"strategy\"],\n `${name}.${key}`,\n );\n if (strategy.strategy !== \"all\" && strategy.strategy !== sizedStrategy)\n throw new TypeError(`${name}.${key}.strategy must be all or ${sizedStrategy}`);\n if (\n strategy.strategy === sizedStrategy &&\n (!Number.isSafeInteger(strategy[sizeKey]) || Number(strategy[sizeKey]) < 1)\n )\n throw new TypeError(`${name}.${key}.${sizeKey} must be a positive integer`);\n }\n}\nconst buildKeys = {\n fileHref: true,\n entryData: true,\n dryRun: true,\n outputPolicy: true,\n ignoreFiles: true,\n metadata: true,\n base: true,\n include: true,\n exclude: true,\n mode: true,\n mirror: true,\n outputName: true,\n urlStrategy: true,\n symlinks: true,\n sort: true,\n} satisfies Record<keyof NonNullable<CliConfig[\"build\"]>, true>;\nconst serverKeys = {\n entryData: true,\n ignoreFiles: true,\n live: true,\n blockSize: true,\n mountPath: true,\n files: true,\n pageSize: true,\n maxPageSize: true,\n fileHref: true,\n headers: true,\n authorize: true,\n include: true,\n exclude: true,\n sort: true,\n host: true,\n port: true,\n} satisfies Record<keyof NonNullable<CliConfig[\"server\"]>, true>;\n\n/** Validate both modes before filesystem publication or server startup. */\nexport function validateCliConfig(value: unknown): asserts value is CliConfig {\n const config = object(value, \"Config\");\n // Preserve the top-level diagnostic used by CLI consumers.\n for (const key of Object.keys(config))\n if (![\"theme\", \"listing\", \"build\", \"server\", \"ignoreFiles\"].includes(key))\n throw new TypeError(\n `Unsupported config key \"${key}\". Use { theme, listing, build, server }; selection rules belong in build/server.`,\n );\n listing(config.listing, \"listing\");\n resolveIgnoreFiles(config.ignoreFiles as CliConfig[\"ignoreFiles\"]);\n for (const mode of [\"build\", \"server\"] as const) {\n if (config[mode] === undefined) continue;\n const options = object(config[mode], mode);\n keys(options, Object.keys(mode === \"build\" ? buildKeys : serverKeys), mode);\n patterns(options.include, `${mode}.include`);\n patterns(options.exclude, `${mode}.exclude`);\n resolveIgnoreFiles(options.ignoreFiles as CliConfig[\"ignoreFiles\"]);\n sort(options.sort, `${mode}.sort`);\n scalar(options.entryData, \"function\", `${mode}.entryData`);\n if (mode === \"build\") {\n scalar(options.fileHref, \"function\", \"build.fileHref\");\n for (const key of [\"base\", \"mode\", \"urlStrategy\"])\n scalar(options[key], \"string\", `${mode}.${key}`);\n scalar(options.mirror, \"boolean\", \"build.mirror\");\n scalar(options.dryRun, \"boolean\", \"build.dryRun\");\n if (\n options.outputPolicy !== undefined &&\n options.outputPolicy !== \"owned\" &&\n options.outputPolicy !== \"replace\"\n )\n throw new TypeError(\"build.outputPolicy must be owned or replace\");\n scalar(options.metadata, \"function\", \"build.metadata\");\n if (\n options.outputName !== undefined &&\n typeof options.outputName !== \"string\" &&\n typeof options.outputName !== \"function\"\n )\n throw new TypeError(\"build.outputName must be a string or function\");\n if (options.symlinks !== undefined) {\n const links = object(options.symlinks, \"build.symlinks\");\n keys(links, [\"follow\", \"boundary\", \"onCycle\"], \"build.symlinks\");\n scalar(links.follow, \"boolean\", \"build.symlinks.follow\");\n for (const [key, allowed] of Object.entries({\n boundary: [\"root\", \"anywhere\"],\n onCycle: [\"error\", \"skip\"],\n }))\n if (links[key] !== undefined && !allowed.includes(String(links[key])))\n throw new TypeError(`build.symlinks.${key} must be one of ${allowed.join(\", \")}`);\n }\n } else {\n for (const key of [\"host\", \"mountPath\"]) scalar(options[key], \"string\", `server.${key}`);\n for (const key of [\"port\", \"pageSize\", \"maxPageSize\", \"blockSize\"])\n scalar(options[key], \"number\", `server.${key}`);\n for (const key of [\"fileHref\", \"headers\", \"authorize\"])\n scalar(options[key], \"function\", `server.${key}`);\n if (\n options.files !== undefined &&\n options.files !== false &&\n ![\"media\", \"attachment\", \"inline\"].includes(String(options.files))\n )\n throw new TypeError(\"server.files must be false, media, attachment or inline\");\n if (options.live !== undefined && options.live !== false) {\n const live = object(options.live, \"server.live\");\n keys(live, [\"intervalMs\", \"maxClients\"], \"server.live\");\n for (const key of [\"intervalMs\", \"maxClients\"])\n scalar(live[key], \"number\", `server.live.${key}`);\n }\n }\n }\n}\n\n/** Preserve literal types and validate JavaScript configuration at definition time. */\nexport function defineConfig<const T extends CliConfig>(config: T): T {\n validateCliConfig(config);\n return config;\n}\n","import path from \"node:path\";\nimport { access, stat } from \"node:fs/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { resolve as resolveModule } from \"import-meta-resolve\";\nimport { parseArgs } from \"node:util\";\nimport { generateExplorer, resolveIgnoreFiles } from \"@dirwell/core\";\nimport type { ExplorerTheme, ListingPolicy, GenerateOptions } from \"@dirwell/core\";\nimport { createPlainTheme } from \"@dirwell/theme-plain\";\nimport { startExplorerServer } from \"@dirwell/server/node\";\nimport { validateCliConfig } from \"./config.ts\";\nexport { defineConfig } from \"./config.ts\";\n\nexport interface CliConfig {\n readonly ignoreFiles?: GenerateOptions[\"ignoreFiles\"];\n readonly build?: Omit<GenerateOptions, \"sourceDir\" | \"outputDir\" | \"theme\" | \"listing\">;\n readonly theme?: ExplorerTheme;\n readonly listing?: ListingPolicy;\n readonly server?: Omit<Parameters<typeof startExplorerServer>[0], \"root\" | \"theme\" | \"listing\">;\n}\nconst help = `Usage:\n dirwell build <source> --out <output>\n dirwell server <source> [--port 3000]\n\nPlain is included and selected by default. Install other themes separately.\nOfficial themes: --theme plain|ledger|crosswave\nCustom themes: --theme <module>#<factory>, or a module's default export.\nReads dirwell.config.mjs in the working folder; --config <file.mjs> selects another file.\nConfig exports theme, listing, build options, and server hooks.\nBuild options: --mode ssg|mpa, --urls relative|base|html-base, --base <path>\nBuild safety: --dry-run, --force (-f) replaces an unowned nonempty output.\nSelection: --ignore-file <path> (repeatable), --no-ignore disables ignore files.\nServer options: --host <host>, --port <port>, --mount <path>, --live\nBoth modes: --page-size <n>, --block-size <n>\nLive server reads on request. It does not build an output tree or require watch mode.\n`;\nconst positive = (value: string | undefined, name: string): number | undefined => {\n if (value === undefined) return undefined;\n const number = Number(value);\n if (!Number.isSafeInteger(number) || number < 1)\n throw new TypeError(`${name} must be a positive integer`);\n return number;\n};\nfunction assertTheme(candidate: unknown): asserts candidate is ExplorerTheme {\n if (\n typeof candidate !== \"object\" ||\n candidate === null ||\n !(\"name\" in candidate) ||\n typeof candidate.name !== \"string\" ||\n !(\"render\" in candidate) ||\n typeof candidate.render !== \"function\"\n )\n throw new TypeError(\"Theme must be an object with name and render(context)\");\n}\nasync function loadTheme(specifier: string, cwd: string): Promise<ExplorerTheme> {\n const official: Record<string, string> = {\n plain: \"@dirwell/theme-plain#createPlainTheme\",\n ledger: \"@dirwell/theme-ledger#createLedgerTheme\",\n crosswave: \"@dirwell/theme-crosswave#createCrosswaveTheme\",\n };\n if (Object.hasOwn(official, specifier)) specifier = official[specifier]!;\n const delimiter = specifier.lastIndexOf(\"#\"),\n name = delimiter < 0 ? specifier : specifier.slice(0, delimiter),\n exported = delimiter < 0 ? \"default\" : specifier.slice(delimiter + 1);\n if (!name || !exported) throw new TypeError(\"Theme needs a module and export name\");\n let moduleUrl: string;\n if (name.startsWith(\".\") || path.isAbsolute(name)) {\n moduleUrl = pathToFileURL(path.resolve(cwd, name)).href;\n } else {\n try {\n moduleUrl = resolveModule(name, pathToFileURL(path.join(cwd, \"package.json\")).href);\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ERR_MODULE_NOT_FOUND\"))\n throw error;\n // npm exec installs explicit --package entries beside the CLI, outside the user's folder.\n try {\n moduleUrl = resolveModule(name, import.meta.url);\n } catch (fallbackError) {\n if (\n fallbackError instanceof Error &&\n \"code\" in fallbackError &&\n fallbackError.code === \"ERR_MODULE_NOT_FOUND\"\n )\n throw new TypeError(\n `Theme package \"${name}\" was not found. Install it in your project or include it with npm exec --package=${name}.`,\n );\n throw fallbackError;\n }\n }\n }\n const module: Record<string, unknown> = await import(moduleUrl);\n const candidate =\n typeof module[exported] === \"function\" ? await module[exported]() : module[exported];\n assertTheme(candidate);\n return candidate;\n}\n/** Parse explicit commands; return server ownership to the caller for graceful shutdown. */\nexport async function runCli(\n args: readonly string[],\n cwd = process.cwd(),\n log: (value: string) => void = console.log,\n) {\n const parsed = parseArgs({\n args: [...args],\n allowPositionals: true,\n options: {\n help: { type: \"boolean\", short: \"h\" },\n force: { type: \"boolean\", short: \"f\" },\n \"dry-run\": { type: \"boolean\" },\n \"ignore-file\": { type: \"string\", multiple: true },\n \"no-ignore\": { type: \"boolean\" },\n live: { type: \"boolean\" },\n theme: { type: \"string\" },\n config: { type: \"string\" },\n out: { type: \"string\" },\n mode: { type: \"string\" },\n urls: { type: \"string\" },\n base: { type: \"string\" },\n host: { type: \"string\" },\n port: { type: \"string\" },\n mount: { type: \"string\" },\n \"page-size\": { type: \"string\" },\n \"block-size\": { type: \"string\" },\n },\n });\n if (parsed.values.help) {\n log(help);\n return null;\n }\n const [command, source] = parsed.positionals;\n if (\n ![\"build\", \"server\"].includes(command ?? \"\") ||\n source === undefined ||\n source === \"\" ||\n parsed.positionals.length !== 2\n )\n throw new TypeError(help);\n const otherModeOptions =\n command === \"build\"\n ? [\"host\", \"port\", \"mount\", \"live\"]\n : [\"out\", \"mode\", \"urls\", \"base\", \"force\", \"dry-run\"];\n for (const option of otherModeOptions)\n if (Object.hasOwn(parsed.values, option))\n throw new TypeError(`--${option} is not supported in ${command} mode`);\n let config: CliConfig = {};\n const configPath = path.resolve(cwd, parsed.values.config ?? \"dirwell.config.mjs\");\n let hasConfig = parsed.values.config !== undefined;\n if (!hasConfig) {\n try {\n await access(configPath);\n hasConfig = true;\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) throw error;\n }\n }\n if (!hasConfig) {\n for (const filename of [\n \"dirwell.config.ts\",\n \"dirwell.config.js\",\n \"dirwell.config.mts\",\n \"dirwell.config.cjs\",\n ]) {\n try {\n await access(path.resolve(cwd, filename));\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") continue;\n throw error;\n }\n throw new TypeError(\n `Found ${filename}. This CLI discovers dirwell.config.mjs only. Migrate the config or select a native JavaScript module with --config; existing rules will not be silently ignored.`,\n );\n }\n }\n if (hasConfig) {\n const module = await import(pathToFileURL(configPath).href);\n if (\n typeof module.default !== \"object\" ||\n module.default === null ||\n Array.isArray(module.default)\n )\n throw new TypeError(\"Config must export an object\");\n for (const key of Object.keys(module.default))\n if (![\"theme\", \"listing\", \"build\", \"server\", \"ignoreFiles\"].includes(key))\n throw new TypeError(\n `Unsupported config key \"${key}\". Use { theme, listing, build, server }; selection rules belong in build/server.`,\n );\n if (typeof module.default.then === \"function\")\n throw new TypeError(\n \"Config must export a resolved object; use top-level await for async setup\",\n );\n validateCliConfig(module.default);\n config = module.default;\n }\n const theme =\n parsed.values.theme !== undefined\n ? await loadTheme(parsed.values.theme, cwd)\n : config.theme === undefined\n ? createPlainTheme()\n : config.theme;\n assertTheme(theme);\n const pageSize = positive(parsed.values[\"page-size\"], \"page-size\"),\n blockSize = positive(parsed.values[\"block-size\"], \"block-size\");\n const listing: ListingPolicy | undefined =\n pageSize !== undefined || blockSize !== undefined\n ? {\n html: pageSize === undefined ? { strategy: \"all\" } : { strategy: \"paged\", pageSize },\n data: blockSize === undefined ? { strategy: \"all\" } : { strategy: \"blocks\", blockSize },\n }\n : config.listing;\n const root = path.resolve(cwd, source);\n if (!(await stat(root)).isDirectory())\n throw new TypeError(\"Source must be an existing directory\");\n if (parsed.values[\"no-ignore\"] && parsed.values[\"ignore-file\"])\n throw new TypeError(\"--no-ignore cannot be combined with --ignore-file\");\n const modeOptions = command === \"build\" ? config.build : config.server;\n const ignoreFiles = resolveIgnoreFiles(\n parsed.values[\"no-ignore\"]\n ? []\n : (parsed.values[\"ignore-file\"] ??\n modeOptions?.ignoreFiles ??\n config.ignoreFiles ?? [{ path: \".gitignore\", optional: true }]),\n cwd,\n );\n if (command === \"build\") {\n if (parsed.values.out === undefined)\n throw new TypeError(\"Build requires --out pointing to a dedicated generated output tree\");\n const mode = parsed.values.mode ?? config.build?.mode ?? \"ssg\",\n urlStrategy = parsed.values.urls ?? config.build?.urlStrategy ?? \"relative\";\n if (mode !== \"ssg\" && mode !== \"mpa\") throw new TypeError(\"mode must be ssg or mpa\");\n if (![\"relative\", \"base\", \"html-base\"].includes(urlStrategy))\n throw new TypeError(\"Unknown URL strategy\");\n const report = await generateExplorer({\n ...config.build,\n sourceDir: root,\n outputDir: path.resolve(cwd, parsed.values.out),\n theme,\n ignoreFiles,\n ...(parsed.values.force ? { outputPolicy: \"replace\" } : {}),\n ...(parsed.values[\"dry-run\"] ? { dryRun: true } : {}),\n mode,\n urlStrategy: urlStrategy as \"relative\" | \"base\" | \"html-base\",\n ...(parsed.values.base === undefined ? {} : { base: parsed.values.base }),\n ...(listing === undefined ? {} : { listing }),\n });\n if (report.dryRun) log(JSON.stringify(report, null, 2));\n else {\n log(`Built ${parsed.values.out}`);\n log(\n `${report.selectedPaths.length} selected paths; ${report.pages.filter((page) => page.outputName !== null).length} directory pages; ${report.pages.filter((page) => page.outputName === null).length} skipped pages.`,\n );\n for (const page of report.pages)\n if (page.directory === \"\" || page.outputName === null)\n log(\n page.outputName === null\n ? `Skipped explorer page: ${page.directory || \"/\"}`\n : `Explorer entry: ${path.join(report.outputDir, page.outputName)}`,\n );\n }\n return null;\n }\n const port = Number(parsed.values.port ?? process.env.PORT ?? config.server?.port ?? 0);\n if (!Number.isSafeInteger(port) || port < 0 || port > 65535)\n throw new TypeError(\"port must be between 0 and 65535\");\n const server = await startExplorerServer({\n ...config.server,\n ...(parsed.values.live ? { live: config.server?.live || {} } : {}),\n root,\n theme,\n ignoreFiles,\n port,\n ...(listing === undefined ? {} : { listing }),\n ...(pageSize === undefined ? {} : { pageSize }),\n ...(blockSize === undefined ? {} : { blockSize }),\n ...(parsed.values.host === undefined ? {} : { host: parsed.values.host }),\n ...(parsed.values.mount === undefined ? {} : { mountPath: parsed.values.mount }),\n });\n log(\n `Dirwell server: ${server.url}${parsed.values.mount ?? config.server?.mountPath ?? \"\"}/browse/`,\n );\n return server;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@dirwell/cli",
3
- "version": "0.0.1-alpha",
4
- "description": "Explicit-theme build and live-server commands for Dirwell.",
3
+ "version": "0.0.1",
4
+ "description": "Minimal file explorer build and live-server commands with Plain included.",
5
+ "homepage": "https://vp-tw.github.io/dirwell/getting-started/",
6
+ "bugs": {
7
+ "url": "https://github.com/vp-tw/dirwell/issues"
8
+ },
5
9
  "license": "MIT",
6
10
  "repository": {
7
11
  "type": "git",
@@ -26,11 +30,12 @@
26
30
  "publishConfig": {
27
31
  "access": "public",
28
32
  "provenance": true,
29
- "tag": "alpha"
33
+ "tag": "latest"
30
34
  },
31
35
  "dependencies": {
32
- "@dirwell/core": "0.0.1-alpha",
33
- "@dirwell/server": "0.0.1-alpha",
36
+ "@dirwell/core": "0.0.1",
37
+ "@dirwell/server": "0.0.1",
38
+ "@dirwell/theme-plain": "0.0.1",
34
39
  "import-meta-resolve": "4.2.0"
35
40
  },
36
41
  "engines": {
@@ -1,125 +0,0 @@
1
- import path from "node:path";
2
- import { pathToFileURL } from "node:url";
3
- import { resolve } from "import-meta-resolve";
4
- import { parseArgs } from "node:util";
5
- import { generateExplorer } from "@dirwell/core";
6
- import { startExplorerServer } from "@dirwell/server/node";
7
- //#region src/index.ts
8
- const help = `Usage:
9
- dirwell build <source> --out <output> --theme <module>#<factory>
10
- dirwell server <source> --theme <module>#<factory> [--port 3000]
11
-
12
- Install your theme separately. No theme is installed or selected implicitly.
13
- Use --config <file.mjs> for a default export with theme, listing, and server hooks.
14
- Build options: --mode ssg|mpa, --urls relative|base|html-base, --base <path>
15
- Server options: --host <host>, --port <port>, --mount <path>, --live
16
- Both modes: --page-size <n>, --block-size <n>
17
- Live server reads on request. It does not build an output tree or require watch mode.
18
- `;
19
- const positive = (value, name) => {
20
- if (value === void 0) return void 0;
21
- const number = Number(value);
22
- if (!Number.isSafeInteger(number) || number < 1) throw new TypeError(`${name} must be a positive integer`);
23
- return number;
24
- };
25
- async function loadTheme(specifier, cwd) {
26
- const delimiter = specifier.lastIndexOf("#"), name = delimiter < 0 ? specifier : specifier.slice(0, delimiter), exported = delimiter < 0 ? "default" : specifier.slice(delimiter + 1);
27
- if (!name || !exported) throw new TypeError("Theme needs a module and export name");
28
- const module = await (name.startsWith(".") || path.isAbsolute(name) ? import(pathToFileURL(path.resolve(cwd, name)).href) : import(resolve(name, pathToFileURL(path.join(cwd, "package.json")).href)));
29
- const candidate = typeof module[exported] === "function" ? await module[exported]() : module[exported];
30
- if (typeof candidate !== "object" || candidate === null || !("name" in candidate) || typeof candidate.name !== "string" || !("render" in candidate) || typeof candidate.render !== "function") throw new TypeError("Theme export must be a theme or theme factory");
31
- return candidate;
32
- }
33
- /** Parse explicit commands; return server ownership to the caller for graceful shutdown. */
34
- async function runCli(args, cwd = process.cwd(), log = console.log) {
35
- const parsed = parseArgs({
36
- args: [...args],
37
- allowPositionals: true,
38
- options: {
39
- help: {
40
- type: "boolean",
41
- short: "h"
42
- },
43
- live: { type: "boolean" },
44
- theme: { type: "string" },
45
- config: { type: "string" },
46
- out: { type: "string" },
47
- mode: { type: "string" },
48
- urls: { type: "string" },
49
- base: { type: "string" },
50
- host: { type: "string" },
51
- port: { type: "string" },
52
- mount: { type: "string" },
53
- "page-size": { type: "string" },
54
- "block-size": { type: "string" }
55
- }
56
- });
57
- if (parsed.values.help) {
58
- log(help);
59
- return null;
60
- }
61
- const [command, source] = parsed.positionals;
62
- if (!["build", "server"].includes(command ?? "") || source === void 0 || parsed.positionals.length !== 2) throw new TypeError(help);
63
- let config = {};
64
- if (parsed.values.config) {
65
- const module = await import(pathToFileURL(path.resolve(cwd, parsed.values.config)).href);
66
- if (typeof module.default !== "object" || module.default === null) throw new TypeError("Config must export an object");
67
- config = module.default;
68
- }
69
- const theme = parsed.values.theme ? await loadTheme(parsed.values.theme, cwd) : config.theme;
70
- if (theme === void 0) throw new TypeError("Choose an explicit --theme or config.theme. Install the theme package separately.");
71
- const pageSize = positive(parsed.values["page-size"], "page-size"), blockSize = positive(parsed.values["block-size"], "block-size");
72
- const listing = pageSize !== void 0 || blockSize !== void 0 ? {
73
- html: pageSize === void 0 ? { strategy: "all" } : {
74
- strategy: "paged",
75
- pageSize
76
- },
77
- data: blockSize === void 0 ? { strategy: "all" } : {
78
- strategy: "blocks",
79
- blockSize
80
- }
81
- } : config.listing;
82
- const root = path.resolve(cwd, source);
83
- if (command === "build") {
84
- if (parsed.values.out === void 0) throw new TypeError("Build requires --out pointing to a dedicated generated output tree");
85
- const mode = parsed.values.mode ?? config.build?.mode ?? "ssg", urlStrategy = parsed.values.urls ?? config.build?.urlStrategy ?? "relative";
86
- if (mode !== "ssg" && mode !== "mpa") throw new TypeError("mode must be ssg or mpa");
87
- if (![
88
- "relative",
89
- "base",
90
- "html-base"
91
- ].includes(urlStrategy)) throw new TypeError("Unknown URL strategy");
92
- await generateExplorer({
93
- ...config.build,
94
- sourceDir: root,
95
- outputDir: path.resolve(cwd, parsed.values.out),
96
- theme,
97
- mode,
98
- urlStrategy,
99
- ...parsed.values.base === void 0 ? {} : { base: parsed.values.base },
100
- ...listing === void 0 ? {} : { listing }
101
- });
102
- log(`Built ${parsed.values.out}`);
103
- return null;
104
- }
105
- const port = Number(parsed.values.port ?? process.env.PORT ?? "0");
106
- if (!Number.isSafeInteger(port) || port < 0 || port > 65535) throw new TypeError("port must be between 0 and 65535");
107
- const server = await startExplorerServer({
108
- ...config.server,
109
- ...parsed.values.live ? { live: {} } : {},
110
- root,
111
- theme,
112
- port,
113
- ...listing === void 0 ? {} : { listing },
114
- ...pageSize === void 0 ? {} : { pageSize },
115
- ...blockSize === void 0 ? {} : { blockSize },
116
- ...parsed.values.host === void 0 ? {} : { host: parsed.values.host },
117
- ...parsed.values.mount === void 0 ? {} : { mountPath: parsed.values.mount }
118
- });
119
- log(`Dirwell server: ${server.url}${parsed.values.mount ?? config.server?.mountPath ?? ""}/browse/`);
120
- return server;
121
- }
122
- //#endregion
123
- export { runCli as t };
124
-
125
- //# sourceMappingURL=src-7nVpwBzA.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"src-7nVpwBzA.mjs","names":["resolveModule"],"sources":["../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { resolve as resolveModule } from \"import-meta-resolve\";\nimport { parseArgs } from \"node:util\";\nimport { generateExplorer } from \"@dirwell/core\";\nimport type { ExplorerTheme, ListingPolicy, GenerateOptions } from \"@dirwell/core\";\nimport { startExplorerServer } from \"@dirwell/server/node\";\nimport type { ExplorerHandlerOptions } from \"@dirwell/server\";\n\nexport interface CliConfig {\n readonly build?: Omit<GenerateOptions, \"sourceDir\" | \"outputDir\" | \"theme\" | \"listing\">;\n readonly theme?: ExplorerTheme;\n readonly listing?: ListingPolicy;\n readonly server?: Omit<ExplorerHandlerOptions, \"root\" | \"theme\" | \"listing\">;\n}\nconst help = `Usage:\n dirwell build <source> --out <output> --theme <module>#<factory>\n dirwell server <source> --theme <module>#<factory> [--port 3000]\n\nInstall your theme separately. No theme is installed or selected implicitly.\nUse --config <file.mjs> for a default export with theme, listing, and server hooks.\nBuild options: --mode ssg|mpa, --urls relative|base|html-base, --base <path>\nServer options: --host <host>, --port <port>, --mount <path>, --live\nBoth modes: --page-size <n>, --block-size <n>\nLive server reads on request. It does not build an output tree or require watch mode.\n`;\nconst positive = (value: string | undefined, name: string): number | undefined => {\n if (value === undefined) return undefined;\n const number = Number(value);\n if (!Number.isSafeInteger(number) || number < 1)\n throw new TypeError(`${name} must be a positive integer`);\n return number;\n};\nasync function loadTheme(specifier: string, cwd: string): Promise<ExplorerTheme> {\n const delimiter = specifier.lastIndexOf(\"#\"),\n name = delimiter < 0 ? specifier : specifier.slice(0, delimiter),\n exported = delimiter < 0 ? \"default\" : specifier.slice(delimiter + 1);\n if (!name || !exported) throw new TypeError(\"Theme needs a module and export name\");\n const moduleUrl =\n name.startsWith(\".\") || path.isAbsolute(name)\n ? pathToFileURL(path.resolve(cwd, name)).href\n : resolveModule(name, pathToFileURL(path.join(cwd, \"package.json\")).href);\n const module: Record<string, unknown> = await import(moduleUrl);\n const candidate =\n typeof module[exported] === \"function\" ? await module[exported]() : module[exported];\n if (\n typeof candidate !== \"object\" ||\n candidate === null ||\n !(\"name\" in candidate) ||\n typeof candidate.name !== \"string\" ||\n !(\"render\" in candidate) ||\n typeof candidate.render !== \"function\"\n )\n throw new TypeError(\"Theme export must be a theme or theme factory\");\n return candidate as ExplorerTheme;\n}\n/** Parse explicit commands; return server ownership to the caller for graceful shutdown. */\nexport async function runCli(\n args: readonly string[],\n cwd = process.cwd(),\n log: (value: string) => void = console.log,\n) {\n const parsed = parseArgs({\n args: [...args],\n allowPositionals: true,\n options: {\n help: { type: \"boolean\", short: \"h\" },\n live: { type: \"boolean\" },\n theme: { type: \"string\" },\n config: { type: \"string\" },\n out: { type: \"string\" },\n mode: { type: \"string\" },\n urls: { type: \"string\" },\n base: { type: \"string\" },\n host: { type: \"string\" },\n port: { type: \"string\" },\n mount: { type: \"string\" },\n \"page-size\": { type: \"string\" },\n \"block-size\": { type: \"string\" },\n },\n });\n if (parsed.values.help) {\n log(help);\n return null;\n }\n const [command, source] = parsed.positionals;\n if (\n ![\"build\", \"server\"].includes(command ?? \"\") ||\n source === undefined ||\n parsed.positionals.length !== 2\n )\n throw new TypeError(help);\n let config: CliConfig = {};\n if (parsed.values.config) {\n const module = await import(pathToFileURL(path.resolve(cwd, parsed.values.config)).href);\n if (typeof module.default !== \"object\" || module.default === null)\n throw new TypeError(\"Config must export an object\");\n config = module.default;\n }\n const theme = parsed.values.theme ? await loadTheme(parsed.values.theme, cwd) : config.theme;\n if (theme === undefined)\n throw new TypeError(\n \"Choose an explicit --theme or config.theme. Install the theme package separately.\",\n );\n const pageSize = positive(parsed.values[\"page-size\"], \"page-size\"),\n blockSize = positive(parsed.values[\"block-size\"], \"block-size\");\n const listing: ListingPolicy | undefined =\n pageSize !== undefined || blockSize !== undefined\n ? {\n html: pageSize === undefined ? { strategy: \"all\" } : { strategy: \"paged\", pageSize },\n data: blockSize === undefined ? { strategy: \"all\" } : { strategy: \"blocks\", blockSize },\n }\n : config.listing;\n const root = path.resolve(cwd, source);\n if (command === \"build\") {\n if (parsed.values.out === undefined)\n throw new TypeError(\"Build requires --out pointing to a dedicated generated output tree\");\n const mode = parsed.values.mode ?? config.build?.mode ?? \"ssg\",\n urlStrategy = parsed.values.urls ?? config.build?.urlStrategy ?? \"relative\";\n if (mode !== \"ssg\" && mode !== \"mpa\") throw new TypeError(\"mode must be ssg or mpa\");\n if (![\"relative\", \"base\", \"html-base\"].includes(urlStrategy))\n throw new TypeError(\"Unknown URL strategy\");\n await generateExplorer({\n ...config.build,\n sourceDir: root,\n outputDir: path.resolve(cwd, parsed.values.out),\n theme,\n mode,\n urlStrategy: urlStrategy as \"relative\" | \"base\" | \"html-base\",\n ...(parsed.values.base === undefined ? {} : { base: parsed.values.base }),\n ...(listing === undefined ? {} : { listing }),\n });\n log(`Built ${parsed.values.out}`);\n return null;\n }\n const port = Number(parsed.values.port ?? process.env.PORT ?? \"0\");\n if (!Number.isSafeInteger(port) || port < 0 || port > 65535)\n throw new TypeError(\"port must be between 0 and 65535\");\n const server = await startExplorerServer({\n ...config.server,\n ...(parsed.values.live ? { live: {} } : {}),\n root,\n theme,\n port,\n ...(listing === undefined ? {} : { listing }),\n ...(pageSize === undefined ? {} : { pageSize }),\n ...(blockSize === undefined ? {} : { blockSize }),\n ...(parsed.values.host === undefined ? {} : { host: parsed.values.host }),\n ...(parsed.values.mount === undefined ? {} : { mountPath: parsed.values.mount }),\n });\n log(\n `Dirwell server: ${server.url}${parsed.values.mount ?? config.server?.mountPath ?? \"\"}/browse/`,\n );\n return server;\n}\n"],"mappings":"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"}