@stritti/vitepress-plugin-openspec 0.7.1 → 0.8.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.
- package/README.md +12 -2
- package/dist/index.cjs +535 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +535 -134
- package/dist/index.js.map +1 -1
- package/dist/lib/detect.d.ts +43 -0
- package/dist/lib/detect.d.ts.map +1 -0
- package/dist/lib/dev-server.d.ts +28 -0
- package/dist/lib/dev-server.d.ts.map +1 -0
- package/dist/lib/generator.d.ts +12 -0
- package/dist/lib/generator.d.ts.map +1 -0
- package/dist/lib/links.d.ts +10 -0
- package/dist/lib/links.d.ts.map +1 -0
- package/dist/lib/logger.d.ts +5 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/markdown.d.ts +6 -0
- package/dist/lib/markdown.d.ts.map +1 -0
- package/dist/lib/navigation.d.ts +16 -0
- package/dist/lib/navigation.d.ts.map +1 -0
- package/dist/lib/options.d.ts +28 -0
- package/dist/lib/options.d.ts.map +1 -0
- package/dist/lib/pages.d.ts +22 -0
- package/dist/lib/pages.d.ts.map +1 -0
- package/dist/lib/reader.d.ts +10 -0
- package/dist/lib/reader.d.ts.map +1 -0
- package/dist/lib/watch.d.ts +36 -0
- package/dist/lib/watch.d.ts.map +1 -0
- package/dist/plugin.d.ts +21 -50
- package/dist/plugin.d.ts.map +1 -1
- package/dist/types.d.ts +16 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +6 -57
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -14,6 +14,204 @@ var path__default = /*#__PURE__*/_interopDefault(path);
|
|
|
14
14
|
var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
15
15
|
|
|
16
16
|
// src/plugin.ts
|
|
17
|
+
var PLUGIN_NAME = "vitepress-plugin-openspec";
|
|
18
|
+
function prefix(color) {
|
|
19
|
+
return pc__default.default.bold(color(`[${PLUGIN_NAME}]`));
|
|
20
|
+
}
|
|
21
|
+
function logWarn(message) {
|
|
22
|
+
console.warn(`${prefix(pc__default.default.yellow)} ${message}`);
|
|
23
|
+
}
|
|
24
|
+
function logInfo(message) {
|
|
25
|
+
console.log(`${prefix(pc__default.default.cyan)} ${message}`);
|
|
26
|
+
}
|
|
27
|
+
function logError(message) {
|
|
28
|
+
console.error(`${prefix(pc__default.default.red)} ${message}`);
|
|
29
|
+
}
|
|
30
|
+
var GENERATED_MARKER = "# Generated by vitepress-plugin-openspec";
|
|
31
|
+
function looksLikeOpenSpecDir(dir) {
|
|
32
|
+
const gitignore = path__default.default.join(dir, ".gitignore");
|
|
33
|
+
if (fs__default.default.existsSync(gitignore)) {
|
|
34
|
+
try {
|
|
35
|
+
if (fs__default.default.readFileSync(gitignore, "utf-8").startsWith(GENERATED_MARKER)) return false;
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (fs__default.default.existsSync(path__default.default.join(dir, "project.md"))) return "strong";
|
|
40
|
+
if (fs__default.default.existsSync(path__default.default.join(dir, "config.yaml")) || fs__default.default.existsSync(path__default.default.join(dir, "config.yml"))) return "strong";
|
|
41
|
+
const changesDir = path__default.default.join(dir, "changes");
|
|
42
|
+
if (fs__default.default.existsSync(changesDir)) {
|
|
43
|
+
try {
|
|
44
|
+
for (const entry of fs__default.default.readdirSync(changesDir, { withFileTypes: true })) {
|
|
45
|
+
if (!entry.isDirectory() && !entry.name.endsWith(".yaml")) continue;
|
|
46
|
+
const marker = path__default.default.join(changesDir, entry.name, ".openspec.yaml");
|
|
47
|
+
const yaml = path__default.default.join(changesDir, `${entry.name}.yaml`);
|
|
48
|
+
if (fs__default.default.existsSync(marker) || fs__default.default.existsSync(yaml)) return "strong";
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const specsDir = path__default.default.join(dir, "specs");
|
|
54
|
+
if (fs__default.default.existsSync(specsDir)) {
|
|
55
|
+
try {
|
|
56
|
+
for (const entry of fs__default.default.readdirSync(specsDir, { withFileTypes: true })) {
|
|
57
|
+
if (!entry.isDirectory()) continue;
|
|
58
|
+
if (fs__default.default.existsSync(path__default.default.join(specsDir, entry.name, "spec.md"))) return "strong";
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const archiveDir = path__default.default.join(changesDir, "archive");
|
|
64
|
+
if (fs__default.default.existsSync(archiveDir)) {
|
|
65
|
+
try {
|
|
66
|
+
if (fs__default.default.readdirSync(archiveDir, { withFileTypes: true }).some((e) => e.isDirectory())) return "weak";
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
function findOpenSpecDir(start, generatedOutputDir) {
|
|
73
|
+
let current = path__default.default.resolve(start);
|
|
74
|
+
for (; ; ) {
|
|
75
|
+
const candidate = path__default.default.join(current, "openspec");
|
|
76
|
+
if (fs__default.default.existsSync(candidate)) {
|
|
77
|
+
const match = looksLikeOpenSpecDir(candidate);
|
|
78
|
+
if (match === "weak" && generatedOutputDir && realPath(candidate) === realPath(generatedOutputDir)) {
|
|
79
|
+
const parent2 = path__default.default.dirname(current);
|
|
80
|
+
if (parent2 === current) return void 0;
|
|
81
|
+
current = parent2;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (match === "strong" || match === "weak") return candidate;
|
|
85
|
+
}
|
|
86
|
+
const parent = path__default.default.dirname(current);
|
|
87
|
+
if (parent === current) return void 0;
|
|
88
|
+
current = parent;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function realPath(target) {
|
|
92
|
+
let current = path__default.default.resolve(target);
|
|
93
|
+
const suffix = [];
|
|
94
|
+
for (; ; ) {
|
|
95
|
+
if (fs__default.default.existsSync(current)) break;
|
|
96
|
+
const parent = path__default.default.dirname(current);
|
|
97
|
+
if (parent === current) return path__default.default.resolve(target);
|
|
98
|
+
suffix.unshift(path__default.default.basename(current));
|
|
99
|
+
current = parent;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
current = fs__default.default.realpathSync(current);
|
|
103
|
+
} catch {
|
|
104
|
+
return path__default.default.resolve(target);
|
|
105
|
+
}
|
|
106
|
+
return suffix.length ? path__default.default.join(current, ...suffix) : current;
|
|
107
|
+
}
|
|
108
|
+
var srcDirCache = /* @__PURE__ */ new Map();
|
|
109
|
+
function detectSrcDir(cwd) {
|
|
110
|
+
const key = path__default.default.resolve(cwd);
|
|
111
|
+
if (srcDirCache.has(key)) return srcDirCache.get(key);
|
|
112
|
+
const result = resolveSrcDir(key);
|
|
113
|
+
srcDirCache.set(key, result);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
function resolveSrcDir(cwd) {
|
|
117
|
+
if (path__default.default.basename(cwd) === ".vitepress") return path__default.default.dirname(cwd);
|
|
118
|
+
if (fs__default.default.existsSync(path__default.default.join(cwd, ".vitepress"))) return cwd;
|
|
119
|
+
const matches = [];
|
|
120
|
+
try {
|
|
121
|
+
for (const entry of fs__default.default.readdirSync(cwd, { withFileTypes: true })) {
|
|
122
|
+
if (!entry.isDirectory()) continue;
|
|
123
|
+
const subDir = path__default.default.join(cwd, entry.name);
|
|
124
|
+
if (fs__default.default.existsSync(path__default.default.join(subDir, ".vitepress"))) matches.push(subDir);
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
if (matches.length === 1) return matches[0];
|
|
129
|
+
if (matches.length > 1) {
|
|
130
|
+
const docsMatch = matches.find((m) => path__default.default.basename(m) === "docs");
|
|
131
|
+
if (docsMatch) return docsMatch;
|
|
132
|
+
logWarn(
|
|
133
|
+
`Multiple VitePress source directories found (${matches.map((m) => path__default.default.relative(cwd, m)).join(", ")}) \u2014 pass an explicit \`srcDir\` option. Skipping page generation.`
|
|
134
|
+
);
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
return cwd;
|
|
138
|
+
}
|
|
139
|
+
function detectVitePressRoot(cwd) {
|
|
140
|
+
const srcDir = detectSrcDir(cwd);
|
|
141
|
+
return srcDir && fs__default.default.existsSync(path__default.default.join(srcDir, ".vitepress")) ? srcDir : cwd;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/lib/options.ts
|
|
145
|
+
function resolveOptions(userOptions = {}, fallbackSrcDir) {
|
|
146
|
+
const outDir = (userOptions.outDir ?? "openspec").replace(/\\/g, "/");
|
|
147
|
+
if (path__default.default.isAbsolute(outDir) || outDir === "" || outDir === "." || outDir === "..") {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`[vitepress-plugin-openspec] Invalid outDir "${outDir}": it must be a relative path inside the VitePress srcDir.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const srcDir = userOptions.srcDir ?? fallbackSrcDir ?? detectSrcDir(process.cwd()) ?? process.cwd();
|
|
153
|
+
const absoluteSrcDir = path__default.default.resolve(srcDir);
|
|
154
|
+
const absoluteOutDir = path__default.default.resolve(absoluteSrcDir, outDir);
|
|
155
|
+
const rel = path__default.default.relative(absoluteSrcDir, absoluteOutDir);
|
|
156
|
+
if (rel.startsWith("..") || path__default.default.isAbsolute(rel) || rel === "") {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`[vitepress-plugin-openspec] Invalid outDir "${outDir}": it resolves outside the VitePress srcDir.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const specDir = userOptions.specDir ?? findOpenSpecDir(process.cwd(), absoluteOutDir) ?? "./openspec";
|
|
162
|
+
return {
|
|
163
|
+
specDir: path__default.default.resolve(specDir),
|
|
164
|
+
outDir,
|
|
165
|
+
srcDir: absoluteSrcDir,
|
|
166
|
+
absoluteOutDir
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
var ALWAYS_GENERATED_BASENAMES = /* @__PURE__ */ new Set(["index.md", ".gitignore", ".openspec-manifest.json"]);
|
|
170
|
+
var ARTIFACT_BASENAMES = /* @__PURE__ */ new Set(["proposal.md", "design.md", "tasks.md"]);
|
|
171
|
+
var generatedDestCache = /* @__PURE__ */ new Map();
|
|
172
|
+
function generatedDestinations(absoluteOutDir) {
|
|
173
|
+
const cached = generatedDestCache.get(absoluteOutDir);
|
|
174
|
+
if (cached) return cached;
|
|
175
|
+
const set = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const entry of readManifest(absoluteOutDir)) {
|
|
177
|
+
set.add(path__default.default.resolve(absoluteOutDir, entry));
|
|
178
|
+
}
|
|
179
|
+
generatedDestCache.set(absoluteOutDir, set);
|
|
180
|
+
return set;
|
|
181
|
+
}
|
|
182
|
+
function invalidateGeneratedDestinations(absoluteOutDir) {
|
|
183
|
+
generatedDestCache.delete(absoluteOutDir);
|
|
184
|
+
}
|
|
185
|
+
function isInside(parent, child) {
|
|
186
|
+
const rel = path__default.default.relative(parent, child);
|
|
187
|
+
return !(rel.startsWith("..") || path__default.default.isAbsolute(rel));
|
|
188
|
+
}
|
|
189
|
+
function classifyLayout(options) {
|
|
190
|
+
if (options.specDir === options.absoluteOutDir) return "equal";
|
|
191
|
+
if (isInside(options.specDir, options.absoluteOutDir)) return "out-inside-spec";
|
|
192
|
+
if (isInside(options.absoluteOutDir, options.specDir)) return "spec-inside-out";
|
|
193
|
+
return "disjoint";
|
|
194
|
+
}
|
|
195
|
+
function isPluginGeneratedFile(options, absFile) {
|
|
196
|
+
if (!isInside(options.absoluteOutDir, absFile)) return false;
|
|
197
|
+
if (generatedDestinations(options.absoluteOutDir).has(path__default.default.resolve(absFile))) return true;
|
|
198
|
+
const base = path__default.default.basename(absFile);
|
|
199
|
+
switch (classifyLayout(options)) {
|
|
200
|
+
case "equal":
|
|
201
|
+
return ALWAYS_GENERATED_BASENAMES.has(base);
|
|
202
|
+
case "out-inside-spec":
|
|
203
|
+
return ALWAYS_GENERATED_BASENAMES.has(base) && !isInside(options.specDir, absFile);
|
|
204
|
+
case "disjoint":
|
|
205
|
+
return ALWAYS_GENERATED_BASENAMES.has(base) || ARTIFACT_BASENAMES.has(base);
|
|
206
|
+
case "spec-inside-out":
|
|
207
|
+
return !isInside(options.specDir, absFile) && (ALWAYS_GENERATED_BASENAMES.has(base) || ARTIFACT_BASENAMES.has(base));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function isSourceArtifactEvent(options, absFile) {
|
|
211
|
+
if (!isInside(options.specDir, absFile)) return false;
|
|
212
|
+
return !isPluginGeneratedFile(options, absFile);
|
|
213
|
+
}
|
|
214
|
+
var ARTIFACT_NAMES = ["proposal", "design", "tasks"];
|
|
17
215
|
function readOpenSpecYaml(dir) {
|
|
18
216
|
const yamlPath = path__default.default.join(dir, ".openspec.yaml");
|
|
19
217
|
if (!fs__default.default.existsSync(yamlPath)) return {};
|
|
@@ -67,11 +265,10 @@ function formatDate(val) {
|
|
|
67
265
|
return String(val);
|
|
68
266
|
}
|
|
69
267
|
function readArtifacts(dir) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return artifacts;
|
|
268
|
+
return ARTIFACT_NAMES.filter((name) => fs__default.default.existsSync(path__default.default.join(dir, `${name}.md`)));
|
|
269
|
+
}
|
|
270
|
+
function isDirectory(dir, name) {
|
|
271
|
+
return fs__default.default.existsSync(path__default.default.join(dir, name, ".openspec.yaml"));
|
|
75
272
|
}
|
|
76
273
|
function readOpenSpecFolder(dir) {
|
|
77
274
|
const resolved = path__default.default.resolve(dir);
|
|
@@ -100,7 +297,7 @@ function readOpenSpecFolder(dir) {
|
|
|
100
297
|
for (const entry of fs__default.default.readdirSync(changesDir, { withFileTypes: true })) {
|
|
101
298
|
if (!entry.isDirectory() || entry.name === "archive") continue;
|
|
102
299
|
const changeDir = path__default.default.join(changesDir, entry.name);
|
|
103
|
-
if (!
|
|
300
|
+
if (!isDirectory(changeDir, ".")) continue;
|
|
104
301
|
const meta = readOpenSpecYaml(changeDir);
|
|
105
302
|
changes.push({
|
|
106
303
|
name: entry.name,
|
|
@@ -117,23 +314,52 @@ function readOpenSpecFolder(dir) {
|
|
|
117
314
|
for (const entry of fs__default.default.readdirSync(archiveDir, { withFileTypes: true })) {
|
|
118
315
|
if (!entry.isDirectory()) continue;
|
|
119
316
|
const changeDir = path__default.default.join(archiveDir, entry.name);
|
|
120
|
-
const match = entry.name.match(/^(\d{4}-\d{2}-\d{2})-(.+)$/);
|
|
121
|
-
const archivedDate = match?.[1];
|
|
122
|
-
const name = match?.[2] ?? entry.name;
|
|
123
317
|
const meta = readOpenSpecYaml(changeDir);
|
|
318
|
+
const match = entry.name.match(/^(\d{4}-\d{2}-\d{2})-(.+)$/);
|
|
124
319
|
archivedChanges.push({
|
|
125
|
-
name,
|
|
320
|
+
name: match?.[2] ?? entry.name,
|
|
126
321
|
title: meta.title ? String(meta.title) : void 0,
|
|
127
322
|
dir: changeDir,
|
|
128
323
|
artifacts: readArtifacts(changeDir),
|
|
129
324
|
createdDate: formatDate(meta.created),
|
|
130
|
-
archivedDate,
|
|
325
|
+
archivedDate: match?.[1],
|
|
131
326
|
archiveFolderName: entry.name
|
|
132
327
|
});
|
|
133
328
|
}
|
|
134
329
|
}
|
|
135
330
|
return { dir: resolved, specs, changes, archivedChanges };
|
|
136
331
|
}
|
|
332
|
+
function rewriteRelativeLinks(content, srcFilePath, openspecRootDir, outDir) {
|
|
333
|
+
const srcDir = path__default.default.dirname(srcFilePath);
|
|
334
|
+
return content.replace(
|
|
335
|
+
/(\[[^\]]*\])\(([^)]+)\)/g,
|
|
336
|
+
(_match, linkText, urlPart) => {
|
|
337
|
+
const titleMatch = urlPart.match(/^(.+?)(\s+["'][^"']*["'])?\s*$/);
|
|
338
|
+
if (!titleMatch) return _match;
|
|
339
|
+
const rawUrl = titleMatch[1].trim();
|
|
340
|
+
const title = titleMatch[2] ?? "";
|
|
341
|
+
if (!rawUrl || rawUrl.startsWith("http://") || rawUrl.startsWith("https://") || rawUrl.startsWith("//") || rawUrl.startsWith("/") || rawUrl.startsWith("#") || rawUrl.startsWith("mailto:") || rawUrl.startsWith("tel:")) {
|
|
342
|
+
return _match;
|
|
343
|
+
}
|
|
344
|
+
const hashIdx = rawUrl.indexOf("#");
|
|
345
|
+
const urlWithoutFragment = hashIdx >= 0 ? rawUrl.slice(0, hashIdx) : rawUrl;
|
|
346
|
+
const fragment = hashIdx >= 0 ? rawUrl.slice(hashIdx) : "";
|
|
347
|
+
if (!urlWithoutFragment) {
|
|
348
|
+
return _match;
|
|
349
|
+
}
|
|
350
|
+
const resolvedPath = path__default.default.resolve(srcDir, urlWithoutFragment);
|
|
351
|
+
const relToOpenspec = path__default.default.relative(openspecRootDir, resolvedPath);
|
|
352
|
+
if (relToOpenspec.startsWith("..") || path__default.default.isAbsolute(relToOpenspec)) {
|
|
353
|
+
return _match;
|
|
354
|
+
}
|
|
355
|
+
const vitePath = relToOpenspec.replace(/\.md$/, "").replace(/\\/g, "/");
|
|
356
|
+
const absoluteLink = `/${outDir}/${vitePath}${fragment}`;
|
|
357
|
+
return `${linkText}(${absoluteLink}${title})`;
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/lib/markdown.ts
|
|
137
363
|
function extractSpecDescription(content) {
|
|
138
364
|
const reqMatch = content.match(/^### Requirement:[^\n]*\n+([\s\S]*?)(?=\n#{1,4} |\n*$)/m);
|
|
139
365
|
if (!reqMatch) return void 0;
|
|
@@ -146,7 +372,7 @@ function extractSpecDescription(content) {
|
|
|
146
372
|
const cut = sentence.lastIndexOf(" ", 160);
|
|
147
373
|
sentence = (cut > 0 ? sentence.slice(0, cut) : sentence.slice(0, 160)) + "\u2026";
|
|
148
374
|
}
|
|
149
|
-
return sentence.replace(/"/g,
|
|
375
|
+
return sentence.replace(/([\\"])/g, "\\$1");
|
|
150
376
|
}
|
|
151
377
|
function stripDeltaMarkers(content) {
|
|
152
378
|
const stripped = content.split("\n").filter((line) => !/^## (ADDED|MODIFIED|REMOVED) Requirements\s*$/.test(line)).join("\n");
|
|
@@ -175,6 +401,11 @@ function transformScenarios(content) {
|
|
|
175
401
|
if (inScenario) result.push(":::");
|
|
176
402
|
return result.join("\n");
|
|
177
403
|
}
|
|
404
|
+
function specTitle(spec) {
|
|
405
|
+
return spec.title ?? humanizeLabel(spec.name);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/lib/pages.ts
|
|
178
409
|
function generateSpecPage(spec) {
|
|
179
410
|
const description = extractSpecDescription(spec.content);
|
|
180
411
|
const transformed = transformScenarios(stripDeltaMarkers(spec.content));
|
|
@@ -185,7 +416,7 @@ function generateSpecPage(spec) {
|
|
|
185
416
|
lines.push("---");
|
|
186
417
|
lines.push("");
|
|
187
418
|
}
|
|
188
|
-
lines.push(`# ${
|
|
419
|
+
lines.push(`# ${specTitle(spec)}`);
|
|
189
420
|
lines.push("");
|
|
190
421
|
lines.push(transformed.trimEnd());
|
|
191
422
|
lines.push("");
|
|
@@ -201,7 +432,7 @@ function generateSpecsIndexPage(specs, outDir) {
|
|
|
201
432
|
lines.push("*No specifications defined yet.*");
|
|
202
433
|
} else {
|
|
203
434
|
for (const spec of specs) {
|
|
204
|
-
lines.push(`- [${
|
|
435
|
+
lines.push(`- [${specTitle(spec)}](/${outDir}/specs/${spec.name}/)`);
|
|
205
436
|
}
|
|
206
437
|
}
|
|
207
438
|
lines.push("");
|
|
@@ -221,10 +452,10 @@ function generateChangeIndexPage(change, outDir) {
|
|
|
221
452
|
}
|
|
222
453
|
lines.push("## Artifacts");
|
|
223
454
|
lines.push("");
|
|
224
|
-
const
|
|
455
|
+
const prefix2 = change.archiveFolderName ? `/${outDir}/changes/archive/${change.archiveFolderName}` : `/${outDir}/changes/${change.name}`;
|
|
225
456
|
for (const artifact of change.artifacts) {
|
|
226
457
|
const label = artifact.charAt(0).toUpperCase() + artifact.slice(1);
|
|
227
|
-
lines.push(`- [${label}](${
|
|
458
|
+
lines.push(`- [${label}](${prefix2}/${artifact})`);
|
|
228
459
|
}
|
|
229
460
|
lines.push("");
|
|
230
461
|
return lines.join("\n");
|
|
@@ -257,46 +488,176 @@ function generateChangesIndexPage(folder, outDir) {
|
|
|
257
488
|
lines.push("");
|
|
258
489
|
return lines.join("\n");
|
|
259
490
|
}
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
491
|
+
function generateRootIndexPage(outDir) {
|
|
492
|
+
return [
|
|
493
|
+
"# Project Documentation",
|
|
494
|
+
"",
|
|
495
|
+
"This section is generated from the project's [OpenSpec](https://openspec.dev/) folder.",
|
|
496
|
+
"OpenSpec is a lightweight, file-based workflow for spec-driven development \u2014",
|
|
497
|
+
"it structures your project's capability specifications and change proposals as plain Markdown files.",
|
|
498
|
+
"",
|
|
499
|
+
`- [Specifications](/${outDir}/specs/) \u2014 canonical capability specs`,
|
|
500
|
+
`- [Changes](/${outDir}/changes/) \u2014 active and archived change proposals`,
|
|
501
|
+
""
|
|
502
|
+
].join("\n");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/lib/generator.ts
|
|
506
|
+
var MANIFEST_FILE = ".openspec-manifest.json";
|
|
507
|
+
var GITIGNORE_CONTENT = "# Generated by vitepress-plugin-openspec \u2014 do not commit generated files.\n*\n!.gitignore\n";
|
|
508
|
+
function writeFile(filePath, content) {
|
|
509
|
+
fs__default.default.mkdirSync(path__default.default.dirname(filePath), { recursive: true });
|
|
510
|
+
fs__default.default.writeFileSync(filePath, content, "utf-8");
|
|
511
|
+
}
|
|
512
|
+
function toRel(absoluteOutDir, filePath) {
|
|
513
|
+
return path__default.default.relative(absoluteOutDir, filePath).split(path__default.default.sep).join("/");
|
|
514
|
+
}
|
|
515
|
+
function pruneEmptyDirs(rootDir, startDir) {
|
|
516
|
+
let dir = startDir;
|
|
517
|
+
while (dir.startsWith(rootDir) && dir !== rootDir) {
|
|
518
|
+
try {
|
|
519
|
+
if (fs__default.default.readdirSync(dir).length > 0) break;
|
|
520
|
+
fs__default.default.rmdirSync(dir);
|
|
521
|
+
} catch {
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
dir = path__default.default.dirname(dir);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function cleanupPreviousManifest(rootDir) {
|
|
528
|
+
const manifestPath = path__default.default.join(rootDir, MANIFEST_FILE);
|
|
529
|
+
if (!fs__default.default.existsSync(manifestPath)) return [];
|
|
530
|
+
let previous = [];
|
|
531
|
+
try {
|
|
532
|
+
const parsed = JSON.parse(fs__default.default.readFileSync(manifestPath, "utf-8"));
|
|
533
|
+
if (Array.isArray(parsed)) previous = parsed.filter((f) => typeof f === "string");
|
|
534
|
+
} catch {
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
const retained = [];
|
|
538
|
+
for (const entry of previous) {
|
|
539
|
+
const entryPath = path__default.default.resolve(rootDir, entry);
|
|
540
|
+
const rel = path__default.default.relative(rootDir, entryPath);
|
|
541
|
+
if (rel.startsWith("..") || path__default.default.isAbsolute(rel)) continue;
|
|
542
|
+
try {
|
|
543
|
+
if (fs__default.default.existsSync(entryPath)) {
|
|
544
|
+
fs__default.default.rmSync(entryPath);
|
|
545
|
+
pruneEmptyDirs(rootDir, path__default.default.dirname(entryPath));
|
|
282
546
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return `${linkText}(${absoluteLink}${title})`;
|
|
547
|
+
} catch {
|
|
548
|
+
retained.push(entry);
|
|
286
549
|
}
|
|
287
|
-
|
|
550
|
+
}
|
|
551
|
+
return retained;
|
|
552
|
+
}
|
|
553
|
+
function readManifest(rootDir) {
|
|
554
|
+
try {
|
|
555
|
+
const parsed = JSON.parse(fs__default.default.readFileSync(path__default.default.join(rootDir, MANIFEST_FILE), "utf-8"));
|
|
556
|
+
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string");
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
return [];
|
|
560
|
+
}
|
|
561
|
+
function writeManifest(files, rootDir) {
|
|
562
|
+
const manifestPath = path__default.default.join(rootDir, MANIFEST_FILE);
|
|
563
|
+
try {
|
|
564
|
+
writeFile(manifestPath, JSON.stringify(files, null, 2));
|
|
565
|
+
invalidateGeneratedDestinations(rootDir);
|
|
566
|
+
} catch {
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function renderChangePages(change, options, isArchived) {
|
|
570
|
+
const subPath = isArchived ? path__default.default.join("changes", "archive", change.archiveFolderName ?? change.name) : path__default.default.join("changes", change.name);
|
|
571
|
+
const changeOutDir = path__default.default.join(options.absoluteOutDir, subPath);
|
|
572
|
+
const files = [];
|
|
573
|
+
files.push({
|
|
574
|
+
rel: toRel(options.absoluteOutDir, path__default.default.join(changeOutDir, "index.md")),
|
|
575
|
+
content: generateChangeIndexPage(change, options.outDir)
|
|
576
|
+
});
|
|
577
|
+
for (const artifact of change.artifacts) {
|
|
578
|
+
const srcFile = path__default.default.join(change.dir, `${artifact}.md`);
|
|
579
|
+
const destFile = path__default.default.join(changeOutDir, `${artifact}.md`);
|
|
580
|
+
if (path__default.default.resolve(srcFile) === path__default.default.resolve(destFile)) continue;
|
|
581
|
+
const content = fs__default.default.readFileSync(srcFile, "utf-8");
|
|
582
|
+
files.push({
|
|
583
|
+
rel: toRel(options.absoluteOutDir, destFile),
|
|
584
|
+
content: rewriteRelativeLinks(content, srcFile, options.specDir, options.outDir)
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
return files;
|
|
588
|
+
}
|
|
589
|
+
function renderAllPages(options) {
|
|
590
|
+
const folder = readOpenSpecFolder(options.specDir);
|
|
591
|
+
const files = [];
|
|
592
|
+
for (const spec of folder.specs) {
|
|
593
|
+
const dest = path__default.default.join(options.absoluteOutDir, "specs", spec.name, "index.md");
|
|
594
|
+
files.push({ rel: toRel(options.absoluteOutDir, dest), content: generateSpecPage(spec) });
|
|
595
|
+
}
|
|
596
|
+
files.push({
|
|
597
|
+
rel: "specs/index.md",
|
|
598
|
+
content: generateSpecsIndexPage(folder.specs, options.outDir)
|
|
599
|
+
});
|
|
600
|
+
for (const change of folder.changes) {
|
|
601
|
+
files.push(...renderChangePages(change, options, false));
|
|
602
|
+
}
|
|
603
|
+
for (const change of folder.archivedChanges) {
|
|
604
|
+
files.push(...renderChangePages(change, options, true));
|
|
605
|
+
}
|
|
606
|
+
files.push({
|
|
607
|
+
rel: "changes/index.md",
|
|
608
|
+
content: generateChangesIndexPage(folder, options.outDir)
|
|
609
|
+
});
|
|
610
|
+
files.push({
|
|
611
|
+
rel: "index.md",
|
|
612
|
+
content: generateRootIndexPage(options.outDir)
|
|
613
|
+
});
|
|
614
|
+
return files;
|
|
615
|
+
}
|
|
616
|
+
function generatePages(options) {
|
|
617
|
+
if (!fs__default.default.existsSync(options.specDir)) {
|
|
618
|
+
logWarn(
|
|
619
|
+
`openspec directory not found: ${path__default.default.relative(options.srcDir, options.specDir)} \u2014 skipping page generation`
|
|
620
|
+
);
|
|
621
|
+
const retained2 = cleanupPreviousManifest(options.absoluteOutDir);
|
|
622
|
+
if (retained2.length > 0 || fs__default.default.existsSync(options.absoluteOutDir)) {
|
|
623
|
+
writeManifest(retained2, options.absoluteOutDir);
|
|
624
|
+
}
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
let files;
|
|
628
|
+
try {
|
|
629
|
+
files = renderAllPages(options);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
logError(`Failed to process openspec directory "${options.specDir}": ${String(err)}`);
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
const retained = cleanupPreviousManifest(options.absoluteOutDir);
|
|
635
|
+
try {
|
|
636
|
+
writeFile(path__default.default.join(options.absoluteOutDir, ".gitignore"), GITIGNORE_CONTENT);
|
|
637
|
+
for (const file of files) {
|
|
638
|
+
writeFile(path__default.default.join(options.absoluteOutDir, file.rel), file.content);
|
|
639
|
+
}
|
|
640
|
+
writeManifest([...files.map((f) => f.rel), ...retained], options.absoluteOutDir);
|
|
641
|
+
} catch (err) {
|
|
642
|
+
logError(`Failed to write openspec pages: ${String(err)}`);
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
logInfo(`Generated docs from ${options.specDir}: ${files.length} file(s)`);
|
|
646
|
+
return true;
|
|
288
647
|
}
|
|
289
648
|
function changeItems(change, outDir, isArchived = false) {
|
|
290
|
-
const
|
|
649
|
+
const prefix2 = isArchived ? `/${outDir}/changes/archive/${change.archiveFolderName}` : `/${outDir}/changes/${change.name}`;
|
|
291
650
|
return change.artifacts.map((a) => ({
|
|
292
651
|
text: a.charAt(0).toUpperCase() + a.slice(1),
|
|
293
|
-
link: `${
|
|
652
|
+
link: `${prefix2}/${a}`
|
|
294
653
|
}));
|
|
295
654
|
}
|
|
296
655
|
function generateOpenSpecSidebar(specDir, options = {}) {
|
|
297
656
|
const outDir = options.outDir ?? "openspec";
|
|
298
657
|
if (!fs__default.default.existsSync(path__default.default.resolve(specDir))) {
|
|
299
|
-
|
|
658
|
+
logWarn(
|
|
659
|
+
`openspec directory not found: ${path__default.default.relative(process.cwd(), path__default.default.resolve(specDir))} \u2014 skipping sidebar generation`
|
|
660
|
+
);
|
|
300
661
|
return [];
|
|
301
662
|
}
|
|
302
663
|
const folder = readOpenSpecFolder(specDir);
|
|
@@ -306,7 +667,7 @@ function generateOpenSpecSidebar(specDir, options = {}) {
|
|
|
306
667
|
collapsed: false,
|
|
307
668
|
items: [
|
|
308
669
|
{ text: "Overview", link: `/${outDir}/specs/` },
|
|
309
|
-
...folder.specs.map((s) => ({ text:
|
|
670
|
+
...folder.specs.map((s) => ({ text: specTitle(s), link: `/${outDir}/specs/${s.name}/` }))
|
|
310
671
|
]
|
|
311
672
|
});
|
|
312
673
|
groups.push({
|
|
@@ -337,7 +698,9 @@ function generateOpenSpecSidebar(specDir, options = {}) {
|
|
|
337
698
|
function openspecNav(specDir, options = {}) {
|
|
338
699
|
const outDir = options.outDir ?? "openspec";
|
|
339
700
|
if (!fs__default.default.existsSync(path__default.default.resolve(specDir))) {
|
|
340
|
-
|
|
701
|
+
logWarn(
|
|
702
|
+
`openspec directory not found: ${path__default.default.relative(process.cwd(), path__default.default.resolve(specDir))} \u2014 skipping nav generation`
|
|
703
|
+
);
|
|
341
704
|
return null;
|
|
342
705
|
}
|
|
343
706
|
return {
|
|
@@ -346,113 +709,151 @@ function openspecNav(specDir, options = {}) {
|
|
|
346
709
|
};
|
|
347
710
|
}
|
|
348
711
|
|
|
349
|
-
// src/
|
|
350
|
-
|
|
351
|
-
function writeFile(filePath, content) {
|
|
352
|
-
fs__default.default.mkdirSync(path__default.default.dirname(filePath), { recursive: true });
|
|
353
|
-
fs__default.default.writeFileSync(filePath, content, "utf-8");
|
|
354
|
-
}
|
|
355
|
-
function generateOpenSpecPages(userOptions = {}) {
|
|
356
|
-
const specDir = userOptions.specDir ?? "./openspec";
|
|
357
|
-
const outDir = userOptions.outDir ?? "openspec";
|
|
358
|
-
const srcDir = userOptions.srcDir ?? process.cwd();
|
|
359
|
-
const absoluteOutDir = path__default.default.resolve(srcDir, outDir);
|
|
360
|
-
if (!fs__default.default.existsSync(path__default.default.resolve(specDir))) {
|
|
361
|
-
console.warn(
|
|
362
|
-
`${pc__default.default.bold(pc__default.default.yellow(`[${PLUGIN_NAME}]`))} openspec directory not found: ${path__default.default.relative(srcDir, path__default.default.resolve(specDir))} \u2014 skipping page generation`
|
|
363
|
-
);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
712
|
+
// src/lib/dev-server.ts
|
|
713
|
+
function readNavigationSignature(options) {
|
|
366
714
|
try {
|
|
367
|
-
const folder = readOpenSpecFolder(specDir);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
);
|
|
376
|
-
for (const change of folder.changes) {
|
|
377
|
-
writeChangePage(change, absoluteOutDir, outDir, false, folder.dir);
|
|
378
|
-
}
|
|
379
|
-
for (const change of folder.archivedChanges) {
|
|
380
|
-
writeChangePage(change, absoluteOutDir, outDir, true, folder.dir);
|
|
381
|
-
}
|
|
382
|
-
writeFile(
|
|
383
|
-
path__default.default.join(absoluteOutDir, "changes", "index.md"),
|
|
384
|
-
generateChangesIndexPage(folder, outDir)
|
|
385
|
-
);
|
|
386
|
-
const rootIndex = [
|
|
387
|
-
"# Project Documentation",
|
|
388
|
-
"",
|
|
389
|
-
"This section is generated from the project's [OpenSpec](https://openspec.dev/) folder.",
|
|
390
|
-
"OpenSpec is a lightweight, file-based workflow for spec-driven development \u2014",
|
|
391
|
-
"it structures your project's capability specifications and change proposals as plain Markdown files.",
|
|
392
|
-
"",
|
|
393
|
-
`- [Specifications](/${outDir}/specs/) \u2014 canonical capability specs`,
|
|
394
|
-
`- [Changes](/${outDir}/changes/) \u2014 active and archived change proposals`,
|
|
395
|
-
""
|
|
396
|
-
].join("\n");
|
|
397
|
-
writeFile(path__default.default.join(absoluteOutDir, "index.md"), rootIndex);
|
|
398
|
-
writeFile(
|
|
399
|
-
path__default.default.join(absoluteOutDir, ".gitignore"),
|
|
400
|
-
"# Generated by vitepress-plugin-openspec \u2014 do not commit generated files.\n*\n!.gitignore\n"
|
|
401
|
-
);
|
|
402
|
-
console.log(
|
|
403
|
-
`${pc__default.default.bold(pc__default.default.cyan(`[${PLUGIN_NAME}]`))} Generated docs from ${pc__default.default.cyan(specDir)}: ${pc__default.default.green(String(folder.specs.length))} spec(s), ${pc__default.default.green(String(folder.changes.length))} change(s), ${pc__default.default.green(String(folder.archivedChanges.length))} archived`
|
|
404
|
-
);
|
|
405
|
-
} catch (err) {
|
|
406
|
-
console.error(
|
|
407
|
-
`${pc__default.default.bold(pc__default.default.red(`[${PLUGIN_NAME}]`))} Failed to process openspec directory "${specDir}": ${String(err)}`
|
|
408
|
-
);
|
|
715
|
+
const folder = readOpenSpecFolder(options.specDir);
|
|
716
|
+
return JSON.stringify({
|
|
717
|
+
specs: folder.specs.map((s) => [s.name, s.title ?? null]),
|
|
718
|
+
changes: folder.changes.map((c) => [c.name, c.title ?? null, c.createdDate ?? null, c.artifacts]),
|
|
719
|
+
archived: folder.archivedChanges.map((c) => [c.name, c.title ?? null, c.archivedDate ?? null, c.artifacts])
|
|
720
|
+
});
|
|
721
|
+
} catch {
|
|
722
|
+
return "unreadable";
|
|
409
723
|
}
|
|
410
724
|
}
|
|
725
|
+
function createScheduler() {
|
|
726
|
+
let timer;
|
|
727
|
+
return {
|
|
728
|
+
schedule(options, server) {
|
|
729
|
+
if (timer) clearTimeout(timer);
|
|
730
|
+
timer = setTimeout(() => {
|
|
731
|
+
timer = void 0;
|
|
732
|
+
generatePages(options);
|
|
733
|
+
invalidateGeneratedDestinations(options.absoluteOutDir);
|
|
734
|
+
server.ws.send({ type: "full-reload" });
|
|
735
|
+
}, 100);
|
|
736
|
+
},
|
|
737
|
+
cancel() {
|
|
738
|
+
if (timer) {
|
|
739
|
+
clearTimeout(timer);
|
|
740
|
+
timer = void 0;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/plugin.ts
|
|
747
|
+
function generateOpenSpecPages(userOptions = {}) {
|
|
748
|
+
const detectedSrcDir = userOptions.srcDir ?? detectSrcDir(process.cwd());
|
|
749
|
+
if (!detectedSrcDir) return false;
|
|
750
|
+
const options = resolveOptions(userOptions, detectedSrcDir);
|
|
751
|
+
return generatePages(options);
|
|
752
|
+
}
|
|
411
753
|
function openspec(userOptions = {}) {
|
|
754
|
+
let resolvedOptions;
|
|
755
|
+
let detachListeners;
|
|
412
756
|
return {
|
|
413
757
|
name: PLUGIN_NAME,
|
|
414
758
|
enforce: "pre",
|
|
415
759
|
configResolved(resolvedConfig) {
|
|
760
|
+
detachListeners?.();
|
|
416
761
|
const vpConfig = resolvedConfig.vitepress;
|
|
417
762
|
const srcDir = userOptions.srcDir ?? vpConfig?.srcDir ?? resolvedConfig.root ?? process.cwd();
|
|
418
|
-
|
|
763
|
+
try {
|
|
764
|
+
resolvedOptions = resolveOptions(userOptions, srcDir);
|
|
765
|
+
} catch (err) {
|
|
766
|
+
logWarn(String(err));
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (!fs__default.default.existsSync(resolvedOptions.specDir)) {
|
|
770
|
+
logWarn(
|
|
771
|
+
`openspec directory not found: ${resolvedOptions.specDir} \u2014 skipping page generation`
|
|
772
|
+
);
|
|
773
|
+
generatePages(resolvedOptions);
|
|
774
|
+
resolvedOptions = void 0;
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
generatePages(resolvedOptions);
|
|
778
|
+
},
|
|
779
|
+
configureServer(server) {
|
|
780
|
+
if (!resolvedOptions || !fs__default.default.existsSync(resolvedOptions.specDir)) return;
|
|
781
|
+
const options = resolvedOptions;
|
|
782
|
+
server.watcher.add(options.specDir);
|
|
783
|
+
const scheduler = createScheduler();
|
|
784
|
+
let navigationSignature = readNavigationSignature(options);
|
|
785
|
+
const onWatchEvent = (event, file) => {
|
|
786
|
+
const absFile = path__default.default.resolve(file);
|
|
787
|
+
if (!isSourceArtifactEvent(options, absFile)) return;
|
|
788
|
+
const signature = readNavigationSignature(options);
|
|
789
|
+
const affectsNavigation = event !== "change" || signature !== navigationSignature;
|
|
790
|
+
if (affectsNavigation) {
|
|
791
|
+
scheduler.cancel();
|
|
792
|
+
navigationSignature = signature;
|
|
793
|
+
server.restart(true);
|
|
794
|
+
} else {
|
|
795
|
+
scheduler.schedule(options, server);
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
const onChange = (f) => onWatchEvent("change", f);
|
|
799
|
+
const onAdd = (f) => onWatchEvent("add", f);
|
|
800
|
+
const onUnlink = (f) => onWatchEvent("unlink", f);
|
|
801
|
+
server.watcher.on("change", onChange);
|
|
802
|
+
server.watcher.on("add", onAdd);
|
|
803
|
+
server.watcher.on("unlink", onUnlink);
|
|
804
|
+
detachListeners = () => {
|
|
805
|
+
scheduler.cancel();
|
|
806
|
+
server.watcher.off("change", onChange);
|
|
807
|
+
server.watcher.off("add", onAdd);
|
|
808
|
+
server.watcher.off("unlink", onUnlink);
|
|
809
|
+
server.httpServer?.removeListener("close", detachListeners);
|
|
810
|
+
detachListeners = void 0;
|
|
811
|
+
};
|
|
812
|
+
server.httpServer?.once("close", detachListeners);
|
|
813
|
+
},
|
|
814
|
+
closeBundle() {
|
|
815
|
+
detachListeners?.();
|
|
816
|
+
detachListeners = void 0;
|
|
419
817
|
}
|
|
420
818
|
};
|
|
421
819
|
}
|
|
422
|
-
function writeChangePage(change, absoluteOutDir, outDir, isArchived, openspecRootDir) {
|
|
423
|
-
const subPath = isArchived ? path__default.default.join("changes", "archive", `${change.archivedDate}-${change.name}`) : path__default.default.join("changes", change.name);
|
|
424
|
-
const changeOutDir = path__default.default.join(absoluteOutDir, subPath);
|
|
425
|
-
writeFile(path__default.default.join(changeOutDir, "index.md"), generateChangeIndexPage(change, outDir));
|
|
426
|
-
for (const artifact of change.artifacts) {
|
|
427
|
-
const srcFile = path__default.default.join(change.dir, `${artifact}.md`);
|
|
428
|
-
const destFile = path__default.default.join(changeOutDir, `${artifact}.md`);
|
|
429
|
-
const content = fs__default.default.readFileSync(srcFile, "utf-8");
|
|
430
|
-
writeFile(destFile, rewriteRelativeLinks(content, srcFile, openspecRootDir, outDir));
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
820
|
function withOpenSpec(config, options = {}) {
|
|
434
|
-
const
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
generateOpenSpecPages({ specDir, outDir, srcDir });
|
|
821
|
+
const cwd = process.cwd();
|
|
822
|
+
const configSrcDir = typeof config.srcDir === "string" && config.srcDir ? path__default.default.resolve(detectVitePressRoot(cwd), config.srcDir) : void 0;
|
|
823
|
+
const detectedSrcDir = options.srcDir ?? configSrcDir ?? detectSrcDir(cwd);
|
|
438
824
|
const result = { ...config };
|
|
825
|
+
const themeConfig = result.themeConfig ?? {};
|
|
826
|
+
if (Array.isArray(themeConfig.sidebar)) {
|
|
827
|
+
themeConfig.sidebar = { "/": themeConfig.sidebar };
|
|
828
|
+
}
|
|
829
|
+
result.themeConfig = themeConfig;
|
|
830
|
+
if (detectedSrcDir === void 0) return result;
|
|
831
|
+
let resolved;
|
|
832
|
+
try {
|
|
833
|
+
resolved = resolveOptions(options, detectedSrcDir);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
logWarn(String(err));
|
|
836
|
+
return result;
|
|
837
|
+
}
|
|
838
|
+
const generated = generatePages(resolved);
|
|
439
839
|
const vite = result.vite ?? {};
|
|
440
840
|
const existingPlugins = vite.plugins ?? [];
|
|
441
|
-
result.vite = { ...vite, plugins: [...existingPlugins, openspec({
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
841
|
+
result.vite = { ...vite, plugins: [...existingPlugins, openspec({ ...options })] };
|
|
842
|
+
if (generated && options.nav !== false) {
|
|
843
|
+
const navEntry = openspecNav(resolved.specDir, {
|
|
844
|
+
outDir: resolved.outDir,
|
|
845
|
+
text: options.navText
|
|
846
|
+
});
|
|
445
847
|
const existingNav = themeConfig.nav ?? [];
|
|
446
848
|
themeConfig.nav = navEntry ? [navEntry, ...existingNav] : existingNav;
|
|
447
849
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
};
|
|
850
|
+
const sidebarKey = `/${resolved.outDir}/`;
|
|
851
|
+
const existingSidebar = themeConfig.sidebar ?? {};
|
|
852
|
+
const pluginOwnsSidebarEntry = generated && options.sidebar !== false && !existingSidebar[sidebarKey];
|
|
853
|
+
if (pluginOwnsSidebarEntry) {
|
|
854
|
+
const items = generateOpenSpecSidebar(resolved.specDir, { outDir: resolved.outDir });
|
|
855
|
+
if (items.length > 0) {
|
|
856
|
+
themeConfig.sidebar = { ...existingSidebar, [sidebarKey]: items };
|
|
456
857
|
}
|
|
457
858
|
}
|
|
458
859
|
result.themeConfig = themeConfig;
|