Setting the file. One moment.
Extract Endpoint Detail · Clerk Backend API · clerk/skills · Skills Docs
ContentsBack to the top of the page (opens in a new tab)
scripts/ extract-endpoint-detail.sh
Shell · 165 lines · 5 KB
path
> <
method
>
}
"
13 METHOD = " ${2 :? Usage : extract-endpoint-detail . sh < path > < method > } "
14 TMPDIR_WORK = $( mktemp -d )
15 trap 'rm -rf "$TMPDIR_WORK"' EXIT
16
17 SPEC = " $TMPDIR_WORK /spec.yml"
18 cat > " $SPEC "
19
20 node - " $ENDPOINT " " $METHOD " " $SPEC " << 'SCRIPT'
21 const fs = require("fs");
22 const endpoint = process.argv[2];
23 const method = process.argv[3].toLowerCase();
24 const specFile = process.argv[4];
25 const lines = fs.readFileSync(specFile, "utf8").split("\n");
26
27 const httpMethods = ["get", "post", "put", "patch", "delete", "options", "head"];
28
29 // Locate paths: and components: sections
30 let pathsStart = -1, pathsEnd = -1, componentsStart = -1;
31 for (let i = 0; i < lines.length; i++) {
32 if (/^paths:\s*$/.test(lines[i])) pathsStart = i;
33 else if (pathsStart >= 0 && pathsEnd < 0 && /^\S/.test(lines[i]) && i > pathsStart) pathsEnd = i;
34 if (/^components:\s*$/.test(lines[i])) componentsStart = i;
35 }
36 if (pathsEnd < 0) pathsEnd = lines.length;
37
38 // Find the target path + method block
39 let targetStart = -1, targetEnd = -1;
40 let currentPath = null;
41
42 for (let i = pathsStart + 1; i < pathsEnd; i++) {
43 const line = lines[i];
44
45 // Path line: exactly 2 spaces + /
46 if (/^ {2}\/\S/.test(line)) {
47 currentPath = line.trim().replace(/:$/, "");
48 continue;
49 }
50
51 // Method line: exactly 4 spaces + method name
52 const methodMatch = line.match(/^ {4}(\w+):\s*$/);
53 if (methodMatch && httpMethods.includes(methodMatch[1])) {
54 if (currentPath === endpoint && methodMatch[1] === method) {
55 targetStart = i;
56 // Find end of this method block
57 for (let j = i + 1; j < pathsEnd; j++) {
58 const nextLine = lines[j];
59 // New method or new path
60 if (/^ {2}\/\S/.test(nextLine) || (/^ {4}\w+:\s*$/.test(nextLine) && httpMethods.some(m => nextLine.trim().startsWith(m + ":")))) {
61 targetEnd = j;
62 break;
63 }
64 }
65 if (targetEnd < 0) targetEnd = pathsEnd;
66 break;
67 }
68 }
69 }
70
71 if (targetStart < 0) {
72 console.error(`Endpoint not found: ${method.toUpperCase()} ${endpoint}`);
73 process.exit(1);
74 }
75
76 const blockLines = lines.slice(targetStart, targetEnd);
77
78 // Collect all $refs from the block
79 const allRefs = new Set();
80 for (const bl of blockLines) {
81 const refMatch = bl.match(/\$ref:\s*['"]?(#\/[^'"}\s]+)['"]?/);
82 if (refMatch) allRefs.add(refMatch[1]);
83 }
84
85 // Resolve a $ref path to the raw YAML lines for that component
86 function resolveRef(ref) {
87 const parts = ref.replace("#/", "").split("/");
88 // Find the component in the file by walking indentation
89 let searchStart = 0;
90 for (let p = 0; p < parts.length; p++) {
91 const indent = p * 2;
92 const target = " ".repeat(indent) + parts[p] + ":";
93 let found = false;
94 for (let i = searchStart; i < lines.length; i++) {
95 if (lines[i].startsWith(target) && (lines[i] === target || lines[i][target.length] === " ")) {
96 searchStart = i + 1;
97 found = true;
98 break;
99 }
100 }
101 if (!found) return null;
102 }
103
104 // Collect lines for this component (until same or lower indent)
105 const componentStart = searchStart - 1;
106 const baseIndent = parts.length * 2;
107 const result = [];
108 for (let i = searchStart; i < lines.length; i++) {
109 const line = lines[i];
110 if (line.trim() === "") { result.push(line); continue; }
111 const lineIndent = line.length - line.trimStart().length;
112 if (lineIndent < baseIndent) break;
113 result.push(line);
114 }
115 return result;
116 }
117
118 // Recursively resolve refs from component bodies
119 function collectDeepRefs(refSet, visited) {
120 const toProcess = [...refSet].filter(r => !visited.has(r));
121 for (const ref of toProcess) {
122 visited.add(ref);
123 const body = resolveRef(ref);
124 if (!body) continue;
125 for (const bl of body) {
126 const refMatch = bl.match(/\$ref:\s*['"]?(#\/[^'"}\s]+)['"]?/);
127 if (refMatch && !visited.has(refMatch[1])) {
128 refSet.add(refMatch[1]);
129 }
130 }
131 }
132 // Recurse if new refs were found
133 const newRefs = [...refSet].filter(r => !visited.has(r));
134 if (newRefs.length > 0) collectDeepRefs(refSet, visited);
135 }
136
137 collectDeepRefs(allRefs, new Set());
138
139 // Output
140 console.log(`## \`${method.toUpperCase()}\` \`${endpoint}\`\n`);
141 console.log("### Endpoint Definition\n");
142 console.log("```yaml");
143 for (const bl of blockLines) {
144 console.log(bl);
145 }
146 console.log("```\n");
147
148 if (allRefs.size > 0) {
149 console.log(`### Referenced Components (${allRefs.size})\n`);
150 const sorted = [...allRefs].sort();
151 for (const ref of sorted) {
152 const name = ref.split("/").pop();
153 const category = ref.replace("#/", "").split("/").slice(0, -1).join("/");
154 console.log(`#### \`${name}\` (${category})\n`);
155 const body = resolveRef(ref);
156 if (body) {
157 console.log("```yaml");
158 for (const bl of body) console.log(bl);
159 console.log("```\n");
160 } else {
161 console.log("_(could not resolve)_\n");
162 }
163 }
164 }
165 SCRIPT