@lucasviola/specwiki 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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +226 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/generate.d.ts +5 -0
  8. package/dist/commands/generate.d.ts.map +1 -0
  9. package/dist/commands/generate.js +197 -0
  10. package/dist/commands/generate.js.map +1 -0
  11. package/dist/commands/init.d.ts +5 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +91 -0
  14. package/dist/commands/init.js.map +1 -0
  15. package/dist/commands/open.d.ts +8 -0
  16. package/dist/commands/open.d.ts.map +1 -0
  17. package/dist/commands/open.js +150 -0
  18. package/dist/commands/open.js.map +1 -0
  19. package/dist/config/loader.d.ts +20 -0
  20. package/dist/config/loader.d.ts.map +1 -0
  21. package/dist/config/loader.js +143 -0
  22. package/dist/config/loader.js.map +1 -0
  23. package/dist/config/patterns.d.ts +9 -0
  24. package/dist/config/patterns.d.ts.map +1 -0
  25. package/dist/config/patterns.js +159 -0
  26. package/dist/config/patterns.js.map +1 -0
  27. package/dist/core/Logger.d.ts +8 -0
  28. package/dist/core/Logger.d.ts.map +1 -0
  29. package/dist/core/Logger.js +33 -0
  30. package/dist/core/Logger.js.map +1 -0
  31. package/dist/core/paths.d.ts +8 -0
  32. package/dist/core/paths.d.ts.map +1 -0
  33. package/dist/core/paths.js +45 -0
  34. package/dist/core/paths.js.map +1 -0
  35. package/dist/discover/specs.d.ts +11 -0
  36. package/dist/discover/specs.d.ts.map +1 -0
  37. package/dist/discover/specs.js +157 -0
  38. package/dist/discover/specs.js.map +1 -0
  39. package/dist/output/html/assets/highlight.css +101 -0
  40. package/dist/output/html/assets/search.js +368 -0
  41. package/dist/output/html/assets/specwiki.css +905 -0
  42. package/dist/output/html/renderer.d.ts +21 -0
  43. package/dist/output/html/renderer.d.ts.map +1 -0
  44. package/dist/output/html/renderer.js +203 -0
  45. package/dist/output/html/renderer.js.map +1 -0
  46. package/dist/output/html/search-index.d.ts +20 -0
  47. package/dist/output/html/search-index.d.ts.map +1 -0
  48. package/dist/output/html/search-index.js +46 -0
  49. package/dist/output/html/search-index.js.map +1 -0
  50. package/dist/output/html/templates/article.mustache +85 -0
  51. package/dist/output/html/templates/index.mustache +80 -0
  52. package/dist/output/html/templates/layout.mustache +205 -0
  53. package/dist/output/llms.d.ts +4 -0
  54. package/dist/output/llms.d.ts.map +1 -0
  55. package/dist/output/llms.js +44 -0
  56. package/dist/output/llms.js.map +1 -0
  57. package/dist/output/readme-index.d.ts +17 -0
  58. package/dist/output/readme-index.d.ts.map +1 -0
  59. package/dist/output/readme-index.js +71 -0
  60. package/dist/output/readme-index.js.map +1 -0
  61. package/dist/output/wiki.d.ts +11 -0
  62. package/dist/output/wiki.d.ts.map +1 -0
  63. package/dist/output/wiki.js +400 -0
  64. package/dist/output/wiki.js.map +1 -0
  65. package/dist/parse/markdown.d.ts +6 -0
  66. package/dist/parse/markdown.d.ts.map +1 -0
  67. package/dist/parse/markdown.js +225 -0
  68. package/dist/parse/markdown.js.map +1 -0
  69. package/dist/types.d.ts +96 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +2 -0
  72. package/dist/types.js.map +1 -0
  73. package/package.json +67 -0
