@dirwell/cli 0.0.0-stage → 0.0.1-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dirwell contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,84 @@
1
- # Temporary Holding Version
1
+ # @dirwell/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Build a static file explorer or serve a live folder. Plain is included as the
4
+ minimal default: no browser JavaScript, framework, fonts or share images.
5
+ Other themes remain separate installs.
6
+
7
+ Starting with `0.0.1-alpha.1`, the CLI defaults to Plain and supports short theme
8
+ names and automatic `dirwell.config.mjs` discovery. Version `0.0.1-alpha` requires an explicit
9
+ `--theme '@dirwell/theme-plain#createPlainTheme'` and a project-installed theme.
10
+
11
+ ## Open or publish a folder
12
+
13
+ ```sh
14
+ npx @dirwell/cli@alpha server ./files
15
+ npx @dirwell/cli@alpha 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
+ ## Select or customize a theme
33
+
34
+ For repeated project use, install the CLI and the theme you want. Both this CLI
35
+ and the compatibility package expose `dirwell`; replace the old CLI dependency
36
+ instead of installing both in the same project. Keep compatibility API consumers
37
+ on their existing line until they are migrated:
38
+
39
+ ```sh
40
+ npm install --save-dev @dirwell/cli@alpha @dirwell/theme-ledger@alpha
41
+ npx dirwell server ./files --theme ledger
42
+ ```
43
+
44
+ `--theme plain`, `--theme ledger` and `--theme crosswave` select the official
45
+ factories. Custom packages or local modules use `--theme 'module#factory'`, or a
46
+ module's default theme/factory export. Resolution prefers the working project,
47
+ then packages installed beside the CLI by `npm exec --package=...`. The CLI
48
+ never downloads a theme itself or falls back to Plain when an explicit theme fails.
49
+
50
+ For theme options, write `dirwell.config.mjs` in the working directory:
51
+
52
+ ```js
53
+ import { createLedgerTheme } from "@dirwell/theme-ledger";
54
+
55
+ export default {
56
+ theme: createLedgerTheme({ title: "Downloads" }),
57
+ build: { exclude: ["drafts/**"] },
58
+ server: { exclude: ["drafts/**"] },
59
+ };
60
+ ```
61
+
62
+ Then run either command without `--theme`. `--config ./other.mjs` selects another
63
+ JavaScript module. Configuration is executable trusted project code, loaded by
64
+ Node without a config loader or TypeScript compiler. Theme precedence is
65
+ `--theme`, then `config.theme`, then Plain. A missing default file is optional;
66
+ an explicit missing file or a broken config fails visibly. If only a legacy
67
+ `dirwell.config.ts`, `.mts`, `.js` or `.cjs` exists, the CLI stops and asks for
68
+ migration or explicit `--config` selection rather than ignoring selection rules.
69
+ Automatic discovery uses only `.mjs`; explicit config must run as native JavaScript
70
+ on your supported Node version.
71
+
72
+ The config exports only `{ theme, listing, build, server }`; unknown top-level
73
+ keys fail instead of silently dropping legacy options. Put `exclude`/`include`
74
+ in `build` and/or `server`. Export a resolved object, using top-level await
75
+ for asynchronous setup. Shared `listing` accepts
76
+ fixed plans or per-directory callbacks. `build` retains core filtering, sorting,
77
+ metadata, symlinks and output-name policies. `server` provides filtering, sorting,
78
+ authorization and header hooks; HTTP cache defaults to no-store. Build metadata
79
+ and archive search are not live-server features.
80
+
81
+ `--page-size` and `--block-size` override the corresponding static policy as a
82
+ pair. When only one is given, the other publication strategy is `all`.
83
+ Mode-specific flags fail when used with the other mode. Legacy dev/daemon
84
+ commands are unsupported; the CLI does not silently translate them.
package/dist/bin.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/bin.mjs ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { t as runCli } from "./src-BDKeXf0W.mjs";
3
+ //#region src/bin.ts
4
+ try {
5
+ const server = await runCli(process.argv.slice(2));
6
+ if (server !== null) {
7
+ let closing = false;
8
+ const close = async () => {
9
+ if (closing) return;
10
+ closing = true;
11
+ try {
12
+ await server.close();
13
+ } catch (error) {
14
+ console.error(error instanceof Error ? error.message : "Shutdown failed");
15
+ process.exitCode = 1;
16
+ }
17
+ };
18
+ process.once("SIGINT", close);
19
+ process.once("SIGTERM", close);
20
+ }
21
+ } catch (error) {
22
+ console.error(error instanceof Error ? error.message : "Dirwell failed");
23
+ process.exitCode = 1;
24
+ }
25
+ //#endregion
26
+ export {};
27
+
28
+ //# sourceMappingURL=bin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runCli } from \"./index.ts\";\ntry {\n const server = await runCli(process.argv.slice(2));\n if (server !== null) {\n let closing = false;\n const close = async () => {\n if (closing) return;\n closing = true;\n try {\n await server.close();\n } catch (error) {\n console.error(error instanceof Error ? error.message : \"Shutdown failed\");\n process.exitCode = 1;\n }\n };\n process.once(\"SIGINT\", close);\n process.once(\"SIGTERM\", close);\n }\n} catch (error) {\n console.error(error instanceof Error ? error.message : \"Dirwell failed\");\n process.exitCode = 1;\n}\n"],"mappings":";;;AAEA,IAAI;CACF,MAAM,SAAS,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC,CAAC;CACjD,IAAI,WAAW,MAAM;EACnB,IAAI,UAAU;EACd,MAAM,QAAQ,YAAY;GACxB,IAAI,SAAS;GACb,UAAU;GACV,IAAI;IACF,MAAM,OAAO,MAAM;GACrB,SAAS,OAAO;IACd,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB;IACxE,QAAQ,WAAW;GACrB;EACF;EACA,QAAQ,KAAK,UAAU,KAAK;EAC5B,QAAQ,KAAK,WAAW,KAAK;CAC/B;AACF,SAAS,OAAO;CACd,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB;CACvE,QAAQ,WAAW;AACrB"}
@@ -0,0 +1,16 @@
1
+ import { ExplorerTheme, GenerateOptions, ListingPolicy } from "@dirwell/core";
2
+ import { startExplorerServer } from "@dirwell/server/node";
3
+ //#region src/index.d.ts
4
+ export interface CliConfig {
5
+ readonly build?: Omit<GenerateOptions, "sourceDir" | "outputDir" | "theme" | "listing">;
6
+ readonly theme?: ExplorerTheme;
7
+ readonly listing?: ListingPolicy;
8
+ readonly server?: Omit<Parameters<typeof startExplorerServer>[0], "root" | "theme" | "listing">;
9
+ }
10
+ /** Parse explicit commands; return server ownership to the caller for graceful shutdown. */
11
+ export declare function runCli(args: readonly string[], cwd?: string, log?: (value: string) => void): Promise<{
12
+ url: string;
13
+ close: () => Promise<void>;
14
+ } | null>;
15
+ //#endregion
16
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { t as runCli } from "./src-BDKeXf0W.mjs";
2
+ export { runCli };
@@ -0,0 +1,194 @@
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 } from "@dirwell/core";
7
+ import { createPlainTheme } from "@dirwell/theme-plain";
8
+ import { startExplorerServer } from "@dirwell/server/node";
9
+ //#region src/index.ts
10
+ const help = `Usage:
11
+ dirwell build <source> --out <output>
12
+ dirwell server <source> [--port 3000]
13
+
14
+ Plain is included and selected by default. Install other themes separately.
15
+ Official themes: --theme plain|ledger|crosswave
16
+ Custom themes: --theme <module>#<factory>, or a module's default export.
17
+ Reads dirwell.config.mjs in the working folder; --config <file.mjs> selects another file.
18
+ Config exports theme, listing, build options, and server hooks.
19
+ Build options: --mode ssg|mpa, --urls relative|base|html-base, --base <path>
20
+ Server options: --host <host>, --port <port>, --mount <path>, --live
21
+ Both modes: --page-size <n>, --block-size <n>
22
+ Live server reads on request. It does not build an output tree or require watch mode.
23
+ `;
24
+ const positive = (value, name) => {
25
+ if (value === void 0) return void 0;
26
+ const number = Number(value);
27
+ if (!Number.isSafeInteger(number) || number < 1) throw new TypeError(`${name} must be a positive integer`);
28
+ return number;
29
+ };
30
+ function assertTheme(candidate) {
31
+ 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)");
32
+ }
33
+ async function loadTheme(specifier, cwd) {
34
+ const official = {
35
+ plain: "@dirwell/theme-plain#createPlainTheme",
36
+ ledger: "@dirwell/theme-ledger#createLedgerTheme",
37
+ crosswave: "@dirwell/theme-crosswave#createCrosswaveTheme"
38
+ };
39
+ if (Object.hasOwn(official, specifier)) specifier = official[specifier];
40
+ const delimiter = specifier.lastIndexOf("#"), name = delimiter < 0 ? specifier : specifier.slice(0, delimiter), exported = delimiter < 0 ? "default" : specifier.slice(delimiter + 1);
41
+ if (!name || !exported) throw new TypeError("Theme needs a module and export name");
42
+ let moduleUrl;
43
+ if (name.startsWith(".") || path.isAbsolute(name)) moduleUrl = pathToFileURL(path.resolve(cwd, name)).href;
44
+ else try {
45
+ moduleUrl = resolve(name, pathToFileURL(path.join(cwd, "package.json")).href);
46
+ } catch (error) {
47
+ if (!(error instanceof Error && "code" in error && error.code === "ERR_MODULE_NOT_FOUND")) throw error;
48
+ try {
49
+ moduleUrl = resolve(name, import.meta.url);
50
+ } catch (fallbackError) {
51
+ 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}.`);
52
+ throw fallbackError;
53
+ }
54
+ }
55
+ const module = await import(moduleUrl);
56
+ const candidate = typeof module[exported] === "function" ? await module[exported]() : module[exported];
57
+ assertTheme(candidate);
58
+ return candidate;
59
+ }
60
+ /** Parse explicit commands; return server ownership to the caller for graceful shutdown. */
61
+ async function runCli(args, cwd = process.cwd(), log = console.log) {
62
+ const parsed = parseArgs({
63
+ args: [...args],
64
+ allowPositionals: true,
65
+ options: {
66
+ help: {
67
+ type: "boolean",
68
+ short: "h"
69
+ },
70
+ live: { type: "boolean" },
71
+ theme: { type: "string" },
72
+ config: { type: "string" },
73
+ out: { type: "string" },
74
+ mode: { type: "string" },
75
+ urls: { type: "string" },
76
+ base: { type: "string" },
77
+ host: { type: "string" },
78
+ port: { type: "string" },
79
+ mount: { type: "string" },
80
+ "page-size": { type: "string" },
81
+ "block-size": { type: "string" }
82
+ }
83
+ });
84
+ if (parsed.values.help) {
85
+ log(help);
86
+ return null;
87
+ }
88
+ const [command, source] = parsed.positionals;
89
+ if (!["build", "server"].includes(command ?? "") || source === void 0 || source === "" || parsed.positionals.length !== 2) throw new TypeError(help);
90
+ const otherModeOptions = command === "build" ? [
91
+ "host",
92
+ "port",
93
+ "mount",
94
+ "live"
95
+ ] : [
96
+ "out",
97
+ "mode",
98
+ "urls",
99
+ "base"
100
+ ];
101
+ for (const option of otherModeOptions) if (Object.hasOwn(parsed.values, option)) throw new TypeError(`--${option} is not supported in ${command} mode`);
102
+ let config = {};
103
+ const configPath = path.resolve(cwd, parsed.values.config ?? "dirwell.config.mjs");
104
+ let hasConfig = parsed.values.config !== void 0;
105
+ if (!hasConfig) try {
106
+ await access(configPath);
107
+ hasConfig = true;
108
+ } catch (error) {
109
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
110
+ }
111
+ if (!hasConfig) for (const filename of [
112
+ "dirwell.config.ts",
113
+ "dirwell.config.js",
114
+ "dirwell.config.mts",
115
+ "dirwell.config.cjs"
116
+ ]) {
117
+ try {
118
+ await access(path.resolve(cwd, filename));
119
+ } catch (error) {
120
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
121
+ throw error;
122
+ }
123
+ 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.`);
124
+ }
125
+ if (hasConfig) {
126
+ const module = await import(pathToFileURL(configPath).href);
127
+ if (typeof module.default !== "object" || module.default === null || Array.isArray(module.default)) throw new TypeError("Config must export an object");
128
+ for (const key of Object.keys(module.default)) if (![
129
+ "theme",
130
+ "listing",
131
+ "build",
132
+ "server"
133
+ ].includes(key)) throw new TypeError(`Unsupported config key "${key}". Use { theme, listing, build, server }; selection rules belong in build/server.`);
134
+ if (typeof module.default.then === "function") throw new TypeError("Config must export a resolved object; use top-level await for async setup");
135
+ config = module.default;
136
+ }
137
+ const theme = parsed.values.theme !== void 0 ? await loadTheme(parsed.values.theme, cwd) : config.theme === void 0 ? createPlainTheme() : config.theme;
138
+ assertTheme(theme);
139
+ const pageSize = positive(parsed.values["page-size"], "page-size"), blockSize = positive(parsed.values["block-size"], "block-size");
140
+ const listing = pageSize !== void 0 || blockSize !== void 0 ? {
141
+ html: pageSize === void 0 ? { strategy: "all" } : {
142
+ strategy: "paged",
143
+ pageSize
144
+ },
145
+ data: blockSize === void 0 ? { strategy: "all" } : {
146
+ strategy: "blocks",
147
+ blockSize
148
+ }
149
+ } : config.listing;
150
+ const root = path.resolve(cwd, source);
151
+ if (!(await stat(root)).isDirectory()) throw new TypeError("Source must be an existing directory");
152
+ if (command === "build") {
153
+ if (parsed.values.out === void 0) throw new TypeError("Build requires --out pointing to a dedicated generated output tree");
154
+ const mode = parsed.values.mode ?? config.build?.mode ?? "ssg", urlStrategy = parsed.values.urls ?? config.build?.urlStrategy ?? "relative";
155
+ if (mode !== "ssg" && mode !== "mpa") throw new TypeError("mode must be ssg or mpa");
156
+ if (![
157
+ "relative",
158
+ "base",
159
+ "html-base"
160
+ ].includes(urlStrategy)) throw new TypeError("Unknown URL strategy");
161
+ await generateExplorer({
162
+ ...config.build,
163
+ sourceDir: root,
164
+ outputDir: path.resolve(cwd, parsed.values.out),
165
+ theme,
166
+ mode,
167
+ urlStrategy,
168
+ ...parsed.values.base === void 0 ? {} : { base: parsed.values.base },
169
+ ...listing === void 0 ? {} : { listing }
170
+ });
171
+ log(`Built ${parsed.values.out}`);
172
+ return null;
173
+ }
174
+ const port = Number(parsed.values.port ?? process.env.PORT ?? config.server?.port ?? 0);
175
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65535) throw new TypeError("port must be between 0 and 65535");
176
+ const server = await startExplorerServer({
177
+ ...config.server,
178
+ ...parsed.values.live ? { live: config.server?.live || {} } : {},
179
+ root,
180
+ theme,
181
+ port,
182
+ ...listing === void 0 ? {} : { listing },
183
+ ...pageSize === void 0 ? {} : { pageSize },
184
+ ...blockSize === void 0 ? {} : { blockSize },
185
+ ...parsed.values.host === void 0 ? {} : { host: parsed.values.host },
186
+ ...parsed.values.mount === void 0 ? {} : { mountPath: parsed.values.mount }
187
+ });
188
+ log(`Dirwell server: ${server.url}${parsed.values.mount ?? config.server?.mountPath ?? ""}/browse/`);
189
+ return server;
190
+ }
191
+ //#endregion
192
+ export { runCli as t };
193
+
194
+ //# sourceMappingURL=src-BDKeXf0W.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-BDKeXf0W.mjs","names":["resolveModule"],"sources":["../src/index.ts"],"sourcesContent":["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 } from \"@dirwell/core\";\nimport type { ExplorerTheme, ListingPolicy, GenerateOptions } from \"@dirwell/core\";\nimport { createPlainTheme } from \"@dirwell/theme-plain\";\nimport { startExplorerServer } from \"@dirwell/server/node\";\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<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>\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 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\" ? [\"host\", \"port\", \"mount\", \"live\"] : [\"out\", \"mode\", \"urls\", \"base\"];\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\"].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 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 (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 ?? 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 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,6 +1,43 @@
1
1
  {
2
2
  "name": "@dirwell/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "Minimal file explorer build and live-server commands with Plain included.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/vp-tw/dirwell.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "bin": {
12
+ "dirwell": "./dist/bin.mjs"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.mts",
23
+ "import": "./dist/index.mjs"
24
+ }
25
+ },
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "provenance": true,
29
+ "tag": "alpha"
30
+ },
31
+ "dependencies": {
32
+ "@dirwell/core": "0.0.1-alpha.1",
33
+ "@dirwell/server": "0.0.1-alpha.1",
34
+ "@dirwell/theme-plain": "0.0.1-alpha.1",
35
+ "import-meta-resolve": "4.2.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=22.12.0"
39
+ },
40
+ "scripts": {
41
+ "build": "vp pack"
42
+ }
6
43
  }