@cms-lab/cli 1.0.4 → 1.0.5
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 +18 -4
- package/dist/bin.js +1 -1
- package/dist/{chunk-A2CHTMQR.js → chunk-BQIQ53GI.js} +435 -25
- package/dist/index.js +1 -1
- package/package.json +12 -8
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
CLI for catching CMS-driven Next.js failures before deploy.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npx
|
|
6
|
+
npx cms-lab scan
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
## Commands
|
|
@@ -21,14 +21,21 @@ cms-lab scan --json
|
|
|
21
21
|
cms-lab scan --json --include-sensitive-output
|
|
22
22
|
cms-lab scan --max-warnings 0
|
|
23
23
|
cms-lab scan --strict
|
|
24
|
+
cms-lab agent-context
|
|
25
|
+
cms-lab agent-context --preset all
|
|
26
|
+
cms-lab agent-context --preset claude
|
|
27
|
+
cms-lab agent-context --preset gemini
|
|
28
|
+
cms-lab agent-context --preset copilot
|
|
29
|
+
cms-lab agent-context --force
|
|
24
30
|
cms-lab explain CMS-ROUTE-404
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
## Scope
|
|
28
34
|
|
|
29
|
-
cms-lab supports Next.js App Router projects using Prismic,
|
|
30
|
-
and
|
|
31
|
-
UID gaps, SEO metadata, image alt text, and
|
|
35
|
+
cms-lab supports Next.js App Router and Pages Router projects using Prismic,
|
|
36
|
+
Strapi, Directus, WordPress, Contentful, and Sanity. It validates configured CMS
|
|
37
|
+
route mappings, route reachability, UID gaps, SEO metadata, image alt text, and
|
|
38
|
+
project-specific required fields.
|
|
32
39
|
|
|
33
40
|
Debug logs use stderr and support `--debug` plus `--verbose <0|1|2|3>`, so JSON
|
|
34
41
|
scan output remains clean on stdout. Raw CMS document `data` is redacted from
|
|
@@ -43,6 +50,13 @@ Exports include local HTML (`--report`), Markdown (`--markdown`), JUnit XML
|
|
|
43
50
|
(`--slack-webhook <url>`). Slack notifications send counts and diagnostic codes
|
|
44
51
|
only, never raw CMS payloads, local paths, or webhook URLs.
|
|
45
52
|
|
|
53
|
+
`cms-lab agent-context` writes safe handoff files so coding agents can read the
|
|
54
|
+
cms-lab docs, package links, route mappings, and safe project facts before
|
|
55
|
+
attempting fixes. The default preset writes `AGENTS.md` plus shared files in
|
|
56
|
+
`.cms-lab/`. Tool-specific presets can write `CLAUDE.md`, `GEMINI.md`, Copilot
|
|
57
|
+
instructions, and a reusable Copilot prompt. Existing files are not overwritten
|
|
58
|
+
unless `--force` is passed.
|
|
59
|
+
|
|
46
60
|
## Config
|
|
47
61
|
|
|
48
62
|
Create `cms-lab.config.ts` in the target Next.js project. Import
|
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Command, CommanderError } from "commander";
|
|
3
3
|
import { access, mkdir, writeFile } from "fs/promises";
|
|
4
|
-
import { dirname, resolve } from "path";
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
5
5
|
import { performance } from "perf_hooks";
|
|
6
6
|
import {
|
|
7
7
|
CmsFetchError,
|
|
@@ -11,10 +11,12 @@ import {
|
|
|
11
11
|
loadCmsLabConfig,
|
|
12
12
|
scanDocuments
|
|
13
13
|
} from "@cms-lab/core";
|
|
14
|
+
import { fetchContentfulDocuments as defaultFetchContentfulDocuments } from "@cms-lab/contentful";
|
|
14
15
|
import { fetchDirectusDocuments as defaultFetchDirectusDocuments } from "@cms-lab/directus";
|
|
15
16
|
import { detectNextProject } from "@cms-lab/next";
|
|
16
17
|
import { fetchPrismicDocuments as defaultFetchPrismicDocuments } from "@cms-lab/prismic";
|
|
17
18
|
import { renderHtmlReport } from "@cms-lab/reporter";
|
|
19
|
+
import { fetchSanityDocuments as defaultFetchSanityDocuments } from "@cms-lab/sanity";
|
|
18
20
|
import { fetchStrapiDocuments as defaultFetchStrapiDocuments } from "@cms-lab/strapi";
|
|
19
21
|
import { fetchWordPressDocuments as defaultFetchWordPressDocuments } from "@cms-lab/wordpress";
|
|
20
22
|
|
|
@@ -115,6 +117,298 @@ function plural(value, singular) {
|
|
|
115
117
|
return value === 1 ? singular : `${singular}s`;
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
// src/agent-context.ts
|
|
121
|
+
var GITHUB_URL = "https://github.com/i-afaqrashid/cms-lab";
|
|
122
|
+
var NPM_URL = "https://www.npmjs.com/package/cms-lab";
|
|
123
|
+
var DOCS_URL = "https://cmslab.afaqrashid.com/docs";
|
|
124
|
+
function renderAgentContextFiles(options) {
|
|
125
|
+
const outputDir = trimSlashes(options.outputDir || ".cms-lab") || ".cms-lab";
|
|
126
|
+
const contextPath = `${outputDir}/agent-context.md`;
|
|
127
|
+
const promptPath = `${outputDir}/agent-prompt.md`;
|
|
128
|
+
const files = [];
|
|
129
|
+
const includeAgentsMd = options.includeAgentsMd && (options.preset === "generic" || options.preset === "codex" || options.preset === "all");
|
|
130
|
+
if (includeAgentsMd) {
|
|
131
|
+
files.push({
|
|
132
|
+
path: "AGENTS.md",
|
|
133
|
+
content: renderAgentsMd(contextPath, promptPath)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (options.preset === "claude" || options.preset === "all") {
|
|
137
|
+
files.push({
|
|
138
|
+
path: "CLAUDE.md",
|
|
139
|
+
content: renderClaudeMd(contextPath, promptPath)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (options.preset === "gemini" || options.preset === "all") {
|
|
143
|
+
files.push({
|
|
144
|
+
path: "GEMINI.md",
|
|
145
|
+
content: renderGeminiMd(contextPath, promptPath)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (options.preset === "copilot" || options.preset === "all") {
|
|
149
|
+
files.push(
|
|
150
|
+
{
|
|
151
|
+
path: ".github/copilot-instructions.md",
|
|
152
|
+
content: renderCopilotInstructions(contextPath, promptPath)
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
path: ".github/prompts/cms-lab-fix.prompt.md",
|
|
156
|
+
content: renderCopilotPrompt(contextPath, promptPath)
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
files.push(
|
|
161
|
+
{
|
|
162
|
+
path: contextPath,
|
|
163
|
+
content: renderContextFile(options)
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
path: promptPath,
|
|
167
|
+
content: renderPromptFile(options)
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
return files;
|
|
171
|
+
}
|
|
172
|
+
function renderAgentsMd(contextPath, promptPath) {
|
|
173
|
+
return `# cms-lab agent handoff
|
|
174
|
+
|
|
175
|
+
This project uses cms-lab to check CMS content against the routes and fields the Next.js app expects.
|
|
176
|
+
|
|
177
|
+
Before changing code for CMS-related failures:
|
|
178
|
+
|
|
179
|
+
1. Read \`${contextPath}\` for the project scan model.
|
|
180
|
+
2. Read \`${promptPath}\` for a task prompt you can adapt.
|
|
181
|
+
3. Check the public cms-lab docs before guessing behavior:
|
|
182
|
+
- GitHub: ${GITHUB_URL}
|
|
183
|
+
- npm: ${NPM_URL}
|
|
184
|
+
- Docs: ${DOCS_URL}
|
|
185
|
+
4. Run \`npx cms-lab doctor\` before a first scan.
|
|
186
|
+
5. Run \`npx cms-lab scan --ci --report\` to reproduce diagnostics.
|
|
187
|
+
6. Use \`npx cms-lab explain <CODE>\` for diagnostic details.
|
|
188
|
+
|
|
189
|
+
Do not print or commit CMS tokens, webhook URLs, private site URLs, raw CMS payloads, or local absolute paths.
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
function renderClaudeMd(contextPath, promptPath) {
|
|
193
|
+
return `# cms-lab Claude Code context
|
|
194
|
+
|
|
195
|
+
@${contextPath}
|
|
196
|
+
@${promptPath}
|
|
197
|
+
|
|
198
|
+
Use these cms-lab files before changing code for CMS route, field, SEO, or report diagnostics. Run \`npx cms-lab doctor\` first when connecting a project, then run \`npx cms-lab scan --ci --report\` to reproduce the current diagnostics.
|
|
199
|
+
|
|
200
|
+
Do not print or commit CMS tokens, webhook URLs, private site URLs, raw CMS payloads, or local absolute paths.
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
function renderGeminiMd(contextPath, promptPath) {
|
|
204
|
+
return `# cms-lab Gemini CLI context
|
|
205
|
+
|
|
206
|
+
@${contextPath}
|
|
207
|
+
@${promptPath}
|
|
208
|
+
|
|
209
|
+
Use these cms-lab files before changing code for CMS route, field, SEO, or report diagnostics. Run \`npx cms-lab doctor\` first when connecting a project, then run \`npx cms-lab scan --ci --report\` to reproduce the current diagnostics.
|
|
210
|
+
|
|
211
|
+
Do not print or commit CMS tokens, webhook URLs, private site URLs, raw CMS payloads, or local absolute paths.
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
function renderCopilotInstructions(contextPath, promptPath) {
|
|
215
|
+
return `# cms-lab Copilot instructions
|
|
216
|
+
|
|
217
|
+
This project uses cms-lab to check CMS content against the routes and fields the Next.js app expects.
|
|
218
|
+
|
|
219
|
+
- Read \`${contextPath}\` before changing code for CMS diagnostics.
|
|
220
|
+
- Use \`${promptPath}\` when asked to investigate a cms-lab failure.
|
|
221
|
+
- Prefer reproducing with \`npx cms-lab scan --ci --report\` before editing application code.
|
|
222
|
+
- Use \`npx cms-lab explain <CODE>\` before deciding where a diagnostic should be fixed.
|
|
223
|
+
- Do not print or commit CMS tokens, webhook URLs, private site URLs, raw CMS payloads, or local absolute paths.
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
function renderCopilotPrompt(contextPath, promptPath) {
|
|
227
|
+
return `# Fix cms-lab diagnostics
|
|
228
|
+
|
|
229
|
+
Use this prompt when investigating cms-lab diagnostics in this repository.
|
|
230
|
+
|
|
231
|
+
1. Read \`${contextPath}\`.
|
|
232
|
+
2. Read \`${promptPath}\`.
|
|
233
|
+
3. Run \`npx cms-lab doctor\`.
|
|
234
|
+
4. Run \`npx cms-lab scan --ci --report\`.
|
|
235
|
+
5. For each diagnostic code, run \`npx cms-lab explain <CODE>\`.
|
|
236
|
+
6. Decide whether the fix belongs in CMS content, cms-lab route mapping, or application code.
|
|
237
|
+
7. Make the smallest verifiable change and rerun cms-lab.
|
|
238
|
+
|
|
239
|
+
Do not expose CMS tokens, webhook URLs, private site URLs, raw CMS payloads, or local absolute paths.
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
function renderContextFile(options) {
|
|
243
|
+
return `# cms-lab agent context
|
|
244
|
+
|
|
245
|
+
Use this file to orient coding agents before they work on CMS route, field, SEO, or report failures.
|
|
246
|
+
|
|
247
|
+
## Canonical references
|
|
248
|
+
|
|
249
|
+
- GitHub: ${GITHUB_URL}
|
|
250
|
+
- npm: ${NPM_URL}
|
|
251
|
+
- Docs: ${DOCS_URL}
|
|
252
|
+
|
|
253
|
+
## Project scan model
|
|
254
|
+
|
|
255
|
+
- Config file: ${options.configFile}
|
|
256
|
+
- Framework: ${projectLabel(options.project)}
|
|
257
|
+
- CMS provider: ${options.config.cms.provider}
|
|
258
|
+
- Site URL: configured in cms-lab config (redacted)
|
|
259
|
+
|
|
260
|
+
${cmsDetails(options.config.cms)}
|
|
261
|
+
|
|
262
|
+
## Route mappings
|
|
263
|
+
|
|
264
|
+
${routeMappings(options.config)}
|
|
265
|
+
|
|
266
|
+
## Checks
|
|
267
|
+
|
|
268
|
+
${checksSummary(options.config)}
|
|
269
|
+
|
|
270
|
+
## Recommended commands
|
|
271
|
+
|
|
272
|
+
\`\`\`sh
|
|
273
|
+
npx cms-lab doctor
|
|
274
|
+
npx cms-lab scan --ci --report
|
|
275
|
+
npx cms-lab scan --json
|
|
276
|
+
npx cms-lab explain CMS-ROUTE-404
|
|
277
|
+
\`\`\`
|
|
278
|
+
|
|
279
|
+
## Agent workflow
|
|
280
|
+
|
|
281
|
+
- Read the cms-lab config and this context before editing application code.
|
|
282
|
+
- Reproduce diagnostics with \`doctor\` or \`scan\` before changing behavior.
|
|
283
|
+
- Prefer fixing route mappings, CMS field assumptions, and template guards over hiding diagnostics.
|
|
284
|
+
- Keep generated reports and JSON output out of commits unless the team explicitly wants them.
|
|
285
|
+
- Do not expose CMS tokens, private URLs, webhook URLs, raw CMS payloads, or local absolute paths.
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
function renderPromptFile(options) {
|
|
289
|
+
return `# cms-lab agent prompt
|
|
290
|
+
|
|
291
|
+
You are working in a Next.js project that uses cms-lab to catch CMS-driven failures before deploy.
|
|
292
|
+
|
|
293
|
+
This prompt is suitable for agents such as Claude Code, Codex, Gemini CLI, Antigravity, OpenCode, and similar coding agents.
|
|
294
|
+
|
|
295
|
+
Start here:
|
|
296
|
+
|
|
297
|
+
1. Read the cms-lab docs: ${DOCS_URL}
|
|
298
|
+
2. Check the npm package usage: ${NPM_URL}
|
|
299
|
+
3. Check the source repository if behavior is unclear: ${GITHUB_URL}
|
|
300
|
+
4. Inspect the local cms-lab config: ${options.configFile}
|
|
301
|
+
5. Run:
|
|
302
|
+
|
|
303
|
+
\`\`\`sh
|
|
304
|
+
npx cms-lab doctor
|
|
305
|
+
npx cms-lab scan --ci --report
|
|
306
|
+
\`\`\`
|
|
307
|
+
|
|
308
|
+
Project facts:
|
|
309
|
+
|
|
310
|
+
- Framework: ${projectLabel(options.project)}
|
|
311
|
+
- CMS provider: ${options.config.cms.provider}
|
|
312
|
+
- Route mappings: ${options.config.routes.map((route) => `${route.type} -> ${route.pattern}`).join(", ")}
|
|
313
|
+
|
|
314
|
+
When diagnostics appear, use \`npx cms-lab explain <CODE>\` before deciding whether the fix belongs in CMS content, route mapping, or application code.
|
|
315
|
+
|
|
316
|
+
Never reveal CMS tokens, private site URLs, webhook URLs, raw CMS payloads, or local absolute paths in commits, issues, pull requests, logs, or summaries.
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
319
|
+
function cmsDetails(config) {
|
|
320
|
+
if (config.provider === "prismic") {
|
|
321
|
+
return `## CMS details
|
|
322
|
+
|
|
323
|
+
- Repository: ${config.repositoryName}`;
|
|
324
|
+
}
|
|
325
|
+
if (config.provider === "strapi") {
|
|
326
|
+
return `## CMS details
|
|
327
|
+
|
|
328
|
+
- Collections: ${config.collections.map((collection) => `${collection.type} (${collection.endpoint})`).join(", ")}`;
|
|
329
|
+
}
|
|
330
|
+
if (config.provider === "directus") {
|
|
331
|
+
return `## CMS details
|
|
332
|
+
|
|
333
|
+
- Collections: ${config.collections.map((collection) => `${collection.type} (${collection.collection})`).join(", ")}`;
|
|
334
|
+
}
|
|
335
|
+
if (config.provider === "contentful") {
|
|
336
|
+
return `## CMS details
|
|
337
|
+
|
|
338
|
+
- Space: ${config.spaceId}
|
|
339
|
+
- Environment: ${config.environment ?? "master"}
|
|
340
|
+
- Content types: ${config.contentTypes.map((contentType) => `${contentType.type} (${contentType.contentType})`).join(", ")}`;
|
|
341
|
+
}
|
|
342
|
+
if (config.provider === "sanity") {
|
|
343
|
+
return `## CMS details
|
|
344
|
+
|
|
345
|
+
- Project: ${config.projectId}
|
|
346
|
+
- Dataset: ${config.dataset}
|
|
347
|
+
- Document types: ${config.contentTypes.map((contentType) => `${contentType.type} (${contentType.documentType})`).join(", ")}`;
|
|
348
|
+
}
|
|
349
|
+
return `## CMS details
|
|
350
|
+
|
|
351
|
+
- Content types: ${(config.contentTypes ?? [
|
|
352
|
+
{ type: "page", endpoint: "pages" },
|
|
353
|
+
{ type: "post", endpoint: "posts" }
|
|
354
|
+
]).map((contentType) => `${contentType.type} (${contentType.endpoint})`).join(", ")}`;
|
|
355
|
+
}
|
|
356
|
+
function routeMappings(config) {
|
|
357
|
+
return config.routes.map((route) => `- ${route.type} -> ${route.pattern}`).join("\n");
|
|
358
|
+
}
|
|
359
|
+
function checksSummary(config) {
|
|
360
|
+
const checks = config.checks;
|
|
361
|
+
const lines = [
|
|
362
|
+
`- Routes: ${checkState(checks?.routes)}`,
|
|
363
|
+
`- SEO: ${checkState(checks?.seo)}`,
|
|
364
|
+
`- Image alt text: ${checkState(checks?.a11y ?? checks?.images)}`,
|
|
365
|
+
`- Required fields: ${requiredFieldsSummary(config)}`
|
|
366
|
+
];
|
|
367
|
+
return lines.join("\n");
|
|
368
|
+
}
|
|
369
|
+
function checkState(value) {
|
|
370
|
+
if (value === false) {
|
|
371
|
+
return "disabled";
|
|
372
|
+
}
|
|
373
|
+
if (value === true || typeof value === "object") {
|
|
374
|
+
return "enabled";
|
|
375
|
+
}
|
|
376
|
+
return "default";
|
|
377
|
+
}
|
|
378
|
+
function requiredFieldsSummary(config) {
|
|
379
|
+
const fields = config.checks?.fields;
|
|
380
|
+
if (!fields || typeof fields === "boolean") {
|
|
381
|
+
return checkState(fields);
|
|
382
|
+
}
|
|
383
|
+
const required = fields.required ?? [];
|
|
384
|
+
if (required.length === 0) {
|
|
385
|
+
return "enabled";
|
|
386
|
+
}
|
|
387
|
+
return required.map(
|
|
388
|
+
(field) => `${field.type}.${field.path} (${field.severity ?? "error"})`
|
|
389
|
+
).join(", ");
|
|
390
|
+
}
|
|
391
|
+
function projectLabel(project) {
|
|
392
|
+
if (project.framework === "next" && project.router === "app") {
|
|
393
|
+
return "Next.js App Router";
|
|
394
|
+
}
|
|
395
|
+
if (project.framework === "next" && project.router === "pages") {
|
|
396
|
+
return "Next.js Pages Router";
|
|
397
|
+
}
|
|
398
|
+
return `${project.framework} ${project.router}`;
|
|
399
|
+
}
|
|
400
|
+
function trimSlashes(value) {
|
|
401
|
+
let start = 0;
|
|
402
|
+
let end = value.length;
|
|
403
|
+
while (start < end && value.charCodeAt(start) === 47) {
|
|
404
|
+
start += 1;
|
|
405
|
+
}
|
|
406
|
+
while (end > start && value.charCodeAt(end - 1) === 47) {
|
|
407
|
+
end -= 1;
|
|
408
|
+
}
|
|
409
|
+
return value.slice(start, end);
|
|
410
|
+
}
|
|
411
|
+
|
|
118
412
|
// src/output.ts
|
|
119
413
|
import pc from "picocolors";
|
|
120
414
|
function formatPrettyResult(result, options = {}) {
|
|
@@ -219,7 +513,7 @@ var noColor = {
|
|
|
219
513
|
// src/index.ts
|
|
220
514
|
async function runCli(argv, dependencies = {}) {
|
|
221
515
|
let exitCode = 0;
|
|
222
|
-
const program = new Command().name("cms-lab").description("Catch CMS bugs before deploy.").version("1.0.
|
|
516
|
+
const program = new Command().name("cms-lab").description("Catch CMS bugs before deploy.").version("1.0.5").exitOverride().configureOutput({
|
|
223
517
|
writeOut: (text) => writeStdout(dependencies, text),
|
|
224
518
|
writeErr: (text) => writeStderr(dependencies, text)
|
|
225
519
|
});
|
|
@@ -230,6 +524,8 @@ Examples:
|
|
|
230
524
|
cms-lab init
|
|
231
525
|
cms-lab doctor --config cms-lab.config.ts
|
|
232
526
|
cms-lab scan --ci --report
|
|
527
|
+
cms-lab agent-context
|
|
528
|
+
cms-lab agent-context --preset all
|
|
233
529
|
cms-lab explain CMS-ROUTE-404
|
|
234
530
|
`
|
|
235
531
|
);
|
|
@@ -327,6 +623,28 @@ Examples:
|
|
|
327
623
|
).action(async (options) => {
|
|
328
624
|
exitCode = await runInit(options, dependencies);
|
|
329
625
|
});
|
|
626
|
+
program.command("agent-context").description("Generate AI-agent handoff files for cms-lab projects.").option("--config <path>", "Path to cms-lab config file").option(
|
|
627
|
+
"--out <dir>",
|
|
628
|
+
"Directory for generated cms-lab agent files",
|
|
629
|
+
".cms-lab"
|
|
630
|
+
).option(
|
|
631
|
+
"--preset <preset>",
|
|
632
|
+
"Agent preset: generic, codex, claude, gemini, copilot, or all",
|
|
633
|
+
"generic"
|
|
634
|
+
).option("--force", "Overwrite existing generated files").option("--no-agents-md", "Do not create or update AGENTS.md").addHelpText(
|
|
635
|
+
"after",
|
|
636
|
+
`
|
|
637
|
+
Examples:
|
|
638
|
+
cms-lab agent-context
|
|
639
|
+
cms-lab agent-context --config cms-lab.config.ts
|
|
640
|
+
cms-lab agent-context --preset all
|
|
641
|
+
cms-lab agent-context --preset claude
|
|
642
|
+
cms-lab agent-context --preset copilot
|
|
643
|
+
cms-lab agent-context --out .cms-lab --force
|
|
644
|
+
`
|
|
645
|
+
).action(async (options) => {
|
|
646
|
+
exitCode = await runAgentContext(options, dependencies);
|
|
647
|
+
});
|
|
330
648
|
try {
|
|
331
649
|
await program.parseAsync(argv, { from: "user" });
|
|
332
650
|
return exitCode;
|
|
@@ -392,22 +710,13 @@ async function runScan(options, dependencies) {
|
|
|
392
710
|
debug.log(1, `config ${loaded.configFile ?? "inline"}`);
|
|
393
711
|
debug.log(1, `site ${config.site.url}`);
|
|
394
712
|
debug.log(1, `cms ${describeCms(config.cms)}`);
|
|
395
|
-
if (config.framework.router !== "app") {
|
|
396
|
-
throw new ConfigLoadError(
|
|
397
|
-
"cms-lab only supports Next.js App Router projects"
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
713
|
const endProject = debug.time("project detection", 2);
|
|
401
714
|
const project = await detectNextProject(cwd);
|
|
402
715
|
endProject();
|
|
403
|
-
|
|
404
|
-
throw new ConfigLoadError(
|
|
405
|
-
"cms-lab only supports Next.js App Router projects"
|
|
406
|
-
);
|
|
407
|
-
}
|
|
716
|
+
assertConfiguredRouterMatchesProject(config.framework.router, project);
|
|
408
717
|
debug.log(
|
|
409
718
|
1,
|
|
410
|
-
`project next ${project.router}
|
|
719
|
+
`project next ${project.router} dir=${projectDirectory(project)}`
|
|
411
720
|
);
|
|
412
721
|
const endCms = debug.time("cms fetch", 2);
|
|
413
722
|
const documents = await fetchCmsDocuments(config.cms, dependencies);
|
|
@@ -574,26 +883,17 @@ async function runDoctor(options, dependencies) {
|
|
|
574
883
|
`config ok${loaded.configFile ? ` - ${loaded.configFile}` : ""}
|
|
575
884
|
`
|
|
576
885
|
);
|
|
577
|
-
if (config.framework.router !== "app") {
|
|
578
|
-
throw new ConfigLoadError(
|
|
579
|
-
"cms-lab only supports Next.js App Router projects"
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
886
|
const endProject = debug.time("project detection", 2);
|
|
583
887
|
const project = await detectNextProject(cwd);
|
|
584
888
|
endProject();
|
|
585
|
-
|
|
586
|
-
throw new ConfigLoadError(
|
|
587
|
-
"cms-lab only supports Next.js App Router projects"
|
|
588
|
-
);
|
|
589
|
-
}
|
|
889
|
+
assertConfiguredRouterMatchesProject(config.framework.router, project);
|
|
590
890
|
debug.log(
|
|
591
891
|
1,
|
|
592
|
-
`project next ${project.router}
|
|
892
|
+
`project next ${project.router} dir=${projectDirectory(project)}`
|
|
593
893
|
);
|
|
594
894
|
writeStdout(
|
|
595
895
|
dependencies,
|
|
596
|
-
`next
|
|
896
|
+
`next ${project.router} ok - ${projectDirectory(project)}
|
|
597
897
|
`
|
|
598
898
|
);
|
|
599
899
|
const endSite = debug.time("site probe", 2);
|
|
@@ -694,6 +994,65 @@ async function runInit(options, dependencies) {
|
|
|
694
994
|
return 2;
|
|
695
995
|
}
|
|
696
996
|
}
|
|
997
|
+
async function runAgentContext(options, dependencies) {
|
|
998
|
+
const cwd = dependencies.cwd ?? process.cwd();
|
|
999
|
+
try {
|
|
1000
|
+
const loaded = await loadCmsLabConfig({ cwd, configPath: options.config });
|
|
1001
|
+
const project = await detectNextProject(cwd);
|
|
1002
|
+
assertConfiguredRouterMatchesProject(
|
|
1003
|
+
loaded.config.framework.router,
|
|
1004
|
+
project
|
|
1005
|
+
);
|
|
1006
|
+
const files = renderAgentContextFiles({
|
|
1007
|
+
config: loaded.config,
|
|
1008
|
+
project,
|
|
1009
|
+
configFile: safeRelativePath(cwd, loaded.configFile),
|
|
1010
|
+
outputDir: options.out ?? ".cms-lab",
|
|
1011
|
+
includeAgentsMd: options.agentsMd !== false,
|
|
1012
|
+
preset: parseAgentContextPreset(options.preset)
|
|
1013
|
+
});
|
|
1014
|
+
const existingFiles = [];
|
|
1015
|
+
for (const file of files) {
|
|
1016
|
+
const target = resolve(cwd, file.path);
|
|
1017
|
+
if (!isInsideDirectory(cwd, target)) {
|
|
1018
|
+
throw new ConfigLoadError(
|
|
1019
|
+
`Refusing to write outside the project: ${file.path}`
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
if (!options.force && await fileExists(target)) {
|
|
1023
|
+
existingFiles.push(file.path);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (existingFiles.length > 0) {
|
|
1027
|
+
writeStderr(
|
|
1028
|
+
dependencies,
|
|
1029
|
+
`Config error: ${existingFiles.join(", ")} already exists. Use --force to overwrite it.
|
|
1030
|
+
`
|
|
1031
|
+
);
|
|
1032
|
+
return 2;
|
|
1033
|
+
}
|
|
1034
|
+
for (const file of files) {
|
|
1035
|
+
const target = resolve(cwd, file.path);
|
|
1036
|
+
await mkdir(dirname(target), { recursive: true });
|
|
1037
|
+
await writeFile(target, file.content, "utf8");
|
|
1038
|
+
writeStdout(dependencies, `created ${file.path}
|
|
1039
|
+
`);
|
|
1040
|
+
}
|
|
1041
|
+
return 0;
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
if (error instanceof ConfigLoadError) {
|
|
1044
|
+
writeStderr(
|
|
1045
|
+
dependencies,
|
|
1046
|
+
`Config error: ${redactSensitive(error.message)}
|
|
1047
|
+
`
|
|
1048
|
+
);
|
|
1049
|
+
return 2;
|
|
1050
|
+
}
|
|
1051
|
+
writeStderr(dependencies, `Config error: ${safeMessageFrom(error)}
|
|
1052
|
+
`);
|
|
1053
|
+
return 2;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
697
1056
|
function writeStdout(dependencies, text) {
|
|
698
1057
|
if (dependencies.stdout) {
|
|
699
1058
|
dependencies.stdout(text);
|
|
@@ -802,6 +1161,15 @@ function parseVerbosity(debug, verbose) {
|
|
|
802
1161
|
}
|
|
803
1162
|
throw new ConfigLoadError("--verbose must be one of: 0, 1, 2, 3");
|
|
804
1163
|
}
|
|
1164
|
+
function parseAgentContextPreset(value) {
|
|
1165
|
+
const preset = value ?? "generic";
|
|
1166
|
+
if (preset === "generic" || preset === "codex" || preset === "claude" || preset === "gemini" || preset === "copilot" || preset === "all") {
|
|
1167
|
+
return preset;
|
|
1168
|
+
}
|
|
1169
|
+
throw new ConfigLoadError(
|
|
1170
|
+
"--preset must be one of: generic, codex, claude, gemini, copilot, all"
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
805
1173
|
function shouldUseColor(options, dependencies) {
|
|
806
1174
|
if (options.ci || options.color === false) {
|
|
807
1175
|
return false;
|
|
@@ -846,6 +1214,16 @@ function describeCms(config) {
|
|
|
846
1214
|
config.collections.map((collection) => collection.collection)
|
|
847
1215
|
)}`;
|
|
848
1216
|
}
|
|
1217
|
+
if (config.provider === "contentful") {
|
|
1218
|
+
return `contentful space=${config.spaceId} environment=${config.environment ?? "master"} contentTypes=${formatList(
|
|
1219
|
+
config.contentTypes.map((contentType) => contentType.contentType)
|
|
1220
|
+
)}`;
|
|
1221
|
+
}
|
|
1222
|
+
if (config.provider === "sanity") {
|
|
1223
|
+
return `sanity project=${config.projectId} dataset=${config.dataset} documentTypes=${formatList(
|
|
1224
|
+
config.contentTypes.map((contentType) => contentType.documentType)
|
|
1225
|
+
)}`;
|
|
1226
|
+
}
|
|
849
1227
|
return `wordpress url=${safeUrl(config.url)} contentTypes=${formatList(
|
|
850
1228
|
config.contentTypes?.map((contentType) => contentType.endpoint) ?? [
|
|
851
1229
|
"pages",
|
|
@@ -901,6 +1279,14 @@ async function fetchCmsDocuments(config, dependencies) {
|
|
|
901
1279
|
if (config.provider === "directus") {
|
|
902
1280
|
return defaultFetchDirectusDocuments(config, { fetch: dependencies.fetch });
|
|
903
1281
|
}
|
|
1282
|
+
if (config.provider === "contentful") {
|
|
1283
|
+
return defaultFetchContentfulDocuments(config, {
|
|
1284
|
+
fetch: dependencies.fetch
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
if (config.provider === "sanity") {
|
|
1288
|
+
return defaultFetchSanityDocuments(config, { fetch: dependencies.fetch });
|
|
1289
|
+
}
|
|
904
1290
|
return defaultFetchWordPressDocuments(config, { fetch: dependencies.fetch });
|
|
905
1291
|
}
|
|
906
1292
|
function assertTypeFilterMatched(documents, types) {
|
|
@@ -1095,6 +1481,30 @@ function parseNotifyOn(value) {
|
|
|
1095
1481
|
"--notify-on must be one of: always, failure, diagnostics"
|
|
1096
1482
|
);
|
|
1097
1483
|
}
|
|
1484
|
+
function assertConfiguredRouterMatchesProject(configuredRouter, project) {
|
|
1485
|
+
if (configuredRouter !== project.router) {
|
|
1486
|
+
throw new ConfigLoadError(
|
|
1487
|
+
`cms-lab config declares Next.js ${configuredRouter} router but detected ${project.router} router`
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function projectDirectory(project) {
|
|
1492
|
+
return project.appDir ?? project.pagesDir ?? project.rootDir;
|
|
1493
|
+
}
|
|
1494
|
+
function safeRelativePath(cwd, path) {
|
|
1495
|
+
if (!path) {
|
|
1496
|
+
return "cms-lab config";
|
|
1497
|
+
}
|
|
1498
|
+
const relativePath = relative(cwd, path);
|
|
1499
|
+
if (relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath) && !relativePath.includes(`..${sep}`)) {
|
|
1500
|
+
return relativePath.split(sep).join("/");
|
|
1501
|
+
}
|
|
1502
|
+
return "custom cms-lab config";
|
|
1503
|
+
}
|
|
1504
|
+
function isInsideDirectory(cwd, target) {
|
|
1505
|
+
const relativePath = relative(cwd, target);
|
|
1506
|
+
return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath) && !relativePath.includes(`..${sep}`);
|
|
1507
|
+
}
|
|
1098
1508
|
async function fileExists(path) {
|
|
1099
1509
|
try {
|
|
1100
1510
|
await access(path);
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cms-lab/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Catch CMS bugs before deploy.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"cms",
|
|
18
|
+
"contentful",
|
|
18
19
|
"nextjs",
|
|
19
20
|
"prismic",
|
|
21
|
+
"sanity",
|
|
20
22
|
"strapi",
|
|
21
23
|
"directus",
|
|
22
24
|
"wordpress",
|
|
@@ -45,13 +47,15 @@
|
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"commander": "^14.0.2",
|
|
47
49
|
"picocolors": "^1.1.1",
|
|
48
|
-
"@cms-lab/
|
|
49
|
-
"@cms-lab/directus": "1.0.
|
|
50
|
-
"@cms-lab/
|
|
51
|
-
"@cms-lab/
|
|
52
|
-
"@cms-lab/prismic": "1.0.
|
|
53
|
-
"@cms-lab/
|
|
54
|
-
"@cms-lab/
|
|
50
|
+
"@cms-lab/contentful": "1.0.5",
|
|
51
|
+
"@cms-lab/directus": "1.0.5",
|
|
52
|
+
"@cms-lab/core": "1.0.5",
|
|
53
|
+
"@cms-lab/next": "1.0.5",
|
|
54
|
+
"@cms-lab/prismic": "1.0.5",
|
|
55
|
+
"@cms-lab/sanity": "1.0.5",
|
|
56
|
+
"@cms-lab/wordpress": "1.0.5",
|
|
57
|
+
"@cms-lab/strapi": "1.0.5",
|
|
58
|
+
"@cms-lab/reporter": "1.0.5"
|
|
55
59
|
},
|
|
56
60
|
"author": "Afaq Rashid",
|
|
57
61
|
"scripts": {
|