Skip to content

Commit d4b7354

Browse files
committed
fix(docs): inject required frontmatter and fix naming collisions in generated API docs
The generate-docs.ts beforeWrite hook was missing url, metaTitle, and metaDescription fields required by source.config.ts. Additionally, the naming regex stripped ById entirely, causing list and get-by-id endpoints to collide on the same filename and corrupt each other's content. generate-docs.ts: - Derive and inject url, metaTitle, metaDescription from operation metadata - Fix naming regex (By[A-Z][a-z]* -> By[A-Z][a-z]{2,}) to preserve ById - Use slice-based content replacement to prevent corruption sync-management-api-docs.yml: - Clean stale MDX files before generation - Run lint:links, lint:spellcheck, and next build before committing - Gate commit, push, and Vercel deploy on changes detected
1 parent cdf8696 commit d4b7354

File tree

2 files changed

+74
-21
lines changed

2 files changed

+74
-21
lines changed

.github/workflows/sync-management-api-docs.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ jobs:
3535
working-directory: apps/docs
3636
run: pnpm fetch-openapi
3737

38+
- name: Clean stale endpoint files
39+
run: find apps/docs/content/docs/management-api/endpoints -name '*.mdx' -type f -delete
40+
3841
- name: Generate docs
3942
working-directory: apps/docs
4043
run: pnpm tsx scripts/generate-docs.ts
@@ -51,6 +54,19 @@ jobs:
5154
git status --short -- apps/docs/content/docs/management-api/
5255
fi
5356
57+
- name: Validate links
58+
if: steps.changes.outputs.changed == 'true'
59+
run: pnpm run lint:links
60+
61+
- name: Validate spelling
62+
if: steps.changes.outputs.changed == 'true'
63+
run: pnpm run lint:spellcheck
64+
65+
- name: Build docs
66+
if: steps.changes.outputs.changed == 'true'
67+
working-directory: apps/docs
68+
run: pnpm exec next build
69+
5470
- name: Commit and push
5571
if: steps.changes.outputs.changed == 'true'
5672
run: |
@@ -61,4 +77,5 @@ jobs:
6177
git push "https://x-access-token:${{ secrets.BOT_TOKEN_DOCS_COMMIT }}@github.com/${{ github.repository }}.git" HEAD:${{ github.ref_name }}
6278
6379
- name: Trigger Vercel deploy
80+
if: steps.changes.outputs.changed == 'true'
6481
run: curl --fail -X POST "${{ secrets.VERCEL_DEPLOY_HOOK_URL }}"

apps/docs/scripts/generate-docs.ts

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ void generateFiles({
1616

1717
const cleanName = operationId
1818
.replace(/V\d+/g, "")
19-
.replace(/By[A-Z][a-z]*/g, "")
19+
.replace(/By[A-Z][a-z]{2,}/g, "")
2020
.replace(/([a-z])([A-Z])/g, "$1-$2")
2121
.toLowerCase();
2222

@@ -28,29 +28,65 @@ void generateFiles({
2828
beforeWrite(files) {
2929
for (const file of files) {
3030
const frontmatterMatch = file.content.match(/^---\n([\s\S]*?)\n---/);
31-
if (frontmatterMatch) {
32-
const frontmatter = frontmatterMatch[1];
33-
if (frontmatter.includes("_openapi:")) {
34-
const apiPageMatch = file.content.match(/<APIPage[^>]*operations=\{(\[.*?\])\}/s);
35-
if (apiPageMatch) {
36-
try {
37-
const operations = JSON.parse(apiPageMatch[1].replace(/'/g, '"'));
38-
if (operations[0]?.path) {
39-
const path = operations[0].path;
40-
41-
const updatedFrontmatter = frontmatter.replace(
42-
/(_openapi:\s*\n\s*method:)/,
43-
`_openapi:\n path: "${path}"\n method:`,
44-
);
45-
46-
file.content = file.content.replace(frontmatter, updatedFrontmatter);
47-
}
48-
} catch (e) {
49-
console.warn(`Failed to parse operations for ${file.path}:`, e);
50-
}
31+
if (!frontmatterMatch) continue;
32+
33+
let frontmatter = frontmatterMatch[1];
34+
35+
const titleMatch = frontmatter.match(/^title:\s*(.+)$/m);
36+
const title = titleMatch
37+
? titleMatch[1].replace(/^['"]|['"]$/g, "")
38+
: "";
39+
40+
const methodMatch = frontmatter.match(/method:\s*(\w+)/);
41+
const method = methodMatch ? methodMatch[1].toUpperCase() : "";
42+
43+
const apiPageMatch = file.content.match(
44+
/<APIPage[^>]*operations=\{(\[.*?\])\}/s,
45+
);
46+
let apiPath = "";
47+
if (apiPageMatch) {
48+
try {
49+
const operations = JSON.parse(apiPageMatch[1].replace(/'/g, '"'));
50+
if (operations[0]?.path) {
51+
apiPath = operations[0].path;
5152
}
53+
} catch (e) {
54+
console.warn(`Failed to parse operations for ${file.path}:`, e);
5255
}
5356
}
57+
58+
if (
59+
frontmatter.includes("_openapi:") &&
60+
!frontmatter.includes("path:") &&
61+
apiPath
62+
) {
63+
frontmatter = frontmatter.replace(
64+
/(_openapi:\s*\n\s*method:)/,
65+
`_openapi:\n path: "${apiPath}"\n method:`,
66+
);
67+
}
68+
69+
const contentAfterFrontmatter = file.content.slice(
70+
frontmatterMatch[0].length,
71+
);
72+
const descMatch = contentAfterFrontmatter.match(/\n\n([^<\n{][^\n]+)\n/);
73+
const description = descMatch ? descMatch[1].trim() : title;
74+
75+
const slug = file.path
76+
.replace(/^.*endpoints\//, "")
77+
.replace(/\.mdx$/, "");
78+
const urlPath = `/management-api/endpoints/${slug}`;
79+
const metaTitle = `${method} ${apiPath} | ${title} - Prisma Postgres`;
80+
const descriptionClean = description.replace(/\.$/, "");
81+
const metaDescription = `Management API: ${descriptionClean}. ${method} ${apiPath}.`;
82+
83+
frontmatter += `\nurl: ${urlPath}`;
84+
frontmatter += `\nmetaTitle: '${metaTitle.replace(/'/g, "''")}'`;
85+
frontmatter += `\nmetaDescription: '${metaDescription.replace(/'/g, "''")}'`;
86+
87+
file.content =
88+
`---\n${frontmatter}\n---` +
89+
file.content.slice(frontmatterMatch[0].length);
5490
}
5591
},
5692
});

0 commit comments

Comments
 (0)