@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 CHANGED
@@ -3,7 +3,7 @@
3
3
  CLI for catching CMS-driven Next.js failures before deploy.
4
4
 
5
5
  ```sh
6
- npx @cms-lab/cli scan
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, Strapi, Directus,
30
- and WordPress. It validates configured CMS route mappings, route reachability,
31
- UID gaps, SEO metadata, image alt text, and project-specific required fields.
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
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-A2CHTMQR.js";
4
+ } from "./chunk-BQIQ53GI.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var exitCode = await runCli(process.argv.slice(2));
@@ -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.4").exitOverride().configureOutput({
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
- if (project.router !== "app") {
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} appDir=${project.appDir ?? "none"}`
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
- if (project.router !== "app") {
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} appDir=${project.appDir ?? "none"}`
892
+ `project next ${project.router} dir=${projectDirectory(project)}`
593
893
  );
594
894
  writeStdout(
595
895
  dependencies,
596
- `next app ok - ${project.appDir ?? project.rootDir}
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runCli
3
- } from "./chunk-A2CHTMQR.js";
3
+ } from "./chunk-BQIQ53GI.js";
4
4
  export {
5
5
  runCli
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cms-lab/cli",
3
- "version": "1.0.4",
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/core": "1.0.4",
49
- "@cms-lab/directus": "1.0.4",
50
- "@cms-lab/next": "1.0.4",
51
- "@cms-lab/reporter": "1.0.4",
52
- "@cms-lab/prismic": "1.0.4",
53
- "@cms-lab/wordpress": "1.0.4",
54
- "@cms-lab/strapi": "1.0.4"
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": {