docs-ready 0.3.0 → 0.6.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/dist/chunk-4NYEWEU2.js +73 -0
- package/dist/chunk-4NYEWEU2.js.map +1 -0
- package/dist/chunk-5XFC7QYB.js +33 -0
- package/dist/chunk-5XFC7QYB.js.map +1 -0
- package/dist/chunk-QI2AROM3.js +153 -0
- package/dist/chunk-QI2AROM3.js.map +1 -0
- package/dist/{generate-LDVEG2WV.js → generate-4EHDLBJX.js} +41 -245
- package/dist/generate-4EHDLBJX.js.map +1 -0
- package/dist/guard-VC7ENYOC.js +359 -0
- package/dist/guard-VC7ENYOC.js.map +1 -0
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/validate-ITHI7RFF.js +219 -0
- package/dist/validate-ITHI7RFF.js.map +1 -0
- package/dist/workflow-PTPU42JU.js +75 -0
- package/dist/workflow-PTPU42JU.js.map +1 -0
- package/package.json +2 -1
- package/dist/generate-LDVEG2WV.js.map +0 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
estimateTokens,
|
|
4
|
+
scanDocs
|
|
5
|
+
} from "./chunk-4NYEWEU2.js";
|
|
6
|
+
import {
|
|
7
|
+
fetchWithTimeout
|
|
8
|
+
} from "./chunk-5XFC7QYB.js";
|
|
9
|
+
import {
|
|
10
|
+
loadConfig
|
|
11
|
+
} from "./chunk-QI2AROM3.js";
|
|
12
|
+
import {
|
|
13
|
+
log,
|
|
14
|
+
spinner
|
|
15
|
+
} from "./chunk-7YN54Y4Y.js";
|
|
16
|
+
|
|
17
|
+
// src/cli/commands/validate.ts
|
|
18
|
+
import path2 from "path";
|
|
19
|
+
|
|
20
|
+
// src/validate/runner.ts
|
|
21
|
+
import fs from "fs/promises";
|
|
22
|
+
import path from "path";
|
|
23
|
+
|
|
24
|
+
// src/validate/rules/format.ts
|
|
25
|
+
function validateFormat(content) {
|
|
26
|
+
const results = [];
|
|
27
|
+
const lines = content.split("\n");
|
|
28
|
+
const hasH1 = lines.some((l) => /^# .+/.test(l));
|
|
29
|
+
results.push({
|
|
30
|
+
rule: "format",
|
|
31
|
+
severity: "error",
|
|
32
|
+
passed: hasH1,
|
|
33
|
+
message: hasH1 ? "H1 title present" : "Missing H1 title (required by llmstxt.org spec)"
|
|
34
|
+
});
|
|
35
|
+
const hasBlockquote = lines.some((l) => /^> .+/.test(l));
|
|
36
|
+
results.push({
|
|
37
|
+
rule: "format",
|
|
38
|
+
severity: "error",
|
|
39
|
+
passed: hasBlockquote,
|
|
40
|
+
message: hasBlockquote ? "Blockquote description present" : "Missing blockquote description"
|
|
41
|
+
});
|
|
42
|
+
const linkLines = lines.filter((l) => l.trim().startsWith("- ["));
|
|
43
|
+
const malformed = linkLines.filter((l) => !/^- \[.+\]\(.+\)/.test(l.trim()));
|
|
44
|
+
const linksOk = malformed.length === 0 && linkLines.length > 0;
|
|
45
|
+
results.push({
|
|
46
|
+
rule: "format",
|
|
47
|
+
severity: "error",
|
|
48
|
+
passed: linksOk,
|
|
49
|
+
message: linksOk ? `${linkLines.length} link entries correctly formatted` : malformed.length > 0 ? `${malformed.length} malformed link entries` : "No link entries found"
|
|
50
|
+
});
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/validate/rules/tokens.ts
|
|
55
|
+
function validateTokens(content, maxTokens) {
|
|
56
|
+
const count = estimateTokens(content);
|
|
57
|
+
const passed = count <= maxTokens;
|
|
58
|
+
return {
|
|
59
|
+
rule: "tokens",
|
|
60
|
+
severity: "warning",
|
|
61
|
+
passed,
|
|
62
|
+
message: passed ? `Token count ~${count} is within limit (${maxTokens})` : `Token count ~${count} exceeds limit of ${maxTokens}`,
|
|
63
|
+
details: { estimatedTokens: count, maxTokens }
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/validate/rules/coverage.ts
|
|
68
|
+
function validateCoverage(llmsTxtContent, totalPages, threshold) {
|
|
69
|
+
const linkRegex = /- \[.+?\]\((.+?)\)/g;
|
|
70
|
+
const links = /* @__PURE__ */ new Set();
|
|
71
|
+
let match;
|
|
72
|
+
while ((match = linkRegex.exec(llmsTxtContent)) !== null) {
|
|
73
|
+
links.add(match[1]);
|
|
74
|
+
}
|
|
75
|
+
const coverage = totalPages > 0 ? links.size / totalPages : 0;
|
|
76
|
+
const passed = coverage >= threshold;
|
|
77
|
+
const pct = Math.round(coverage * 100);
|
|
78
|
+
return {
|
|
79
|
+
rule: "coverage",
|
|
80
|
+
severity: "error",
|
|
81
|
+
passed,
|
|
82
|
+
message: passed ? `Coverage ${pct}% (${links.size}/${totalPages} pages) meets ${Math.round(threshold * 100)}% threshold` : `Coverage ${pct}% (${links.size}/${totalPages} pages) below ${Math.round(threshold * 100)}% threshold`,
|
|
83
|
+
details: { coverage, linkedPages: links.size, totalPages, threshold }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/validate/rules/links.ts
|
|
88
|
+
async function validateLinks(content) {
|
|
89
|
+
const results = [];
|
|
90
|
+
const linkRegex = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
|
|
91
|
+
const urls = /* @__PURE__ */ new Set();
|
|
92
|
+
let match;
|
|
93
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
94
|
+
urls.add(match[2]);
|
|
95
|
+
}
|
|
96
|
+
for (const url of urls) {
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetchWithTimeout(url, { timeout: 1e4 });
|
|
99
|
+
if (response.ok || response.status === 301 || response.status === 302) {
|
|
100
|
+
results.push({
|
|
101
|
+
rule: "links",
|
|
102
|
+
severity: "error",
|
|
103
|
+
passed: true,
|
|
104
|
+
message: `${url} \u2014 ${response.status} OK`
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
results.push({
|
|
108
|
+
rule: "links",
|
|
109
|
+
severity: "error",
|
|
110
|
+
passed: false,
|
|
111
|
+
message: `${url} \u2014 ${response.status}`,
|
|
112
|
+
details: { url, statusCode: response.status }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const err = error;
|
|
117
|
+
results.push({
|
|
118
|
+
rule: "links",
|
|
119
|
+
severity: "warning",
|
|
120
|
+
passed: false,
|
|
121
|
+
message: `${url} \u2014 ${err.name === "AbortError" ? "timeout" : err.message}`,
|
|
122
|
+
details: { url, error: err.message }
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/validate/runner.ts
|
|
130
|
+
async function runValidation(config, pages, outputDir) {
|
|
131
|
+
const results = [];
|
|
132
|
+
const llmsTxtPath = path.join(outputDir, "llms.txt");
|
|
133
|
+
const llmsFullTxtPath = path.join(outputDir, "llms-full.txt");
|
|
134
|
+
const aiContextPath = path.join(outputDir, "ai-context.md");
|
|
135
|
+
let llmsTxt = null;
|
|
136
|
+
let llmsFullTxt = null;
|
|
137
|
+
let aiContext = null;
|
|
138
|
+
try {
|
|
139
|
+
llmsTxt = await fs.readFile(llmsTxtPath, "utf-8");
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
llmsFullTxt = await fs.readFile(llmsFullTxtPath, "utf-8");
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
aiContext = await fs.readFile(aiContextPath, "utf-8");
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
if (llmsTxt) {
|
|
151
|
+
results.push(...validateFormat(llmsTxt));
|
|
152
|
+
} else {
|
|
153
|
+
results.push({
|
|
154
|
+
rule: "format",
|
|
155
|
+
severity: "warning",
|
|
156
|
+
passed: false,
|
|
157
|
+
message: "llms.txt not found \u2014 run `docs-ready generate` first"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (llmsFullTxt) {
|
|
161
|
+
results.push(validateTokens(llmsFullTxt, config.validate.max_tokens));
|
|
162
|
+
}
|
|
163
|
+
if (llmsTxt && config.validate.check_coverage) {
|
|
164
|
+
results.push(validateCoverage(llmsTxt, pages.length, config.validate.coverage_threshold));
|
|
165
|
+
}
|
|
166
|
+
if (config.validate.check_links) {
|
|
167
|
+
const contentToCheck = [llmsTxt, aiContext].filter(Boolean).join("\n");
|
|
168
|
+
if (contentToCheck) {
|
|
169
|
+
results.push(...await validateLinks(contentToCheck));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
function getValidationExitCode(results) {
|
|
175
|
+
return results.some((r) => r.severity === "error" && !r.passed) ? 1 : 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/cli/commands/validate.ts
|
|
179
|
+
async function validateCommand(options = {}) {
|
|
180
|
+
const cwd = process.cwd();
|
|
181
|
+
const config = await loadConfig(cwd);
|
|
182
|
+
if (options.checkLinks === false) {
|
|
183
|
+
config.validate.check_links = false;
|
|
184
|
+
}
|
|
185
|
+
const spin = spinner("Scanning documentation...");
|
|
186
|
+
spin.start();
|
|
187
|
+
const docsDir = path2.resolve(cwd, config.docs.dir);
|
|
188
|
+
const pages = await scanDocs(docsDir, {
|
|
189
|
+
include: config.docs.include,
|
|
190
|
+
exclude: config.docs.exclude
|
|
191
|
+
});
|
|
192
|
+
spin.stop();
|
|
193
|
+
const outputDir = path2.resolve(cwd, config.generate.output_dir);
|
|
194
|
+
const spin2 = spinner("Running validation...");
|
|
195
|
+
spin2.start();
|
|
196
|
+
const results = await runValidation(config, pages, outputDir);
|
|
197
|
+
spin2.stop();
|
|
198
|
+
for (const result of results) {
|
|
199
|
+
if (result.passed) {
|
|
200
|
+
log.success(`[${result.rule}] ${result.message}`);
|
|
201
|
+
} else if (result.severity === "warning") {
|
|
202
|
+
log.warn(`[${result.rule}] ${result.message}`);
|
|
203
|
+
} else {
|
|
204
|
+
log.error(`[${result.rule}] ${result.message}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const passed = results.filter((r) => r.passed).length;
|
|
208
|
+
const failed = results.filter((r) => !r.passed).length;
|
|
209
|
+
log.info(`
|
|
210
|
+
Validation: ${passed} passed, ${failed} issues`);
|
|
211
|
+
const exitCode = getValidationExitCode(results);
|
|
212
|
+
if (exitCode !== 0) {
|
|
213
|
+
process.exitCode = exitCode;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export {
|
|
217
|
+
validateCommand
|
|
218
|
+
};
|
|
219
|
+
//# sourceMappingURL=validate-ITHI7RFF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/validate.ts","../src/validate/runner.ts","../src/validate/rules/format.ts","../src/validate/rules/tokens.ts","../src/validate/rules/coverage.ts","../src/validate/rules/links.ts"],"sourcesContent":["import path from \"node:path\";\nimport { loadConfig } from \"../../core/config.js\";\nimport { scanDocs } from \"../../core/scanner.js\";\nimport { runValidation, getValidationExitCode } from \"../../validate/runner.js\";\nimport { log, spinner } from \"../../utils/logger.js\";\n\ninterface ValidateOptions {\n checkLinks?: boolean;\n}\n\nexport async function validateCommand(options: ValidateOptions = {}): Promise<void> {\n const cwd = process.cwd();\n const config = await loadConfig(cwd);\n\n // Override check_links if --no-links was passed\n if (options.checkLinks === false) {\n config.validate.check_links = false;\n }\n\n const spin = spinner(\"Scanning documentation...\");\n spin.start();\n const docsDir = path.resolve(cwd, config.docs.dir);\n const pages = await scanDocs(docsDir, {\n include: config.docs.include,\n exclude: config.docs.exclude,\n });\n spin.stop();\n\n const outputDir = path.resolve(cwd, config.generate.output_dir);\n\n const spin2 = spinner(\"Running validation...\");\n spin2.start();\n const results = await runValidation(config, pages, outputDir);\n spin2.stop();\n\n // Print results\n for (const result of results) {\n if (result.passed) {\n log.success(`[${result.rule}] ${result.message}`);\n } else if (result.severity === \"warning\") {\n log.warn(`[${result.rule}] ${result.message}`);\n } else {\n log.error(`[${result.rule}] ${result.message}`);\n }\n }\n\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n log.info(`\\nValidation: ${passed} passed, ${failed} issues`);\n\n const exitCode = getValidationExitCode(results);\n if (exitCode !== 0) {\n process.exitCode = exitCode;\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { DocsReadyConfig } from \"../core/config.js\";\nimport type { ScannedPage } from \"../core/scanner.js\";\nimport type { ValidationResult } from \"./types.js\";\nimport { validateFormat } from \"./rules/format.js\";\nimport { validateTokens } from \"./rules/tokens.js\";\nimport { validateCoverage } from \"./rules/coverage.js\";\nimport { validateLinks } from \"./rules/links.js\";\n\nexport async function runValidation(\n config: DocsReadyConfig,\n pages: ScannedPage[],\n outputDir: string\n): Promise<ValidationResult[]> {\n const results: ValidationResult[] = [];\n\n // Read generated files\n const llmsTxtPath = path.join(outputDir, \"llms.txt\");\n const llmsFullTxtPath = path.join(outputDir, \"llms-full.txt\");\n const aiContextPath = path.join(outputDir, \"ai-context.md\");\n\n let llmsTxt: string | null = null;\n let llmsFullTxt: string | null = null;\n let aiContext: string | null = null;\n\n try { llmsTxt = await fs.readFile(llmsTxtPath, \"utf-8\"); } catch {}\n try { llmsFullTxt = await fs.readFile(llmsFullTxtPath, \"utf-8\"); } catch {}\n try { aiContext = await fs.readFile(aiContextPath, \"utf-8\"); } catch {}\n\n // Format check on llms.txt\n if (llmsTxt) {\n results.push(...validateFormat(llmsTxt));\n } else {\n results.push({\n rule: \"format\",\n severity: \"warning\",\n passed: false,\n message: \"llms.txt not found — run `docs-ready generate` first\",\n });\n }\n\n // Token check on llms-full.txt\n if (llmsFullTxt) {\n results.push(validateTokens(llmsFullTxt, config.validate.max_tokens));\n }\n\n // Coverage check\n if (llmsTxt && config.validate.check_coverage) {\n results.push(validateCoverage(llmsTxt, pages.length, config.validate.coverage_threshold));\n }\n\n // Link check\n if (config.validate.check_links) {\n const contentToCheck = [llmsTxt, aiContext].filter(Boolean).join(\"\\n\");\n if (contentToCheck) {\n results.push(...await validateLinks(contentToCheck));\n }\n }\n\n return results;\n}\n\nexport function getValidationExitCode(results: ValidationResult[]): number {\n return results.some((r) => r.severity === \"error\" && !r.passed) ? 1 : 0;\n}\n","import type { ValidationResult } from \"../types.js\";\n\nexport function validateFormat(content: string): ValidationResult[] {\n const results: ValidationResult[] = [];\n const lines = content.split(\"\\n\");\n\n // Check H1 title\n const hasH1 = lines.some((l) => /^# .+/.test(l));\n results.push({\n rule: \"format\",\n severity: \"error\",\n passed: hasH1,\n message: hasH1 ? \"H1 title present\" : \"Missing H1 title (required by llmstxt.org spec)\",\n });\n\n // Check blockquote description\n const hasBlockquote = lines.some((l) => /^> .+/.test(l));\n results.push({\n rule: \"format\",\n severity: \"error\",\n passed: hasBlockquote,\n message: hasBlockquote ? \"Blockquote description present\" : \"Missing blockquote description\",\n });\n\n // Check link format: - [Title](url) or - [Title](url): description\n const linkLines = lines.filter((l) => l.trim().startsWith(\"- [\"));\n const malformed = linkLines.filter((l) => !/^- \\[.+\\]\\(.+\\)/.test(l.trim()));\n const linksOk = malformed.length === 0 && linkLines.length > 0;\n results.push({\n rule: \"format\",\n severity: \"error\",\n passed: linksOk,\n message: linksOk\n ? `${linkLines.length} link entries correctly formatted`\n : malformed.length > 0\n ? `${malformed.length} malformed link entries`\n : \"No link entries found\",\n });\n\n return results;\n}\n","import { estimateTokens } from \"../../utils/tokens.js\";\nimport type { ValidationResult } from \"../types.js\";\n\nexport function validateTokens(content: string, maxTokens: number): ValidationResult {\n const count = estimateTokens(content);\n const passed = count <= maxTokens;\n return {\n rule: \"tokens\",\n severity: \"warning\",\n passed,\n message: passed\n ? `Token count ~${count} is within limit (${maxTokens})`\n : `Token count ~${count} exceeds limit of ${maxTokens}`,\n details: { estimatedTokens: count, maxTokens },\n };\n}\n","import type { ValidationResult } from \"../types.js\";\n\nexport function validateCoverage(\n llmsTxtContent: string,\n totalPages: number,\n threshold: number\n): ValidationResult {\n // Count unique links in llms.txt\n const linkRegex = /- \\[.+?\\]\\((.+?)\\)/g;\n const links = new Set<string>();\n let match;\n while ((match = linkRegex.exec(llmsTxtContent)) !== null) {\n links.add(match[1]);\n }\n\n const coverage = totalPages > 0 ? links.size / totalPages : 0;\n const passed = coverage >= threshold;\n const pct = Math.round(coverage * 100);\n\n return {\n rule: \"coverage\",\n severity: \"error\",\n passed,\n message: passed\n ? `Coverage ${pct}% (${links.size}/${totalPages} pages) meets ${Math.round(threshold * 100)}% threshold`\n : `Coverage ${pct}% (${links.size}/${totalPages} pages) below ${Math.round(threshold * 100)}% threshold`,\n details: { coverage, linkedPages: links.size, totalPages, threshold },\n };\n}\n","import { fetchWithTimeout } from \"../../utils/http.js\";\nimport type { ValidationResult } from \"../types.js\";\n\nexport async function validateLinks(content: string): Promise<ValidationResult[]> {\n const results: ValidationResult[] = [];\n const linkRegex = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n const urls = new Set<string>();\n let match;\n\n while ((match = linkRegex.exec(content)) !== null) {\n urls.add(match[2]);\n }\n\n for (const url of urls) {\n try {\n const response = await fetchWithTimeout(url, { timeout: 10000 });\n if (response.ok || response.status === 301 || response.status === 302) {\n results.push({\n rule: \"links\",\n severity: \"error\",\n passed: true,\n message: `${url} — ${response.status} OK`,\n });\n } else {\n results.push({\n rule: \"links\",\n severity: \"error\",\n passed: false,\n message: `${url} — ${response.status}`,\n details: { url, statusCode: response.status },\n });\n }\n } catch (error) {\n const err = error as Error;\n results.push({\n rule: \"links\",\n severity: \"warning\",\n passed: false,\n message: `${url} — ${err.name === \"AbortError\" ? \"timeout\" : err.message}`,\n details: { url, error: err.message },\n });\n }\n }\n\n return results;\n}\n"],"mappings":"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","names":["path","path"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/guard/workflow.ts
|
|
4
|
+
function generateWorkflowYaml(config) {
|
|
5
|
+
const schedule = config.guard.workflow.schedule;
|
|
6
|
+
const labels = config.guard.workflow.labels.map((l) => `"${l}"`).join(", ");
|
|
7
|
+
return `# Auto-generated by docs-ready. Safe to re-run.
|
|
8
|
+
name: docs-ready guard
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
schedule:
|
|
12
|
+
- cron: "${schedule}"
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
issues: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
guard:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version: "20"
|
|
27
|
+
|
|
28
|
+
- name: Install docs-ready
|
|
29
|
+
run: npm install -g docs-ready
|
|
30
|
+
|
|
31
|
+
- name: Run guard checks
|
|
32
|
+
id: guard
|
|
33
|
+
run: |
|
|
34
|
+
docs-ready guard --output markdown > guard-report.md 2>&1 || echo "GUARD_FAILED=true" >> $GITHUB_ENV
|
|
35
|
+
|
|
36
|
+
- name: Close previous docs-ready issues
|
|
37
|
+
if: env.GUARD_FAILED == 'true'
|
|
38
|
+
uses: actions/github-script@v7
|
|
39
|
+
with:
|
|
40
|
+
script: |
|
|
41
|
+
const issues = await github.rest.issues.listForRepo({
|
|
42
|
+
owner: context.repo.owner,
|
|
43
|
+
repo: context.repo.repo,
|
|
44
|
+
labels: ${labels ? `[${labels}]` : '"docs-ready"'},
|
|
45
|
+
state: "open",
|
|
46
|
+
});
|
|
47
|
+
for (const issue of issues.data) {
|
|
48
|
+
await github.rest.issues.update({
|
|
49
|
+
owner: context.repo.owner,
|
|
50
|
+
repo: context.repo.repo,
|
|
51
|
+
issue_number: issue.number,
|
|
52
|
+
state: "closed",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- name: Create issue on failure
|
|
57
|
+
if: env.GUARD_FAILED == 'true'
|
|
58
|
+
uses: actions/github-script@v7
|
|
59
|
+
with:
|
|
60
|
+
script: |
|
|
61
|
+
const fs = require("fs");
|
|
62
|
+
const body = fs.readFileSync("guard-report.md", "utf-8");
|
|
63
|
+
await github.rest.issues.create({
|
|
64
|
+
owner: context.repo.owner,
|
|
65
|
+
repo: context.repo.repo,
|
|
66
|
+
title: "docs-ready: Guard checks failed",
|
|
67
|
+
body: body,
|
|
68
|
+
labels: [${labels}],
|
|
69
|
+
});
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
generateWorkflowYaml
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=workflow-PTPU42JU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guard/workflow.ts"],"sourcesContent":["import type { DocsReadyConfig } from \"../core/config.js\";\n\nexport function generateWorkflowYaml(config: DocsReadyConfig): string {\n const schedule = config.guard.workflow.schedule;\n const labels = config.guard.workflow.labels.map((l) => `\"${l}\"`).join(\", \");\n\n return `# Auto-generated by docs-ready. Safe to re-run.\nname: docs-ready guard\n\non:\n schedule:\n - cron: \"${schedule}\"\n workflow_dispatch:\n\npermissions:\n issues: write\n\njobs:\n guard:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n\n - name: Install docs-ready\n run: npm install -g docs-ready\n\n - name: Run guard checks\n id: guard\n run: |\n docs-ready guard --output markdown > guard-report.md 2>&1 || echo \"GUARD_FAILED=true\" >> $GITHUB_ENV\n\n - name: Close previous docs-ready issues\n if: env.GUARD_FAILED == 'true'\n uses: actions/github-script@v7\n with:\n script: |\n const issues = await github.rest.issues.listForRepo({\n owner: context.repo.owner,\n repo: context.repo.repo,\n labels: ${labels ? `[${labels}]` : '\"docs-ready\"'},\n state: \"open\",\n });\n for (const issue of issues.data) {\n await github.rest.issues.update({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: issue.number,\n state: \"closed\",\n });\n }\n\n - name: Create issue on failure\n if: env.GUARD_FAILED == 'true'\n uses: actions/github-script@v7\n with:\n script: |\n const fs = require(\"fs\");\n const body = fs.readFileSync(\"guard-report.md\", \"utf-8\");\n await github.rest.issues.create({\n owner: context.repo.owner,\n repo: context.repo.repo,\n title: \"docs-ready: Guard checks failed\",\n body: body,\n labels: [${labels}],\n });\n`;\n}\n"],"mappings":";;;AAEO,SAAS,qBAAqB,QAAiC;AACpE,QAAM,WAAW,OAAO,MAAM,SAAS;AACvC,QAAM,SAAS,OAAO,MAAM,SAAS,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AAE1E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,eAKM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAgCC,SAAS,IAAI,MAAM,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAwBtC,MAAM;AAAA;AAAA;AAG/B;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docs-ready",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Make your docs AI-ready. Keep them that way.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^25.5.2",
|
|
61
|
+
"msw": "^2.12.14",
|
|
61
62
|
"tsup": "^8.2.0",
|
|
62
63
|
"typescript": "^5.5.0",
|
|
63
64
|
"vitest": "^2.0.0"
|