@stritti/vitepress-plugin-openspec 0.3.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 Stephan Strittmatter
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 ADDED
@@ -0,0 +1,145 @@
1
+ # @stritti/vitepress-plugin-openspec
2
+
3
+ A [VitePress](https://vitepress.dev/) plugin that renders your project's [OpenSpec](https://openspec.dev/) folder as structured documentation pages — specs, changes, and artifacts — with sidebar and nav integration.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bun add @stritti/vitepress-plugin-openspec
11
+ # or: npm install @stritti/vitepress-plugin-openspec
12
+ ```
13
+
14
+ The package is also available from [GitHub Packages](https://github.com/stritti/vitepress-plugin-openspec/pkgs/npm/vitepress-plugin-openspec). To install from there, add an `.npmrc` in your project root with a [personal access token](https://github.com/settings/tokens) that has `read:packages` scope:
15
+
16
+ ```
17
+ @stritti:registry=https://npm.pkg.github.com
18
+ //npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Usage
24
+
25
+ Add the following to your `.vitepress/config.ts`:
26
+
27
+ ```typescript
28
+ import { defineConfig } from 'vitepress'
29
+ import path from 'node:path'
30
+ import { fileURLToPath } from 'node:url'
31
+ import openspec, {
32
+ generateOpenSpecPages,
33
+ generateOpenSpecSidebar,
34
+ openspecNav,
35
+ } from '@stritti/vitepress-plugin-openspec'
36
+
37
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
38
+ const specDir = path.resolve(__dirname, '../openspec') // path to your openspec/ folder
39
+
40
+ // Must be called before defineConfig so pages exist when VitePress scans for routes.
41
+ // This is required for first builds and CI environments.
42
+ generateOpenSpecPages({
43
+ specDir,
44
+ outDir: 'openspec', // output directory inside VitePress srcDir
45
+ srcDir: path.resolve(__dirname, '..'), // your docs/ directory
46
+ })
47
+
48
+ export default defineConfig({
49
+ vite: {
50
+ plugins: [
51
+ openspec({ specDir, outDir: 'openspec' }), // keeps pages in sync during dev
52
+ ],
53
+ },
54
+ themeConfig: {
55
+ nav: [
56
+ openspecNav(specDir, { outDir: 'openspec', text: 'Docs' }),
57
+ ],
58
+ sidebar: {
59
+ '/openspec/': generateOpenSpecSidebar(specDir, { outDir: 'openspec' }),
60
+ },
61
+ },
62
+ })
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Options
68
+
69
+ All three APIs (`generateOpenSpecPages`, `openspec`, `generateOpenSpecSidebar`, `openspecNav`) accept the same options object:
70
+
71
+ | Option | Type | Default | Description |
72
+ | --- | --- | --- | --- |
73
+ | `specDir` | `string` | `'./openspec'` | Path to your project's `openspec/` directory |
74
+ | `outDir` | `string` | `'openspec'` | Output directory relative to VitePress `srcDir` |
75
+ | `srcDir` | `string` | `process.cwd()` | VitePress source directory (the `docs/` folder). Required for `generateOpenSpecPages`. |
76
+
77
+ ---
78
+
79
+ ## Output Structure
80
+
81
+ The plugin reads your `openspec/` folder and generates:
82
+
83
+ ```
84
+ openspec/ ← your source (not committed if used as output)
85
+ ├── specs/
86
+ │ └── <capability>/
87
+ │ └── spec.md
88
+ └── changes/
89
+ ├── <change-name>/
90
+ │ ├── .openspec.yaml
91
+ │ ├── proposal.md
92
+ │ ├── design.md
93
+ │ └── tasks.md
94
+ └── archive/
95
+ └── YYYY-MM-DD-<change-name>/
96
+
97
+ docs/openspec/ ← generated by the plugin (add to .gitignore)
98
+ ├── index.md
99
+ ├── specs/
100
+ │ ├── index.md
101
+ │ └── <capability>/
102
+ │ └── index.md
103
+ └── changes/
104
+ ├── index.md
105
+ ├── <change-name>/
106
+ │ ├── index.md
107
+ │ ├── proposal.md
108
+ │ └── tasks.md
109
+ └── archive/
110
+ └── YYYY-MM-DD-<change-name>/
111
+ └── ...
112
+ ```
113
+
114
+ Add the generated output folder to `.gitignore`:
115
+
116
+ ```
117
+ docs/openspec/
118
+ ```
119
+
120
+ ---
121
+
122
+ ## How It Works
123
+
124
+ VitePress scans `srcDir` for `.md` files **before** any Vite plugin hooks run. The plugin solves this with two complementary mechanisms:
125
+
126
+ 1. **`generateOpenSpecPages()`** — called synchronously at the top of `config.ts`, before `defineConfig()`. Ensures all pages exist when VitePress scans for routes (critical for first builds and CI).
127
+ 2. **`openspec()` Vite plugin** — calls `generateOpenSpecPages()` again on every config reload during `vitepress dev`, keeping pages in sync with your source files.
128
+
129
+ ---
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ bun install # Install dependencies
135
+ bun run build # Build the plugin
136
+ bun run dev # Watch mode
137
+ bun test # Run tests
138
+ bun run lint # Type check
139
+ ```
140
+
141
+ ---
142
+
143
+ ## License
144
+
145
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,402 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var fs = require('fs');
6
+ var path2 = require('path');
7
+ var pc = require('picocolors');
8
+ var yaml = require('js-yaml');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
13
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
14
+ var pc__default = /*#__PURE__*/_interopDefault(pc);
15
+ var yaml__default = /*#__PURE__*/_interopDefault(yaml);
16
+
17
+ // src/plugin.ts
18
+ function readOpenSpecYaml(dir) {
19
+ const yamlPath = path2__default.default.join(dir, ".openspec.yaml");
20
+ if (!fs__default.default.existsSync(yamlPath)) return {};
21
+ try {
22
+ return yaml__default.default.load(fs__default.default.readFileSync(yamlPath, "utf-8")) ?? {};
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+ var ACRONYM_DICT = {
28
+ api: "API",
29
+ rest: "REST",
30
+ graphql: "GraphQL",
31
+ grpc: "gRPC",
32
+ openapi: "OpenAPI",
33
+ oauth: "OAuth",
34
+ oauth2: "OAuth2",
35
+ http: "HTTP",
36
+ https: "HTTPS",
37
+ url: "URL",
38
+ uri: "URI",
39
+ sdk: "SDK",
40
+ ui: "UI",
41
+ ux: "UX",
42
+ id: "ID",
43
+ db: "DB",
44
+ sql: "SQL",
45
+ css: "CSS",
46
+ html: "HTML",
47
+ json: "JSON",
48
+ yaml: "YAML",
49
+ xml: "XML",
50
+ jwt: "JWT",
51
+ ci: "CI",
52
+ cd: "CD"
53
+ };
54
+ function humanizeLabel(name) {
55
+ if (!name) return "";
56
+ return name.split("-").map((word) => {
57
+ if (/^v\d+$/.test(word)) return word;
58
+ return ACRONYM_DICT[word] ?? word.charAt(0).toUpperCase() + word.slice(1);
59
+ }).join(" ");
60
+ }
61
+ function parseFrontmatterTitle(content) {
62
+ const match = content.match(/^---\s*\n(?:.*\n)*?title:\s*['"]?([^\n'"]+)['"]?\s*\n/);
63
+ return match?.[1]?.trim() || void 0;
64
+ }
65
+ function formatDate(val) {
66
+ if (!val) return void 0;
67
+ if (val instanceof Date) return val.toISOString().slice(0, 10);
68
+ return String(val);
69
+ }
70
+ function readArtifacts(dir) {
71
+ const artifacts = [];
72
+ for (const name of ["proposal", "design", "tasks"]) {
73
+ if (fs__default.default.existsSync(path2__default.default.join(dir, `${name}.md`))) artifacts.push(name);
74
+ }
75
+ return artifacts;
76
+ }
77
+ function readOpenSpecFolder(dir) {
78
+ const resolved = path2__default.default.resolve(dir);
79
+ if (!fs__default.default.existsSync(resolved)) {
80
+ throw new Error(`[vitepress-plugin-openspec] openspec directory not found: ${resolved}`);
81
+ }
82
+ const specs = [];
83
+ const specsDir = path2__default.default.join(resolved, "specs");
84
+ if (fs__default.default.existsSync(specsDir)) {
85
+ for (const entry of fs__default.default.readdirSync(specsDir, { withFileTypes: true })) {
86
+ if (!entry.isDirectory()) continue;
87
+ const specPath = path2__default.default.join(specsDir, entry.name, "spec.md");
88
+ if (!fs__default.default.existsSync(specPath)) continue;
89
+ const content = fs__default.default.readFileSync(specPath, "utf-8");
90
+ specs.push({
91
+ name: entry.name,
92
+ title: parseFrontmatterTitle(content),
93
+ specPath,
94
+ content
95
+ });
96
+ }
97
+ }
98
+ const changes = [];
99
+ const changesDir = path2__default.default.join(resolved, "changes");
100
+ if (fs__default.default.existsSync(changesDir)) {
101
+ for (const entry of fs__default.default.readdirSync(changesDir, { withFileTypes: true })) {
102
+ if (!entry.isDirectory() || entry.name === "archive") continue;
103
+ const changeDir = path2__default.default.join(changesDir, entry.name);
104
+ if (!fs__default.default.existsSync(path2__default.default.join(changeDir, ".openspec.yaml"))) continue;
105
+ const meta = readOpenSpecYaml(changeDir);
106
+ changes.push({
107
+ name: entry.name,
108
+ title: meta.title ? String(meta.title) : void 0,
109
+ dir: changeDir,
110
+ artifacts: readArtifacts(changeDir),
111
+ createdDate: formatDate(meta.created)
112
+ });
113
+ }
114
+ }
115
+ const archivedChanges = [];
116
+ const archiveDir = path2__default.default.join(changesDir, "archive");
117
+ if (fs__default.default.existsSync(archiveDir)) {
118
+ for (const entry of fs__default.default.readdirSync(archiveDir, { withFileTypes: true })) {
119
+ if (!entry.isDirectory()) continue;
120
+ const changeDir = path2__default.default.join(archiveDir, entry.name);
121
+ const match = entry.name.match(/^(\d{4}-\d{2}-\d{2})-(.+)$/);
122
+ const archivedDate = match?.[1];
123
+ const name = match?.[2] ?? entry.name;
124
+ const meta = readOpenSpecYaml(changeDir);
125
+ archivedChanges.push({
126
+ name,
127
+ title: meta.title ? String(meta.title) : void 0,
128
+ dir: changeDir,
129
+ artifacts: readArtifacts(changeDir),
130
+ createdDate: formatDate(meta.created),
131
+ archivedDate,
132
+ archiveFolderName: entry.name
133
+ });
134
+ }
135
+ }
136
+ return { dir: resolved, specs, changes, archivedChanges };
137
+ }
138
+ function extractSpecDescription(content) {
139
+ const reqMatch = content.match(/^### Requirement:[^\n]*\n+([\s\S]*?)(?=\n#{1,4} |\n*$)/m);
140
+ if (!reqMatch) return void 0;
141
+ const para = reqMatch[1].trim();
142
+ if (!para) return void 0;
143
+ const sentenceMatch = para.match(/^([^.?!]+[.?!])/);
144
+ if (!sentenceMatch) return void 0;
145
+ let sentence = sentenceMatch[1].trim();
146
+ if (sentence.length > 160) {
147
+ const cut = sentence.lastIndexOf(" ", 160);
148
+ sentence = (cut > 0 ? sentence.slice(0, cut) : sentence.slice(0, 160)) + "\u2026";
149
+ }
150
+ return sentence.replace(/"/g, '\\"');
151
+ }
152
+ function stripDeltaMarkers(content) {
153
+ const stripped = content.split("\n").filter((line) => !/^## (ADDED|MODIFIED|REMOVED) Requirements\s*$/.test(line)).join("\n");
154
+ return stripped.replace(/\n{3,}/g, "\n\n");
155
+ }
156
+ function transformScenarios(content) {
157
+ const lines = content.split("\n");
158
+ const result = [];
159
+ let inScenario = false;
160
+ for (const line of lines) {
161
+ const scenarioMatch = line.match(/^#### Scenario: (.+)$/);
162
+ const isHeading = /^#{1,6} /.test(line);
163
+ if (scenarioMatch) {
164
+ if (inScenario) result.push(":::");
165
+ result.push(`:::details ${scenarioMatch[1]}`);
166
+ inScenario = true;
167
+ } else if (isHeading && inScenario) {
168
+ result.push(":::");
169
+ result.push("");
170
+ result.push(line);
171
+ inScenario = false;
172
+ } else {
173
+ result.push(line);
174
+ }
175
+ }
176
+ if (inScenario) result.push(":::");
177
+ return result.join("\n");
178
+ }
179
+ function generateSpecPage(spec) {
180
+ const description = extractSpecDescription(spec.content);
181
+ const transformed = transformScenarios(stripDeltaMarkers(spec.content));
182
+ const lines = [];
183
+ if (description) {
184
+ lines.push("---");
185
+ lines.push(`description: "${description}"`);
186
+ lines.push("---");
187
+ lines.push("");
188
+ }
189
+ lines.push(`# ${spec.title ?? humanizeLabel(spec.name)}`);
190
+ lines.push("");
191
+ lines.push(transformed.trimEnd());
192
+ lines.push("");
193
+ return lines.join("\n");
194
+ }
195
+ function generateSpecsIndexPage(specs, outDir) {
196
+ const lines = [];
197
+ lines.push("# Specifications");
198
+ lines.push("");
199
+ lines.push("Canonical capability specifications for this project.");
200
+ lines.push("");
201
+ if (specs.length === 0) {
202
+ lines.push("*No specifications defined yet.*");
203
+ } else {
204
+ for (const spec of specs) {
205
+ lines.push(`- [${spec.title ?? humanizeLabel(spec.name)}](/${outDir}/specs/${spec.name}/)`);
206
+ }
207
+ }
208
+ lines.push("");
209
+ return lines.join("\n");
210
+ }
211
+ function generateChangeIndexPage(change, outDir) {
212
+ const lines = [];
213
+ lines.push(`# ${change.title ?? humanizeLabel(change.name)}`);
214
+ lines.push("");
215
+ if (change.createdDate) {
216
+ lines.push(`**Created:** ${change.createdDate}`);
217
+ lines.push("");
218
+ }
219
+ if (change.archivedDate) {
220
+ lines.push(`**Archived:** ${change.archivedDate}`);
221
+ lines.push("");
222
+ }
223
+ lines.push("## Artifacts");
224
+ lines.push("");
225
+ const prefix = change.archiveFolderName ? `/${outDir}/changes/archive/${change.archiveFolderName}` : `/${outDir}/changes/${change.name}`;
226
+ for (const artifact of change.artifacts) {
227
+ const label = artifact.charAt(0).toUpperCase() + artifact.slice(1);
228
+ lines.push(`- [${label}](${prefix}/${artifact})`);
229
+ }
230
+ lines.push("");
231
+ return lines.join("\n");
232
+ }
233
+ function generateChangesIndexPage(folder, outDir) {
234
+ const lines = [];
235
+ lines.push("# Changes");
236
+ lines.push("");
237
+ if (folder.changes.length === 0) {
238
+ lines.push("*No active changes.*");
239
+ } else {
240
+ lines.push("## Active");
241
+ lines.push("");
242
+ for (const change of folder.changes) {
243
+ const date = change.createdDate ? ` *(${change.createdDate})*` : "";
244
+ lines.push(`- [${change.title ?? humanizeLabel(change.name)}](/${outDir}/changes/${change.name}/)${date}`);
245
+ }
246
+ }
247
+ if (folder.archivedChanges.length > 0) {
248
+ lines.push("");
249
+ lines.push("## Archiv");
250
+ lines.push("");
251
+ for (const change of folder.archivedChanges) {
252
+ const date = change.archivedDate ? ` *(archiviert: ${change.archivedDate})*` : "";
253
+ lines.push(
254
+ `- [${change.title ?? humanizeLabel(change.name)}](/${outDir}/changes/archive/${change.archiveFolderName}/)${date}`
255
+ );
256
+ }
257
+ }
258
+ lines.push("");
259
+ return lines.join("\n");
260
+ }
261
+ function changeItems(change, outDir, isArchived = false) {
262
+ const prefix = isArchived ? `/${outDir}/changes/archive/${change.archiveFolderName}` : `/${outDir}/changes/${change.name}`;
263
+ return change.artifacts.map((a) => ({
264
+ text: a.charAt(0).toUpperCase() + a.slice(1),
265
+ link: `${prefix}/${a}`
266
+ }));
267
+ }
268
+ function generateOpenSpecSidebar(specDir, options = {}) {
269
+ const outDir = options.outDir ?? "openspec";
270
+ const folder = readOpenSpecFolder(specDir);
271
+ const groups = [];
272
+ groups.push({
273
+ text: "Specifications",
274
+ collapsed: false,
275
+ items: [
276
+ { text: "Overview", link: `/${outDir}/specs/` },
277
+ ...folder.specs.map((s) => ({ text: s.title ?? humanizeLabel(s.name), link: `/${outDir}/specs/${s.name}/` }))
278
+ ]
279
+ });
280
+ groups.push({
281
+ text: "Changes",
282
+ collapsed: false,
283
+ items: [
284
+ { text: "Overview", link: `/${outDir}/changes/` },
285
+ ...folder.changes.map((c) => ({
286
+ text: c.title ?? humanizeLabel(c.name),
287
+ collapsed: true,
288
+ items: changeItems(c, outDir)
289
+ }))
290
+ ]
291
+ });
292
+ if (folder.archivedChanges.length > 0) {
293
+ groups.push({
294
+ text: "Archiv",
295
+ collapsed: true,
296
+ items: folder.archivedChanges.map((c) => ({
297
+ text: c.title ?? humanizeLabel(c.name),
298
+ collapsed: true,
299
+ items: changeItems(c, outDir, true)
300
+ }))
301
+ });
302
+ }
303
+ return groups;
304
+ }
305
+ function openspecNav(specDir, options = {}) {
306
+ const outDir = options.outDir ?? "openspec";
307
+ if (!fs__default.default.existsSync(path2__default.default.resolve(specDir))) {
308
+ throw new Error(
309
+ `[vitepress-plugin-openspec] openspec directory not found: ${path2__default.default.resolve(specDir)}`
310
+ );
311
+ }
312
+ return {
313
+ text: options.text ?? "Docs",
314
+ link: `/${outDir}/`
315
+ };
316
+ }
317
+
318
+ // src/plugin.ts
319
+ var PLUGIN_NAME = "vitepress-plugin-openspec";
320
+ function writeFile(filePath, content) {
321
+ fs__default.default.mkdirSync(path2__default.default.dirname(filePath), { recursive: true });
322
+ fs__default.default.writeFileSync(filePath, content, "utf-8");
323
+ }
324
+ function copyFile(src, dest) {
325
+ fs__default.default.mkdirSync(path2__default.default.dirname(dest), { recursive: true });
326
+ fs__default.default.copyFileSync(src, dest);
327
+ }
328
+ function generateOpenSpecPages(userOptions = {}) {
329
+ const specDir = userOptions.specDir ?? "./openspec";
330
+ const outDir = userOptions.outDir ?? "openspec";
331
+ const srcDir = userOptions.srcDir ?? process.cwd();
332
+ const absoluteOutDir = path2__default.default.resolve(srcDir, outDir);
333
+ try {
334
+ const folder = readOpenSpecFolder(specDir);
335
+ for (const spec of folder.specs) {
336
+ const dest = path2__default.default.join(absoluteOutDir, "specs", spec.name, "index.md");
337
+ writeFile(dest, generateSpecPage(spec));
338
+ }
339
+ writeFile(
340
+ path2__default.default.join(absoluteOutDir, "specs", "index.md"),
341
+ generateSpecsIndexPage(folder.specs, outDir)
342
+ );
343
+ for (const change of folder.changes) {
344
+ writeChangePage(change, absoluteOutDir, outDir, false);
345
+ }
346
+ for (const change of folder.archivedChanges) {
347
+ writeChangePage(change, absoluteOutDir, outDir, true);
348
+ }
349
+ writeFile(
350
+ path2__default.default.join(absoluteOutDir, "changes", "index.md"),
351
+ generateChangesIndexPage(folder, outDir)
352
+ );
353
+ const rootIndex = [
354
+ "# Project Documentation",
355
+ "",
356
+ "This section is generated from the project's [OpenSpec](https://openspec.dev/) folder.",
357
+ "OpenSpec is a lightweight, file-based workflow for spec-driven development \u2014",
358
+ "it structures your project's capability specifications and change proposals as plain Markdown files.",
359
+ "",
360
+ `- [Specifications](/${outDir}/specs/) \u2014 canonical capability specs`,
361
+ `- [Changes](/${outDir}/changes/) \u2014 active and archived change proposals`,
362
+ ""
363
+ ].join("\n");
364
+ writeFile(path2__default.default.join(absoluteOutDir, "index.md"), rootIndex);
365
+ console.log(
366
+ `${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`
367
+ );
368
+ } catch (err) {
369
+ console.error(
370
+ `${pc__default.default.bold(pc__default.default.red(`[${PLUGIN_NAME}]`))} Failed to process openspec directory "${specDir}": ${String(err)}`
371
+ );
372
+ }
373
+ }
374
+ function openspec(userOptions = {}) {
375
+ return {
376
+ name: PLUGIN_NAME,
377
+ enforce: "pre",
378
+ configResolved(resolvedConfig) {
379
+ const vpConfig = resolvedConfig.vitepress;
380
+ const srcDir = userOptions.srcDir ?? vpConfig?.srcDir ?? resolvedConfig.root ?? process.cwd();
381
+ generateOpenSpecPages({ ...userOptions, srcDir });
382
+ }
383
+ };
384
+ }
385
+ function writeChangePage(change, absoluteOutDir, outDir, isArchived) {
386
+ const subPath = isArchived ? path2__default.default.join("changes", "archive", `${change.archivedDate}-${change.name}`) : path2__default.default.join("changes", change.name);
387
+ const changeOutDir = path2__default.default.join(absoluteOutDir, subPath);
388
+ writeFile(path2__default.default.join(changeOutDir, "index.md"), generateChangeIndexPage(change, outDir));
389
+ for (const artifact of change.artifacts) {
390
+ const srcFile = path2__default.default.join(change.dir, `${artifact}.md`);
391
+ const destFile = path2__default.default.join(changeOutDir, `${artifact}.md`);
392
+ copyFile(srcFile, destFile);
393
+ }
394
+ }
395
+
396
+ exports.default = openspec;
397
+ exports.generateOpenSpecPages = generateOpenSpecPages;
398
+ exports.generateOpenSpecSidebar = generateOpenSpecSidebar;
399
+ exports.openspec = openspec;
400
+ exports.openspecNav = openspecNav;
401
+ //# sourceMappingURL=index.cjs.map
402
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/plugin.ts"],"names":["path","fs","yaml","pc"],"mappings":"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","file":"index.cjs","sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type {\n CapabilitySpec,\n Change,\n ChangeArtifact,\n NavItem,\n OpenSpecFolder,\n SidebarItem,\n} from './types.js'\n\n// ---------------------------------------------------------------------------\n// Folder reader\n// ---------------------------------------------------------------------------\n\nfunction readOpenSpecYaml(dir: string): Record<string, unknown> {\n const yamlPath = path.join(dir, '.openspec.yaml')\n if (!fs.existsSync(yamlPath)) return {}\n try {\n return (yaml.load(fs.readFileSync(yamlPath, 'utf-8')) ?? {}) as Record<string, unknown>\n } catch {\n return {}\n }\n}\n\nconst ACRONYM_DICT: Record<string, string> = {\n api: 'API',\n rest: 'REST',\n graphql: 'GraphQL',\n grpc: 'gRPC',\n openapi: 'OpenAPI',\n oauth: 'OAuth',\n oauth2: 'OAuth2',\n http: 'HTTP',\n https: 'HTTPS',\n url: 'URL',\n uri: 'URI',\n sdk: 'SDK',\n ui: 'UI',\n ux: 'UX',\n id: 'ID',\n db: 'DB',\n sql: 'SQL',\n css: 'CSS',\n html: 'HTML',\n json: 'JSON',\n yaml: 'YAML',\n xml: 'XML',\n jwt: 'JWT',\n ci: 'CI',\n cd: 'CD',\n}\n\nfunction humanizeLabel(name: string): string {\n if (!name) return ''\n return name\n .split('-')\n .map((word) => {\n if (/^v\\d+$/.test(word)) return word\n return ACRONYM_DICT[word] ?? (word.charAt(0).toUpperCase() + word.slice(1))\n })\n .join(' ')\n}\n\nfunction parseFrontmatterTitle(content: string): string | undefined {\n const match = content.match(/^---\\s*\\n(?:.*\\n)*?title:\\s*['\"]?([^\\n'\"]+)['\"]?\\s*\\n/)\n return match?.[1]?.trim() || undefined\n}\n\nfunction formatDate(val: unknown): string | undefined {\n if (!val) return undefined\n if (val instanceof Date) return val.toISOString().slice(0, 10)\n return String(val)\n}\n\nfunction readArtifacts(dir: string): ChangeArtifact[] {\n const artifacts: ChangeArtifact[] = []\n for (const name of ['proposal', 'design', 'tasks'] as ChangeArtifact[]) {\n if (fs.existsSync(path.join(dir, `${name}.md`))) artifacts.push(name)\n }\n return artifacts\n}\n\n/**\n * Scans an openspec/ directory and returns a structured representation of all\n * canonical specs, active changes, and archived changes.\n *\n * Throws if the directory does not exist.\n */\nexport function readOpenSpecFolder(dir: string): OpenSpecFolder {\n const resolved = path.resolve(dir)\n if (!fs.existsSync(resolved)) {\n throw new Error(`[vitepress-plugin-openspec] openspec directory not found: ${resolved}`)\n }\n\n // --- Canonical specs ---\n const specs: CapabilitySpec[] = []\n const specsDir = path.join(resolved, 'specs')\n if (fs.existsSync(specsDir)) {\n for (const entry of fs.readdirSync(specsDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n const specPath = path.join(specsDir, entry.name, 'spec.md')\n if (!fs.existsSync(specPath)) continue\n const content = fs.readFileSync(specPath, 'utf-8')\n specs.push({\n name: entry.name,\n title: parseFrontmatterTitle(content),\n specPath,\n content,\n })\n }\n }\n\n // --- Active changes ---\n const changes: Change[] = []\n const changesDir = path.join(resolved, 'changes')\n if (fs.existsSync(changesDir)) {\n for (const entry of fs.readdirSync(changesDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name === 'archive') continue\n const changeDir = path.join(changesDir, entry.name)\n if (!fs.existsSync(path.join(changeDir, '.openspec.yaml'))) continue\n const meta = readOpenSpecYaml(changeDir)\n changes.push({\n name: entry.name,\n title: meta.title ? String(meta.title) : undefined,\n dir: changeDir,\n artifacts: readArtifacts(changeDir),\n createdDate: formatDate(meta.created),\n })\n }\n }\n\n // --- Archived changes ---\n const archivedChanges: Change[] = []\n const archiveDir = path.join(changesDir, 'archive')\n if (fs.existsSync(archiveDir)) {\n for (const entry of fs.readdirSync(archiveDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n const changeDir = path.join(archiveDir, entry.name)\n // Parse YYYY-MM-DD-<name> format\n const match = entry.name.match(/^(\\d{4}-\\d{2}-\\d{2})-(.+)$/)\n const archivedDate = match?.[1]\n const name = match?.[2] ?? entry.name\n const meta = readOpenSpecYaml(changeDir)\n archivedChanges.push({\n name,\n title: meta.title ? String(meta.title) : undefined,\n dir: changeDir,\n artifacts: readArtifacts(changeDir),\n createdDate: formatDate(meta.created),\n archivedDate,\n archiveFolderName: entry.name,\n })\n }\n }\n\n return { dir: resolved, specs, changes, archivedChanges }\n}\n\n// ---------------------------------------------------------------------------\n// Spec content transformations\n// ---------------------------------------------------------------------------\n\nfunction extractSpecDescription(content: string): string | undefined {\n const reqMatch = content.match(/^### Requirement:[^\\n]*\\n+([\\s\\S]*?)(?=\\n#{1,4} |\\n*$)/m)\n if (!reqMatch) return undefined\n const para = reqMatch[1].trim()\n if (!para) return undefined\n const sentenceMatch = para.match(/^([^.?!]+[.?!])/)\n if (!sentenceMatch) return undefined\n let sentence = sentenceMatch[1].trim()\n if (sentence.length > 160) {\n const cut = sentence.lastIndexOf(' ', 160)\n sentence = (cut > 0 ? sentence.slice(0, cut) : sentence.slice(0, 160)) + '…'\n }\n return sentence.replace(/\"/g, '\\\\\"')\n}\n\nfunction stripDeltaMarkers(content: string): string {\n const stripped = content\n .split('\\n')\n .filter((line) => !/^## (ADDED|MODIFIED|REMOVED) Requirements\\s*$/.test(line))\n .join('\\n')\n // Collapse consecutive blank lines to a single blank line\n return stripped.replace(/\\n{3,}/g, '\\n\\n')\n}\n\nfunction transformScenarios(content: string): string {\n const lines = content.split('\\n')\n const result: string[] = []\n let inScenario = false\n\n for (const line of lines) {\n const scenarioMatch = line.match(/^#### Scenario: (.+)$/)\n const isHeading = /^#{1,6} /.test(line)\n\n if (scenarioMatch) {\n if (inScenario) result.push(':::')\n result.push(`:::details ${scenarioMatch[1]}`)\n inScenario = true\n } else if (isHeading && inScenario) {\n result.push(':::')\n result.push('')\n result.push(line)\n inScenario = false\n } else {\n result.push(line)\n }\n }\n\n if (inScenario) result.push(':::')\n return result.join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Page generators\n// ---------------------------------------------------------------------------\n\n/**\n * Generates VitePress Markdown for a canonical capability spec page.\n */\nexport function generateSpecPage(spec: CapabilitySpec): string {\n const description = extractSpecDescription(spec.content)\n const transformed = transformScenarios(stripDeltaMarkers(spec.content))\n const lines: string[] = []\n if (description) {\n lines.push('---')\n lines.push(`description: \"${description}\"`)\n lines.push('---')\n lines.push('')\n }\n lines.push(`# ${spec.title ?? humanizeLabel(spec.name)}`)\n lines.push('')\n lines.push(transformed.trimEnd())\n lines.push('')\n return lines.join('\\n')\n}\n\n/**\n * Generates the index page listing all canonical specs.\n */\nexport function generateSpecsIndexPage(specs: CapabilitySpec[], outDir: string): string {\n const lines: string[] = []\n lines.push('# Specifications')\n lines.push('')\n lines.push('Canonical capability specifications for this project.')\n lines.push('')\n if (specs.length === 0) {\n lines.push('*No specifications defined yet.*')\n } else {\n for (const spec of specs) {\n lines.push(`- [${spec.title ?? humanizeLabel(spec.name)}](/${outDir}/specs/${spec.name}/)`)\n }\n }\n lines.push('')\n return lines.join('\\n')\n}\n\n/**\n * Generates the index page for a single change.\n */\nexport function generateChangeIndexPage(change: Change, outDir: string): string {\n const lines: string[] = []\n lines.push(`# ${change.title ?? humanizeLabel(change.name)}`)\n lines.push('')\n if (change.createdDate) {\n lines.push(`**Created:** ${change.createdDate}`)\n lines.push('')\n }\n if (change.archivedDate) {\n lines.push(`**Archived:** ${change.archivedDate}`)\n lines.push('')\n }\n lines.push('## Artifacts')\n lines.push('')\n const prefix = change.archiveFolderName\n ? `/${outDir}/changes/archive/${change.archiveFolderName}`\n : `/${outDir}/changes/${change.name}`\n for (const artifact of change.artifacts) {\n const label = artifact.charAt(0).toUpperCase() + artifact.slice(1)\n lines.push(`- [${label}](${prefix}/${artifact})`)\n }\n lines.push('')\n return lines.join('\\n')\n}\n\n/**\n * Generates the changes overview page listing active and archived changes.\n */\nexport function generateChangesIndexPage(folder: OpenSpecFolder, outDir: string): string {\n const lines: string[] = []\n lines.push('# Changes')\n lines.push('')\n\n if (folder.changes.length === 0) {\n lines.push('*No active changes.*')\n } else {\n lines.push('## Active')\n lines.push('')\n for (const change of folder.changes) {\n const date = change.createdDate ? ` *(${change.createdDate})*` : ''\n lines.push(`- [${change.title ?? humanizeLabel(change.name)}](/${outDir}/changes/${change.name}/)${date}`)\n }\n }\n\n if (folder.archivedChanges.length > 0) {\n lines.push('')\n lines.push('## Archiv')\n lines.push('')\n for (const change of folder.archivedChanges) {\n const date = change.archivedDate ? ` *(archiviert: ${change.archivedDate})*` : ''\n lines.push(\n `- [${change.title ?? humanizeLabel(change.name)}](/${outDir}/changes/archive/${change.archiveFolderName}/)${date}`,\n )\n }\n }\n\n lines.push('')\n return lines.join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Navigation helpers\n// ---------------------------------------------------------------------------\n\nfunction changeItems(change: Change, outDir: string, isArchived = false): SidebarItem[] {\n const prefix = isArchived\n ? `/${outDir}/changes/archive/${change.archiveFolderName}`\n : `/${outDir}/changes/${change.name}`\n return change.artifacts.map((a) => ({\n text: a.charAt(0).toUpperCase() + a.slice(1),\n link: `${prefix}/${a}`,\n }))\n}\n\n/**\n * Returns a VitePress sidebar configuration for the OpenSpec documentation.\n * Includes groups for Specifications, active Changes, and archived Changes.\n */\nexport function generateOpenSpecSidebar(\n specDir: string,\n options: { outDir?: string } = {},\n): SidebarItem[] {\n const outDir = options.outDir ?? 'openspec'\n const folder = readOpenSpecFolder(specDir)\n const groups: SidebarItem[] = []\n\n // Specifications group\n groups.push({\n text: 'Specifications',\n collapsed: false,\n items: [\n { text: 'Overview', link: `/${outDir}/specs/` },\n ...folder.specs.map((s) => ({ text: s.title ?? humanizeLabel(s.name), link: `/${outDir}/specs/${s.name}/` })),\n ],\n })\n\n // Changes group\n groups.push({\n text: 'Changes',\n collapsed: false,\n items: [\n { text: 'Overview', link: `/${outDir}/changes/` },\n ...folder.changes.map((c) => ({\n text: c.title ?? humanizeLabel(c.name),\n collapsed: true,\n items: changeItems(c, outDir),\n })),\n ],\n })\n\n // Archive group (only if non-empty)\n if (folder.archivedChanges.length > 0) {\n groups.push({\n text: 'Archiv',\n collapsed: true,\n items: folder.archivedChanges.map((c) => ({\n text: c.title ?? humanizeLabel(c.name),\n collapsed: true,\n items: changeItems(c, outDir, true),\n })),\n })\n }\n\n return groups\n}\n\n/**\n * Returns a VitePress nav entry for the OpenSpec documentation section.\n */\nexport function openspecNav(\n specDir: string,\n options: { outDir?: string; text?: string } = {},\n): NavItem {\n const outDir = options.outDir ?? 'openspec'\n if (!fs.existsSync(path.resolve(specDir))) {\n throw new Error(\n `[vitepress-plugin-openspec] openspec directory not found: ${path.resolve(specDir)}`,\n )\n }\n return {\n text: options.text ?? 'Docs',\n link: `/${outDir}/`,\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport pc from 'picocolors'\nimport type { Plugin } from 'vite'\nimport type { Change, OpenSpecPluginOptions } from './types.js'\nimport {\n generateChangeIndexPage,\n generateChangesIndexPage,\n generateSpecPage,\n generateSpecsIndexPage,\n readOpenSpecFolder,\n} from './utils.js'\n\nconst PLUGIN_NAME = 'vitepress-plugin-openspec'\n\nfunction writeFile(filePath: string, content: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n fs.writeFileSync(filePath, content, 'utf-8')\n}\n\nfunction copyFile(src: string, dest: string): void {\n fs.mkdirSync(path.dirname(dest), { recursive: true })\n fs.copyFileSync(src, dest)\n}\n\n/**\n * Synchronously generates all VitePress Markdown pages from the openspec/\n * directory and writes them to disk.\n *\n * Call this at the top of your `docs/.vitepress/config.ts` **before**\n * `defineConfig()` so the files exist when VitePress scans the source\n * directory for routes.\n *\n * @example\n * ```typescript\n * // docs/.vitepress/config.ts\n * import { defineConfig } from 'vitepress'\n * import path from 'node:path'\n * import { fileURLToPath } from 'node:url'\n * import openspec, { generateOpenSpecPages } from 'vitepress-plugin-openspec'\n *\n * const __dirname = path.dirname(fileURLToPath(import.meta.url))\n * const specDir = path.resolve(__dirname, '../../openspec')\n *\n * // Generate pages before VitePress scans srcDir for routes\n * generateOpenSpecPages({ specDir, outDir: 'openspec', srcDir: path.resolve(__dirname, '..') })\n *\n * export default defineConfig({ ... })\n * ```\n */\nexport function generateOpenSpecPages(userOptions: OpenSpecPluginOptions = {}): void {\n const specDir = userOptions.specDir ?? './openspec'\n const outDir = userOptions.outDir ?? 'openspec'\n const srcDir = userOptions.srcDir ?? process.cwd()\n const absoluteOutDir = path.resolve(srcDir, outDir)\n\n try {\n const folder = readOpenSpecFolder(specDir)\n\n // --- Spec pages ---\n for (const spec of folder.specs) {\n const dest = path.join(absoluteOutDir, 'specs', spec.name, 'index.md')\n writeFile(dest, generateSpecPage(spec))\n }\n writeFile(\n path.join(absoluteOutDir, 'specs', 'index.md'),\n generateSpecsIndexPage(folder.specs, outDir),\n )\n\n // --- Active change pages ---\n for (const change of folder.changes) {\n writeChangePage(change, absoluteOutDir, outDir, false)\n }\n\n // --- Archived change pages ---\n for (const change of folder.archivedChanges) {\n writeChangePage(change, absoluteOutDir, outDir, true)\n }\n\n // --- Changes index ---\n writeFile(\n path.join(absoluteOutDir, 'changes', 'index.md'),\n generateChangesIndexPage(folder, outDir),\n )\n\n // --- Root index ---\n const rootIndex = [\n '# Project Documentation',\n '',\n \"This section is generated from the project's [OpenSpec](https://openspec.dev/) folder.\",\n 'OpenSpec is a lightweight, file-based workflow for spec-driven development —',\n 'it structures your project\\'s capability specifications and change proposals as plain Markdown files.',\n '',\n `- [Specifications](/${outDir}/specs/) — canonical capability specs`,\n `- [Changes](/${outDir}/changes/) — active and archived change proposals`,\n '',\n ].join('\\n')\n writeFile(path.join(absoluteOutDir, 'index.md'), rootIndex)\n\n console.log(\n `${pc.bold(pc.cyan(`[${PLUGIN_NAME}]`))} Generated docs from ${pc.cyan(specDir)}: ` +\n `${pc.green(String(folder.specs.length))} spec(s), ` +\n `${pc.green(String(folder.changes.length))} change(s), ` +\n `${pc.green(String(folder.archivedChanges.length))} archived`,\n )\n } catch (err) {\n console.error(\n `${pc.bold(pc.red(`[${PLUGIN_NAME}]`))} Failed to process openspec directory \"${specDir}\": ${String(err)}`,\n )\n }\n}\n\n/**\n * VitePress plugin that reads an openspec/ directory and generates structured\n * Markdown documentation pages inside the VitePress source directory.\n *\n * For the pages to be available on the first build (including CI), also call\n * `generateOpenSpecPages()` at the top of your `config.ts` before `defineConfig`.\n *\n * @example\n * ```typescript\n * // .vitepress/config.ts\n * import { defineConfig } from 'vitepress'\n * import openspec, { generateOpenSpecPages } from 'vitepress-plugin-openspec'\n *\n * generateOpenSpecPages({ specDir, outDir: 'openspec', srcDir: path.resolve(__dirname, '..') })\n *\n * export default defineConfig({\n * vite: { plugins: [openspec({ specDir: './openspec', outDir: 'project-docs' })] },\n * })\n * ```\n */\nexport function openspec(userOptions: OpenSpecPluginOptions = {}): Plugin {\n return {\n name: PLUGIN_NAME,\n enforce: 'pre',\n\n configResolved(resolvedConfig) {\n const vpConfig = (resolvedConfig as unknown as { vitepress?: { srcDir?: string } }).vitepress\n const srcDir =\n userOptions.srcDir ?? vpConfig?.srcDir ?? resolvedConfig.root ?? process.cwd()\n generateOpenSpecPages({ ...userOptions, srcDir })\n },\n }\n}\n\nfunction writeChangePage(\n change: Change,\n absoluteOutDir: string,\n outDir: string,\n isArchived: boolean,\n): void {\n const subPath = isArchived\n ? path.join('changes', 'archive', `${change.archivedDate}-${change.name}`)\n : path.join('changes', change.name)\n const changeOutDir = path.join(absoluteOutDir, subPath)\n\n // Write index page\n writeFile(path.join(changeOutDir, 'index.md'), generateChangeIndexPage(change, outDir))\n\n // Copy artifact files\n for (const artifact of change.artifacts) {\n const srcFile = path.join(change.dir, `${artifact}.md`)\n const destFile = path.join(changeOutDir, `${artifact}.md`)\n copyFile(srcFile, destFile)\n }\n}\n\nexport default openspec\n"]}