@@ -0,0 +1,197 @@
1
+ import chalk from "chalk";
2
+ import path from "node:path";
3
+ import { DEFAULT_SPEC_PATTERNS } from "../config/patterns.js";
4
+ import { log } from "../core/Logger.js";
5
+ import { PathEscapeError, resolveOutputWithinProject } from "../core/paths.js";
6
+ import { discoverSpecs } from "../discover/specs.js";
7
+ import { parseSpecFile } from "../parse/markdown.js";
8
+ import { writeLlmsTxt } from "../output/llms.js";
9
+ import { buildWiki, writeHtmlWiki, writeWiki } from "../output/wiki.js";
10
+ const ZERO_SPECS_TIP = "Tip: specwiki discovers .md and .mdc files anywhere in your project — AGENTS.md, .cursor/rules/, specs/, README.md, and similar paths.";
11
+ function printZeroSpecsMessage() {
12
+ console.log(chalk.yellow("No spec files found."));
13
+ console.log(chalk.dim(ZERO_SPECS_TIP));
14
+ }
15
+ function createListJsonResult(specFiles) {
16
+ const categories = new Map();
17
+ for (const file of specFiles) {
18
+ const category = categories.get(file.category) ?? {
19
+ name: file.category,
20
+ files: [],
21
+ };
22
+ category.files.push({
23
+ relativePath: file.relativePath,
24
+ title: file.title,
25
+ category: file.category,
26
+ });
27
+ categories.set(file.category, category);
28
+ }
29
+ return {
30
+ categories: [...categories.values()].sort((left, right) => left.name.localeCompare(right.name)),
31
+ };
32
+ }
33
+ function createGenerateJsonResult(outputDir, pages) {
34
+ return {
35
+ specCount: pages.length,
36
+ outputDir,
37
+ pages: pages.map(({ slug, title, category, sourcePath, description }) => ({
38
+ slug,
39
+ title,
40
+ category,
41
+ sourcePath,
42
+ description,
43
+ })),
44
+ };
45
+ }
46
+ function printJsonResult(command, result) {
47
+ if ("categories" in result) {
48
+ log.info("output.json", {
49
+ command,
50
+ categoryCount: result.categories.length,
51
+ specCount: result.categories.reduce((count, category) => count + category.files.length, 0),
52
+ });
53
+ }
54
+ else {
55
+ log.info("output.json", {
56
+ command,
57
+ pageCount: result.pages.length,
58
+ specCount: result.specCount,
59
+ });
60
+ }
61
+ console.log(JSON.stringify(result));
62
+ }
63
+ function emitCliCommand(command, options) {
64
+ const patterns = options.patterns ?? DEFAULT_SPEC_PATTERNS;
65
+ const projectRoot = path.resolve(options.projectRoot);
66
+ log.info("cli.command", {
67
+ command,
68
+ projectRoot,
69
+ ...(command === "generate"
70
+ ? { outputDir: path.resolve(projectRoot, options.outputDir) }
71
+ : {}),
72
+ verbose: Boolean(options.verbose),
73
+ patternCount: patterns.length,
74
+ });
75
+ }
76
+ function propagateCliError(command, err) {
77
+ const message = err instanceof Error ? err.message : String(err);
78
+ log.error("cli.error", { command, message });
79
+ const error = err instanceof Error ? err : new Error(message);
80
+ error.cliErrorLogged = true;
81
+ throw error;
82
+ }
83
+ export async function generateWiki(options) {
84
+ log.setVerbose(Boolean(options.verbose));
85
+ const { projectRoot, outputDir } = options;
86
+ const resolvedProjectRoot = path.resolve(projectRoot);
87
+ emitCliCommand("generate", {
88
+ ...options,
89
+ projectRoot: resolvedProjectRoot,
90
+ });
91
+ try {
92
+ let resolvedOutput;
93
+ try {
94
+ resolvedOutput = await resolveOutputWithinProject(resolvedProjectRoot, outputDir);
95
+ }
96
+ catch (err) {
97
+ if (err instanceof PathEscapeError) {
98
+ log.error("output.error", { message: err.message });
99
+ propagateCliError("generate", err);
100
+ }
101
+ throw err;
102
+ }
103
+ const relativeOutput = path.relative(resolvedProjectRoot, resolvedOutput);
104
+ const outputIgnorePath = relativeOutput.length > 0 && !relativeOutput.startsWith("..")
105
+ ? relativeOutput
106
+ : undefined;
107
+ const specFiles = await discoverSpecs({
108
+ projectRoot: resolvedProjectRoot,
109
+ patterns: options.patterns,
110
+ ignorePaths: outputIgnorePath ? [outputIgnorePath] : undefined,
111
+ });
112
+ if (specFiles.length === 0) {
113
+ if (options.json) {
114
+ printJsonResult("generate", {
115
+ specCount: 0,
116
+ outputDir: resolvedOutput,
117
+ pages: [],
118
+ });
119
+ return;
120
+ }
121
+ printZeroSpecsMessage();
122
+ return;
123
+ }
124
+ const parsed = await Promise.all(specFiles.map(parseSpecFile));
125
+ const wiki = buildWiki(parsed);
126
+ const written = await writeWiki(resolvedOutput, wiki);
127
+ const htmlWritten = await writeHtmlWiki(resolvedOutput, wiki, {
128
+ noSearch: options.noSearch,
129
+ });
130
+ if (options.emitLlmsTxt) {
131
+ await writeLlmsTxt(resolvedOutput, wiki.pages);
132
+ }
133
+ log.info("generate.summary", {
134
+ pageCount: wiki.pages.length,
135
+ markdownFiles: written.length,
136
+ htmlFiles: htmlWritten.length,
137
+ });
138
+ if (options.json) {
139
+ printJsonResult("generate", createGenerateJsonResult(resolvedOutput, wiki.pages));
140
+ return;
141
+ }
142
+ console.log(chalk.green(`✓ Generated wiki with ${wiki.pages.length} page(s)`));
143
+ console.log(chalk.dim(` Markdown: ${resolvedOutput}/`));
144
+ console.log(chalk.dim(` HTML: ${resolvedOutput}/html/`));
145
+ console.log(chalk.dim(` ${written.length + htmlWritten.length} files written`));
146
+ }
147
+ catch (err) {
148
+ propagateCliError("generate", err);
149
+ }
150
+ }
151
+ export async function listSpecs(options) {
152
+ log.setVerbose(Boolean(options.verbose));
153
+ const resolvedProjectRoot = path.resolve(options.projectRoot);
154
+ emitCliCommand("list", {
155
+ ...options,
156
+ projectRoot: resolvedProjectRoot,
157
+ });
158
+ try {
159
+ const specFiles = await discoverSpecs({
160
+ projectRoot: resolvedProjectRoot,
161
+ patterns: options.patterns,
162
+ });
163
+ if (specFiles.length === 0) {
164
+ if (options.json) {
165
+ printJsonResult("list", { categories: [] });
166
+ return;
167
+ }
168
+ printZeroSpecsMessage();
169
+ return;
170
+ }
171
+ if (options.json) {
172
+ printJsonResult("list", createListJsonResult(specFiles));
173
+ return;
174
+ }
175
+ const byCategory = new Map();
176
+ for (const file of specFiles) {
177
+ const list = byCategory.get(file.category) ?? [];
178
+ list.push(file);
179
+ byCategory.set(file.category, list);
180
+ }
181
+ console.log(chalk.bold(`Found ${specFiles.length} spec file(s):\n`));
182
+ for (const [category, files] of [...byCategory.entries()].sort()) {
183
+ console.log(chalk.cyan(category));
184
+ for (const file of files) {
185
+ console.log(` ${file.title} — ${file.relativePath}`);
186
+ }
187
+ console.log();
188
+ }
189
+ }
190
+ catch (err) {
191
+ propagateCliError("list", err);
192
+ }
193
+ }
194
+ export function isCliErrorLogged(err) {
195
+ return err instanceof Error && err.cliErrorLogged === true;
196
+ }
197
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { InitOptions } from "../types.js";
2
+ export declare function initConfig(options: InitOptions): Promise<void>;
3
+ export declare function isInitErrorLogged(err: unknown): boolean;
4
+ export declare function getInitExitCode(err: unknown): number;
5
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA6C/C,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDpE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEvD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAKpD"}
@@ -0,0 +1,91 @@
1
+ import chalk from "chalk";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { DEFAULT_SPEC_PATTERNS, validatePatternList, } from "../config/patterns.js";
5
+ import { log } from "../core/Logger.js";
6
+ const CONFIG_JSON = "specwiki.config.json";
7
+ const CONFIG_JS = "specwiki.config.js";
8
+ const CONFIG_EXISTS_MESSAGE = "Config file already exists. Use --force to overwrite specwiki.config.json.";
9
+ const CONFIG_JS_EXISTS_MESSAGE = "specwiki.config.js already exists. Edit it manually or remove it before running --force.";
10
+ function throwInitError(message, exitCode = 2) {
11
+ console.log(chalk.yellow(message));
12
+ log.error("init.error", { message });
13
+ const error = new Error(message);
14
+ error.exitCode = exitCode;
15
+ error.cliErrorLogged = true;
16
+ throw error;
17
+ }
18
+ function assertConfinedUnderProject(projectRoot, targetPath) {
19
+ const relative = path.relative(projectRoot, targetPath);
20
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
21
+ throwInitError("Config file must stay within the project root");
22
+ }
23
+ }
24
+ async function fileExists(filePath) {
25
+ try {
26
+ await fs.access(filePath);
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ function buildScaffoldContent() {
34
+ const patterns = validatePatternList([...DEFAULT_SPEC_PATTERNS]);
35
+ return `${JSON.stringify({ patterns }, null, 2)}\n`;
36
+ }
37
+ export async function initConfig(options) {
38
+ log.setVerbose(Boolean(options.verbose));
39
+ const resolvedProjectRoot = path.resolve(options.projectRoot);
40
+ if (options.verbose) {
41
+ log.info("cli.command", {
42
+ command: "init",
43
+ projectRoot: resolvedProjectRoot,
44
+ force: Boolean(options.force),
45
+ verbose: true,
46
+ });
47
+ }
48
+ const jsonPath = path.join(resolvedProjectRoot, CONFIG_JSON);
49
+ const jsPath = path.join(resolvedProjectRoot, CONFIG_JS);
50
+ assertConfinedUnderProject(resolvedProjectRoot, jsonPath);
51
+ const jsExists = await fileExists(jsPath);
52
+ const jsonExists = await fileExists(jsonPath);
53
+ if (jsExists) {
54
+ throwInitError(CONFIG_JS_EXISTS_MESSAGE);
55
+ }
56
+ if (jsonExists && !options.force) {
57
+ throwInitError(CONFIG_EXISTS_MESSAGE);
58
+ }
59
+ let content;
60
+ try {
61
+ content = buildScaffoldContent();
62
+ }
63
+ catch (err) {
64
+ const message = err instanceof Error ? err.message : "Failed to build config scaffold";
65
+ throwInitError(message);
66
+ }
67
+ try {
68
+ await fs.mkdir(resolvedProjectRoot, { recursive: true });
69
+ await fs.writeFile(jsonPath, content, "utf-8");
70
+ }
71
+ catch (err) {
72
+ const message = err instanceof Error ? err.message : "Failed to write config file";
73
+ throwInitError(message, 1);
74
+ }
75
+ if (options.verbose) {
76
+ log.info("init.write", { sourcePath: CONFIG_JSON });
77
+ }
78
+ console.log(chalk.green(`Created ${CONFIG_JSON}`));
79
+ console.log(chalk.dim(` ${jsonPath}`));
80
+ console.log(chalk.dim("Next: specwiki list, then specwiki generate"));
81
+ }
82
+ export function isInitErrorLogged(err) {
83
+ return err instanceof Error && err.cliErrorLogged === true;
84
+ }
85
+ export function getInitExitCode(err) {
86
+ if (err instanceof Error && typeof err.exitCode === "number") {
87
+ return err.exitCode;
88
+ }
89
+ return 1;
90
+ }
91
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { OpenOptions } from "../types.js";
2
+ type LaunchHandler = (command: string, args: string[]) => Promise<void>;
3
+ export declare function setLaunchHandlerForTests(handler: LaunchHandler): void;
4
+ export declare function resetLaunchHandlerForTests(): void;
5
+ export declare function openWiki(options: OpenOptions): Promise<void>;
6
+ export declare function isOpenErrorLogged(err: unknown): boolean;
7
+ export {};
8
+ //# sourceMappingURL=open.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/commands/open.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ/C,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAOxE,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAErE;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAIjD;AA2FD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DlE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEvD"}
@@ -0,0 +1,150 @@
1
+ import chalk from "chalk";
2
+ import { execFile } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { log } from "../core/Logger.js";
7
+ import { assertConfinedUnder as assertConfinedUnderPath, assertRealpathConfinedUnder as assertRealpathConfinedUnderPath, PathEscapeError, resolveOutputWithinProject, } from "../core/paths.js";
8
+ const execFileAsync = promisify(execFile);
9
+ const INDEX_RELATIVE_PATH = path.join("html", "index.html");
10
+ const MISSING_INDEX_MESSAGE = "Wiki index not found. Run `specwiki generate` first.";
11
+ let launchHandler = async (command, args) => {
12
+ await execFileAsync(command, args);
13
+ };
14
+ export function setLaunchHandlerForTests(handler) {
15
+ launchHandler = handler;
16
+ }
17
+ export function resetLaunchHandlerForTests() {
18
+ launchHandler = async (command, args) => {
19
+ await execFileAsync(command, args);
20
+ };
21
+ }
22
+ function throwOpenError(message) {
23
+ console.log(chalk.yellow(message));
24
+ const error = new Error(message);
25
+ error.cliErrorLogged = true;
26
+ throw error;
27
+ }
28
+ function handlePathEscape(label, err) {
29
+ if (err instanceof PathEscapeError) {
30
+ log.error("open.error", {
31
+ message: `Path escapes allowed directory: ${label}`,
32
+ });
33
+ throwOpenError(err.message);
34
+ }
35
+ throw err;
36
+ }
37
+ function assertConfinedUnder(root, target, label) {
38
+ try {
39
+ assertConfinedUnderPath(root, target, label);
40
+ }
41
+ catch (err) {
42
+ handlePathEscape(label, err);
43
+ }
44
+ }
45
+ async function assertRealpathConfinedUnder(root, target, label) {
46
+ try {
47
+ await assertRealpathConfinedUnderPath(root, target, label);
48
+ }
49
+ catch (err) {
50
+ if (err.code === "ENOENT") {
51
+ return;
52
+ }
53
+ if (err instanceof Error && err.cliErrorLogged) {
54
+ throw err;
55
+ }
56
+ if (err instanceof PathEscapeError) {
57
+ handlePathEscape(label, err);
58
+ }
59
+ const message = err instanceof Error ? err.message : String(err);
60
+ log.error("open.error", { message });
61
+ throwOpenError(message);
62
+ }
63
+ }
64
+ async function resolveIndexPath(projectRoot, outputDir) {
65
+ let resolvedOutput;
66
+ try {
67
+ resolvedOutput = await resolveOutputWithinProject(projectRoot, outputDir);
68
+ }
69
+ catch (err) {
70
+ handlePathEscape("output directory", err);
71
+ }
72
+ const indexPath = path.join(resolvedOutput, INDEX_RELATIVE_PATH);
73
+ assertConfinedUnder(resolvedOutput, indexPath, INDEX_RELATIVE_PATH);
74
+ return indexPath;
75
+ }
76
+ function getBrowserLauncher() {
77
+ switch (process.platform) {
78
+ case "darwin":
79
+ return { command: "open", buildArgs: (indexPath) => [indexPath] };
80
+ case "win32":
81
+ return {
82
+ command: "cmd",
83
+ buildArgs: (indexPath) => ["/c", "start", "", indexPath],
84
+ };
85
+ default:
86
+ return {
87
+ command: "xdg-open",
88
+ buildArgs: (indexPath) => [indexPath],
89
+ };
90
+ }
91
+ }
92
+ export async function openWiki(options) {
93
+ log.setVerbose(Boolean(options.verbose));
94
+ const resolvedProjectRoot = path.resolve(options.projectRoot);
95
+ const resolvedOutput = path.resolve(resolvedProjectRoot, options.outputDir);
96
+ log.info("cli.command", {
97
+ command: "open",
98
+ projectRoot: resolvedProjectRoot,
99
+ outputDir: resolvedOutput,
100
+ verbose: Boolean(options.verbose),
101
+ });
102
+ let indexPath;
103
+ try {
104
+ indexPath = await resolveIndexPath(resolvedProjectRoot, options.outputDir);
105
+ }
106
+ catch (err) {
107
+ if (err instanceof Error && err.cliErrorLogged) {
108
+ throw err;
109
+ }
110
+ const message = err instanceof Error ? err.message : String(err);
111
+ log.error("open.error", { message });
112
+ throwOpenError(message);
113
+ }
114
+ try {
115
+ await fs.access(indexPath);
116
+ await assertRealpathConfinedUnder(resolvedProjectRoot, indexPath, INDEX_RELATIVE_PATH);
117
+ }
118
+ catch (err) {
119
+ if (err instanceof Error && err.cliErrorLogged) {
120
+ throw err;
121
+ }
122
+ const code = err.code;
123
+ if (code === "ENOENT") {
124
+ log.error("open.error", {
125
+ message: MISSING_INDEX_MESSAGE,
126
+ indexPath,
127
+ });
128
+ throwOpenError(MISSING_INDEX_MESSAGE);
129
+ }
130
+ const message = err instanceof Error ? err.message : String(err);
131
+ log.error("open.error", { message, indexPath });
132
+ throwOpenError(`Cannot access wiki index: ${message}`);
133
+ }
134
+ const { command, buildArgs } = getBrowserLauncher();
135
+ try {
136
+ await launchHandler(command, buildArgs(indexPath));
137
+ }
138
+ catch (err) {
139
+ const message = err instanceof Error ? err.message : String(err);
140
+ log.error("open.error", { message, indexPath });
141
+ throwOpenError(`Failed to open browser: ${message}`);
142
+ }
143
+ log.info("open.launch", { indexPath });
144
+ console.log(chalk.green("Opened wiki in browser"));
145
+ console.log(chalk.dim(` ${indexPath}`));
146
+ }
147
+ export function isOpenErrorLogged(err) {
148
+ return err instanceof Error && err.cliErrorLogged === true;
149
+ }
150
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/commands/open.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface SpecwikiConfig {
2
+ patterns?: string[];
3
+ }
4
+ export interface ResolvedPatterns {
5
+ patterns?: string[];
6
+ configSource?: string;
7
+ }
8
+ export declare class ConfigError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ export declare function loadProjectConfig(projectRoot: string): Promise<{
12
+ config: SpecwikiConfig;
13
+ sourcePath: string;
14
+ } | null>;
15
+ export declare function resolvePatternsFromEnv(): string[] | undefined;
16
+ export declare function resolveEffectivePatterns(options: {
17
+ projectRoot: string;
18
+ cliPatterns?: string[];
19
+ }): Promise<ResolvedPatterns>;
20
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA4CD,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AA6BD,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAwDhE;AAED,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,GAAG,SAAS,CAa7D;AAED,wBAAsB,wBAAwB,CAAC,OAAO,EAAE;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB5B"}
@@ -0,0 +1,143 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { parsePatternList, validatePatternList } from "./patterns.js";
5
+ const CONFIG_CANDIDATES = [
6
+ "specwiki.config.js",
7
+ "specwiki.config.json",
8
+ ];
9
+ function assertConfigPathWithinProject(projectRoot, configPath, realConfigPath) {
10
+ const relative = path.relative(projectRoot, realConfigPath);
11
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
12
+ throw new ConfigError("Config file must stay within the project root");
13
+ }
14
+ if (path.basename(realConfigPath) !== path.basename(configPath)) {
15
+ throw new ConfigError("Config file must stay within the project root");
16
+ }
17
+ }
18
+ async function resolveConfigPath(projectRoot, filename) {
19
+ const resolvedProjectRoot = path.resolve(projectRoot);
20
+ const configPath = path.join(resolvedProjectRoot, filename);
21
+ const realProjectRoot = await fs.realpath(resolvedProjectRoot);
22
+ const realConfigPath = await fs.realpath(configPath);
23
+ assertConfigPathWithinProject(realProjectRoot, configPath, realConfigPath);
24
+ return realConfigPath;
25
+ }
26
+ function loadConfigFailureMessage(filename, kind) {
27
+ if (kind === "json") {
28
+ return `Invalid JSON in ${filename}`;
29
+ }
30
+ return `Failed to load ${filename}`;
31
+ }
32
+ export class ConfigError extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "ConfigError";
36
+ }
37
+ }
38
+ function normalizeConfig(raw) {
39
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
40
+ throw new ConfigError("Config must export a plain object");
41
+ }
42
+ const config = raw;
43
+ if (config.patterns === undefined) {
44
+ return {};
45
+ }
46
+ if (!Array.isArray(config.patterns)) {
47
+ throw new ConfigError("Config patterns must be an array of glob strings");
48
+ }
49
+ if (!config.patterns.every((pattern) => typeof pattern === "string")) {
50
+ throw new ConfigError("Config patterns must be an array of glob strings");
51
+ }
52
+ try {
53
+ return { patterns: validatePatternList(config.patterns) };
54
+ }
55
+ catch (err) {
56
+ const message = err instanceof Error ? err.message : "Invalid config patterns";
57
+ throw new ConfigError(message);
58
+ }
59
+ }
60
+ export async function loadProjectConfig(projectRoot) {
61
+ for (const filename of CONFIG_CANDIDATES) {
62
+ const configPath = path.join(projectRoot, filename);
63
+ try {
64
+ await fs.access(configPath);
65
+ }
66
+ catch {
67
+ continue;
68
+ }
69
+ try {
70
+ const resolvedConfigPath = await resolveConfigPath(projectRoot, filename);
71
+ if (filename.endsWith(".json")) {
72
+ let raw;
73
+ try {
74
+ raw = JSON.parse(await fs.readFile(resolvedConfigPath, "utf-8"));
75
+ }
76
+ catch (err) {
77
+ if (err instanceof SyntaxError) {
78
+ throw new ConfigError(loadConfigFailureMessage(filename, "json"));
79
+ }
80
+ throw new ConfigError(loadConfigFailureMessage(filename, "json"));
81
+ }
82
+ return {
83
+ config: normalizeConfig(raw),
84
+ sourcePath: filename,
85
+ };
86
+ }
87
+ let module;
88
+ try {
89
+ module = await import(pathToFileURL(resolvedConfigPath).href);
90
+ }
91
+ catch (err) {
92
+ if (err instanceof SyntaxError) {
93
+ throw new ConfigError(`Invalid JavaScript in ${filename}`);
94
+ }
95
+ throw new ConfigError(loadConfigFailureMessage(filename, "js"));
96
+ }
97
+ const exported = module.default ?? module;
98
+ return {
99
+ config: normalizeConfig(exported),
100
+ sourcePath: filename,
101
+ };
102
+ }
103
+ catch (err) {
104
+ if (err instanceof ConfigError) {
105
+ throw err;
106
+ }
107
+ const kind = filename.endsWith(".json") ? "json" : "js";
108
+ throw new ConfigError(loadConfigFailureMessage(filename, kind));
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ export function resolvePatternsFromEnv() {
114
+ const value = process.env.SPECWIKI_PATTERNS;
115
+ if (value === undefined || value.trim().length === 0) {
116
+ return undefined;
117
+ }
118
+ try {
119
+ return parsePatternList(value);
120
+ }
121
+ catch (err) {
122
+ const message = err instanceof Error ? err.message : "Invalid SPECWIKI_PATTERNS";
123
+ throw new ConfigError(message);
124
+ }
125
+ }
126
+ export async function resolveEffectivePatterns(options) {
127
+ if (options.cliPatterns) {
128
+ return { patterns: options.cliPatterns };
129
+ }
130
+ const envPatterns = resolvePatternsFromEnv();
131
+ if (envPatterns) {
132
+ return { patterns: envPatterns };
133
+ }
134
+ const loaded = await loadProjectConfig(options.projectRoot);
135
+ if (loaded) {
136
+ return {
137
+ patterns: loaded.config.patterns,
138
+ configSource: loaded.sourcePath,
139
+ };
140
+ }
141
+ return {};
142
+ }
143
+ //# sourceMappingURL=loader.js.map