create-zudo-doc 2.4.1 → 2.5.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.
@@ -11,5 +11,5 @@ export { getSecondaryLang };
11
11
  *
12
12
  * Bumped in lockstep by scripts/release-create-zudo-doc.sh.
13
13
  */
14
- export declare const ZUDO_DOC_PIN = "^2.4.1";
14
+ export declare const ZUDO_DOC_PIN = "^2.5.1";
15
15
  export declare function scaffold(choices: UserChoices): Promise<void>;
package/dist/scaffold.js CHANGED
@@ -18,7 +18,7 @@ export { getSecondaryLang };
18
18
  *
19
19
  * Bumped in lockstep by scripts/release-create-zudo-doc.sh.
20
20
  */
21
- export const ZUDO_DOC_PIN = "^2.4.1";
21
+ export const ZUDO_DOC_PIN = "^2.5.1";
22
22
  /**
23
23
  * Files in `templates/base/**` that must never be copied into a generated
24
24
  * project. Each entry is matched against the path relative to `templates/base/`
@@ -582,7 +582,7 @@ function generatePackageJson(choices) {
582
582
  // @takazudo/zudo-doc/integrations/doc-history which in turn imports
583
583
  // @takazudo/zudo-doc-history-server/git-history. Without this dep the
584
584
  // plugin host fails at init with ERR_MODULE_NOT_FOUND — W8A (#1739).
585
- deps["@takazudo/zudo-doc-history-server"] = "^2.4.1";
585
+ deps["@takazudo/zudo-doc-history-server"] = "^2.5.1";
586
586
  // tsx is no longer needed here: the relocated package plugin imports the
587
587
  // runner directly (no `tsx -e` spawn) since the package ships compiled
588
588
  // dist/ — package-first migration #2321 (#2337).
@@ -212,10 +212,10 @@ export function generateSettingsFile(choices) {
212
212
  if (choices.features.includes("claudeResources")) {
213
213
  lines.push(` claudeResources: {`);
214
214
  lines.push(` claudeDir: ".claude",`);
215
- lines.push(` } as { claudeDir: string; projectRoot?: string } | false,`);
215
+ lines.push(` } as { claudeDir: string; projectRoot?: string; scanRoot?: string } | false,`);
216
216
  }
217
217
  else {
218
- lines.push(` claudeResources: false as { claudeDir: string; projectRoot?: string } | false,`);
218
+ lines.push(` claudeResources: false as { claudeDir: string; projectRoot?: string; scanRoot?: string } | false,`);
219
219
  }
220
220
  if (choices.features.includes("claudeResources")) {
221
221
  lines.push(` defaultLocaleOnlyPrefixes: [`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "2.4.1",
3
+ "version": "2.5.1",
4
4
  "description": "Create a new zudo-doc documentation site",
5
5
  "license": "MIT",
6
6
  "author": "Takeshi Takatsudo",
@@ -61,6 +61,7 @@
61
61
  "@types/fs-extra": "^11.0.4",
62
62
  "@types/minimist": "^1.2.5",
63
63
  "@types/node": "^22.0.0",
64
+ "gray-matter": "^4.0.3",
64
65
  "typescript": "^5.9.0",
65
66
  "vitest": "^4.1.0"
66
67
  }
@@ -147,6 +147,26 @@ describe("generateClaudeResourcesDocs", () => {
147
147
  expect(overview).toContain('<CategoryNav categories={');
148
148
  });
149
149
 
150
+ it("overview body has no prose line duplicating the frontmatter description", () => {
151
+ // Regression test for #2568: the body used to repeat the frontmatter
152
+ // `description` verbatim as a standalone prose line right below `---`.
153
+ generateClaudeResourcesDocs({
154
+ claudeDir,
155
+ projectRoot: tmpDir,
156
+ docsDir,
157
+ });
158
+
159
+ const overview = fs.readFileSync(
160
+ path.join(docsDir, "claude", "index.mdx"),
161
+ "utf8",
162
+ );
163
+ const parsed = matter(overview);
164
+ const description = parsed.data.description as string;
165
+ const bodyLines = parsed.content.split("\n");
166
+
167
+ expect(bodyLines).not.toContain(description);
168
+ });
169
+
150
170
  it("skill page has correct frontmatter", () => {
151
171
  generateClaudeResourcesDocs({
152
172
  claudeDir,
@@ -575,6 +595,88 @@ describe("generateClaudeResourcesDocs", () => {
575
595
  });
576
596
  });
577
597
 
598
+ // ---------------------------------------------------------------------------
599
+ // excludeDirs boundary matching (#2561)
600
+ // ---------------------------------------------------------------------------
601
+
602
+ describe("excludeDirs boundary matching", () => {
603
+ it("discovers CLAUDE.md in a prefix-colliding sibling of an excluded dir (dist-extra vs dist)", () => {
604
+ fs.mkdirSync(path.join(tmpDir, "dist-extra"), { recursive: true });
605
+ fs.writeFileSync(
606
+ path.join(tmpDir, "dist-extra", "CLAUDE.md"),
607
+ "# dist-extra instructions",
608
+ );
609
+
610
+ const result = generateClaudeResourcesDocs({
611
+ claudeDir,
612
+ projectRoot: tmpDir,
613
+ docsDir,
614
+ });
615
+
616
+ // root/CLAUDE.md (from the fixture) + dist-extra/CLAUDE.md
617
+ expect(result.claudemd).toBe(2);
618
+ const decoyPage = path.join(docsDir, "claude-md", "dist-extra.mdx");
619
+ expect(fs.existsSync(decoyPage)).toBe(true);
620
+ expect(fs.readFileSync(decoyPage, "utf8")).toContain("dist-extra instructions");
621
+ });
622
+
623
+ it("still excludes CLAUDE.md directly inside the excluded dist directory", () => {
624
+ fs.mkdirSync(path.join(tmpDir, "dist"), { recursive: true });
625
+ fs.writeFileSync(
626
+ path.join(tmpDir, "dist", "CLAUDE.md"),
627
+ "# dist instructions — should be excluded",
628
+ );
629
+
630
+ const result = generateClaudeResourcesDocs({
631
+ claudeDir,
632
+ projectRoot: tmpDir,
633
+ docsDir,
634
+ });
635
+
636
+ // Only root/CLAUDE.md (from the fixture) — dist/CLAUDE.md is excluded.
637
+ expect(result.claudemd).toBe(1);
638
+ expect(fs.existsSync(path.join(docsDir, "claude-md", "dist.mdx"))).toBe(false);
639
+ });
640
+
641
+ it("still excludes nested CLAUDE.md files under the excluded dist directory (exact-match boundary holds)", () => {
642
+ fs.mkdirSync(path.join(tmpDir, "dist", "nested"), { recursive: true });
643
+ fs.writeFileSync(
644
+ path.join(tmpDir, "dist", "nested", "CLAUDE.md"),
645
+ "# nested dist instructions — should be excluded",
646
+ );
647
+
648
+ const result = generateClaudeResourcesDocs({
649
+ claudeDir,
650
+ projectRoot: tmpDir,
651
+ docsDir,
652
+ });
653
+
654
+ // Only root/CLAUDE.md (from the fixture) — the whole dist/ subtree,
655
+ // including nested dirs, is excluded once the top-level dist match hits.
656
+ expect(result.claudemd).toBe(1);
657
+ expect(fs.existsSync(path.join(docsDir, "claude-md", "dist--nested.mdx"))).toBe(false);
658
+ });
659
+
660
+ it("still excludes docsDir contents when docsDir carries a trailing separator", () => {
661
+ // path.join preserves a trailing separator on a single argument, so the
662
+ // docsDir exclude entry keeps it — the boundary compare must not break.
663
+ fs.writeFileSync(
664
+ path.join(docsDir, "CLAUDE.md"),
665
+ "# decoy inside docsDir — should be excluded",
666
+ );
667
+
668
+ const result = generateClaudeResourcesDocs({
669
+ claudeDir,
670
+ projectRoot: tmpDir,
671
+ docsDir: docsDir + path.sep,
672
+ });
673
+
674
+ // Only root/CLAUDE.md (from the fixture) — docs/CLAUDE.md is excluded.
675
+ expect(result.claudemd).toBe(1);
676
+ expect(fs.existsSync(path.join(docsDir, "claude-md", "docs.mdx"))).toBe(false);
677
+ });
678
+ });
679
+
578
680
  // ---------------------------------------------------------------------------
579
681
  // CLAUDE.md repo-relative link downgrade (#2411, Class B)
580
682
  // ---------------------------------------------------------------------------
@@ -214,11 +214,19 @@ function findClaudeMdFiles(dir: string, excludeDirs: string[]): string[] {
214
214
  const results: string[] = [];
215
215
  if (!fs.existsSync(dir)) return results;
216
216
 
217
+ // Strip trailing separators (path.join preserves one on e.g. "docs/") so the
218
+ // boundary compare below stays exact for such entries too.
219
+ const excludes = excludeDirs.map((d) =>
220
+ d.endsWith(path.sep) ? d.slice(0, -path.sep.length) : d,
221
+ );
222
+
217
223
  for (const item of fs.readdirSync(dir)) {
218
224
  if (item === "node_modules") continue;
219
225
  if (item.startsWith(".")) continue;
220
226
  const itemPath = path.join(dir, item);
221
- if (excludeDirs.some((d) => itemPath.startsWith(d))) continue;
227
+ // Path-segment-boundary-aware: a raw startsWith(d) would also match a
228
+ // sibling like "dist-extra" against an excluded "dist" (#2561).
229
+ if (excludes.some((d) => itemPath === d || itemPath.startsWith(d + path.sep))) continue;
222
230
 
223
231
  // lstat (not stat) so symlinks aren't followed — a symlinked dir can point
224
232
  // back into the project (e.g. e2e fixtures linking to packages/) or out to
@@ -231,7 +239,7 @@ function findClaudeMdFiles(dir: string, excludeDirs: string[]): string[] {
231
239
  continue;
232
240
  }
233
241
  if (stat.isDirectory()) {
234
- results.push(...findClaudeMdFiles(itemPath, excludeDirs));
242
+ results.push(...findClaudeMdFiles(itemPath, excludes));
235
243
  } else if (stat.isFile() && item === "CLAUDE.md") {
236
244
  results.push(itemPath);
237
245
  }
@@ -698,8 +706,6 @@ sidebar_position: 899
698
706
  generated: true
699
707
  ---
700
708
 
701
- Claude Code configuration reference.
702
-
703
709
  ## Resources
704
710
 
705
711
  <CategoryNav categories={${categoriesAttr}} />