Skip to content

Commit 435fede

Browse files
committed
Merge branch fix/build-dependencies
2 parents b565078 + 579645a commit 435fede

27 files changed

+2078
-2592
lines changed

app/api/generate-schema-doc/route.ts

Lines changed: 116 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import fs from "fs/promises";
55
import path from "path";
66
import os from "os";
77
import { createRequire } from "module"; // Import createRequire
8+
import prettier from "prettier";
9+
import parserTypescript from "prettier/parser-typescript";
10+
// import { DocGenerator } from "json-schema-static-docs"; // Removed
811

912
// Helper to resolve package paths
10-
const require = createRequire(import.meta.url);
13+
// const require = createRequire(import.meta.url);
1114

1215
// Recursive copy function (fs.cp might not be available everywhere or handle nested dirs reliably)
1316
async function copyDirRecursive(src: string, dest: string) {
1417
await fs.mkdir(dest, { recursive: true });
1518
const entries = await fs.readdir(src, { withFileTypes: true });
16-
for (let entry of entries) {
19+
for (const entry of entries) {
1720
const srcPath = path.join(src, entry.name);
1821
const destPath = path.join(dest, entry.name);
1922
if (entry.isDirectory()) {
@@ -25,7 +28,7 @@ async function copyDirRecursive(src: string, dest: string) {
2528
}
2629

2730
// Helper to create a temporary file
28-
async function writeTempSchemaFile(schema: any): Promise<string> {
31+
async function writeTempSchemaFile(schema: Record<string, unknown>): Promise<string> {
2932
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "schema-"));
3033
const tempFilePath = path.join(tempDir, "schema.json");
3134
await fs.writeFile(tempFilePath, JSON.stringify(schema, null, 2));
@@ -69,14 +72,73 @@ async function cleanupTempDirs(pathsToClean: string[]) {
6972
}
7073
}
7174

75+
// Define an interface for the expected structure of the imported library module if possible
76+
// If the exact structure is unknown, we might have to keep `any` casts but minimize them.
77+
interface JsonSchemaStaticDocsOptions {
78+
inputPath: string;
79+
outputPath: string;
80+
templatePath: string;
81+
createIndex: boolean;
82+
addFrontMatter: boolean;
83+
// Replace 'any' with 'unknown' for stricter checking
84+
[key: string]: unknown;
85+
}
86+
87+
interface JsonSchemaStaticDocsInstance {
88+
generate: () => Promise<void>;
89+
// Add other known methods/properties if available
90+
[key: string]: unknown;
91+
}
92+
93+
// Type for the constructor, assuming it's the default export or the module itself
94+
type JsonSchemaStaticDocsConstructor = new (
95+
options: JsonSchemaStaticDocsOptions,
96+
) => JsonSchemaStaticDocsInstance;
97+
98+
// Type for the dynamically imported module
99+
type JsonSchemaStaticDocsModule =
100+
| JsonSchemaStaticDocsConstructor // Case: Module is the constructor
101+
| { default: JsonSchemaStaticDocsConstructor } // Case: Constructor is default export
102+
| { JsonSchemaStaticDocs: JsonSchemaStaticDocsConstructor } // Case: Constructor is named export 'JsonSchemaStaticDocs'
103+
| { DocGenerator: JsonSchemaStaticDocsConstructor } // Case: Constructor is named export 'DocGenerator'
104+
| { [key: string]: unknown }; // Fallback for other structures
105+
106+
// Custom Type Guard for the Constructor
107+
function isDocConstructor(obj: unknown): obj is JsonSchemaStaticDocsConstructor {
108+
return typeof obj === 'function' && obj.prototype !== undefined;
109+
}
110+
111+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
112+
async function formatMarkdown(markdown: string): Promise<string> {
113+
try {
114+
const result: string = await prettier.format(markdown, {
115+
parser: "markdown",
116+
plugins: [parserTypescript],
117+
printWidth: 80,
118+
proseWrap: "always",
119+
});
120+
return result;
121+
} catch (error: unknown) { // Type error as unknown
122+
console.warn("Could not parse error response from generate-schema-doc");
123+
// Safe access to properties
124+
let statusText = "Unknown status";
125+
if (typeof error === 'object' && error !== null && 'statusText' in error) {
126+
statusText = String((error as { statusText: unknown }).statusText);
127+
}
128+
const errorDetails = `: ${statusText}`; // Use const as errorDetails is not reassigned after this block
129+
const message = error instanceof Error ? error.message : String(error);
130+
throw new Error(`Markdown formatting failed${errorDetails}. Original error: ${message}`);
131+
}
132+
}
133+
72134
export async function POST(request: Request) {
73135
let tempInputPath: string | undefined;
74136
let tempOutputPath: string | undefined;
75137
let tempTemplatePath: string | undefined; // Path for copied templates
76138
const cleanupPaths: string[] = []; // Keep track of all paths to clean
77139

78140
try {
79-
const { schema } = await request.json();
141+
const { schema } = (await request.json()) as { schema: Record<string, unknown> }; // Added type assertion
80142

81143
if (!schema) {
82144
return NextResponse.json(
@@ -130,25 +192,59 @@ export async function POST(request: Request) {
130192
console.log(`API: Finished copying templates.`);
131193

132194
// 4. Instantiate and run json-schema-static-docs
133-
const Constructor =
134-
JsonSchemaStaticDocsLib.default || JsonSchemaStaticDocsLib;
195+
// Attempt to find the constructor more safely
196+
const LibraryModule: JsonSchemaStaticDocsModule = JsonSchemaStaticDocsLib;
197+
let Constructor: JsonSchemaStaticDocsConstructor | null = null;
198+
199+
// Use the type guard first for the direct constructor case
200+
if (isDocConstructor(LibraryModule)) {
201+
Constructor = LibraryModule;
202+
// Check if it's an object before checking properties
203+
} else if (LibraryModule && typeof LibraryModule === 'object') {
204+
// Use 'in' operator for safer property checking
205+
if ('default' in LibraryModule && LibraryModule.default && isDocConstructor(LibraryModule.default)) {
206+
// Add type assertion
207+
Constructor = LibraryModule.default as JsonSchemaStaticDocsConstructor;
208+
} else if ('JsonSchemaStaticDocs' in LibraryModule && LibraryModule.JsonSchemaStaticDocs && isDocConstructor(LibraryModule.JsonSchemaStaticDocs)) {
209+
// Add type assertion
210+
Constructor = LibraryModule.JsonSchemaStaticDocs as JsonSchemaStaticDocsConstructor;
211+
} else if ('DocGenerator' in LibraryModule && LibraryModule.DocGenerator && isDocConstructor(LibraryModule.DocGenerator)) {
212+
// Add type assertion
213+
Constructor = LibraryModule.DocGenerator as JsonSchemaStaticDocsConstructor;
214+
}
215+
}
135216

136-
const generator = new Constructor({
217+
if (!Constructor) {
218+
console.error("API: Could not find JsonSchemaStaticDocs constructor in the imported module.", LibraryModule);
219+
throw new Error("Failed to load the documentation generator library correctly.");
220+
}
221+
222+
// Instantiate using the found constructor
223+
// Cast options object at point of use due to [key: string]: unknown
224+
const options: JsonSchemaStaticDocsOptions = {
137225
inputPath: tempInputDirectory,
138226
outputPath: tempOutputPath,
139-
templatePath: tempTemplatePath, // <-- Use the path with copied templates
140-
createIndex: false, // We only want the single doc
141-
addFrontMatter: false, // No frontmatter needed
142-
// Add any other options needed for styling/generation
143-
// ajvOptions: { allowUnionTypes: true }, // Example ajv option
144-
// enableMetaEnum: true, // Example meta enum option
145-
});
227+
templatePath: tempTemplatePath,
228+
createIndex: false,
229+
addFrontMatter: false,
230+
};
231+
const generator: JsonSchemaStaticDocsInstance = new Constructor(options);
146232

147233
console.log(
148234
`API: Running generator. Input: ${tempInputDirectory}, Output: ${tempOutputPath}, Templates: ${tempTemplatePath}`,
149235
);
150-
await generator.generate();
151-
console.log("API: Generator finished.");
236+
// Wrap generate call in try-catch
237+
try {
238+
// Call generate method (type safety from interface)
239+
await generator.generate();
240+
console.log("API: Generator finished.");
241+
} catch (genError: unknown) { // Ensure genError is unknown
242+
console.error("API: Error during generator.generate():", genError);
243+
// Use type guard for message
244+
const message = genError instanceof Error ? genError.message : "Generator failed";
245+
246+
throw new Error(`Schema documentation generation failed: ${message}`);
247+
}
152248

153249
// 5. Read the generated Markdown file
154250
const markdown = await readGeneratedMarkdown(
@@ -161,16 +257,18 @@ export async function POST(request: Request) {
161257

162258
// 7. Return the Markdown content
163259
return NextResponse.json({ markdown });
164-
} catch (error: any) {
260+
} catch (error: unknown) { // Changed to unknown
165261
console.error("API Error generating schema doc:", error);
166262

167263
// Ensure cleanup happens even on error
168264
await cleanupTempDirs(cleanupPaths);
169265

266+
// Add type guard before accessing message
267+
const details = error instanceof Error ? error.message : "Unknown error";
170268
return NextResponse.json(
171269
{
172270
error: "Failed to generate schema documentation",
173-
details: error.message,
271+
details: details, // Use guarded details
174272
},
175273
{ status: 500 },
176274
);

app/api/schemas/[name]/route.ts

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import { NextResponse } from "next/server";
22
import fs from "fs/promises";
33
import path from "path";
4+
// Correct the import for the 'jsonc' library version 2.0.0
5+
import { jsonc } from "jsonc";
6+
7+
// Remove the explicit RouteContext interface
8+
// interface RouteContext {
9+
// params: {
10+
// name: string;
11+
// };
12+
// }
413

514
export async function GET(
6-
request: Request,
7-
{ params }: { params: Promise<{ name: string }> },
15+
_request: Request, // Prefix with _ if not used
16+
// Use the correct Next.js 15 type signature with Promise
17+
{ params }: { params: Promise<{ name: string }> },
818
) {
19+
// Remove the explicit destructuring added in the previous step
20+
// const { params } = context;
921
console.log("[API /api/schemas/[name]] Waiting for params...");
1022

11-
// Await the params promise
23+
// Ensure params is awaited (was likely correct before, but confirming)
1224
const awaitedParams = await params;
1325
console.log(
1426
"[API /api/schemas/[name]] Incoming params resolved:",
@@ -52,30 +64,60 @@ export async function GET(
5264

5365
const fileContent = await fs.readFile(filePath, "utf-8");
5466

55-
// Optionally validate if it's valid JSON before returning
67+
// Parse JSONC (JSON with comments) for validation
5668
try {
57-
JSON.parse(fileContent);
58-
} catch (parseError) {
59-
console.error(
60-
`[API /api/schemas/${name}] File is not valid JSON: ${filePath}`,
61-
);
69+
// Use the imported jsonc object directly
70+
if (typeof jsonc.parse === 'function') {
71+
// Call parse via the imported jsonc object
72+
jsonc.parse(fileContent);
73+
} else {
74+
// Handle the case where jsonc or jsonc.parse is not loaded correctly
75+
console.error(`[API /api/schemas/${name}] jsonc.parse function not found.`);
76+
throw new Error("JSONC parsing library failed to load.");
77+
}
78+
} catch (parseError: unknown) { // Catch specific parsing error
79+
// Handle JSON parsing errors
80+
console.error(`Error parsing schema file ${name}:`, parseError);
81+
// Provide a more specific error message
82+
// const message = parseError instanceof Error ? parseError.message : "Invalid JSONC format"; // Removed unused variable
83+
// Check if parseError has a relevant property like 'message' before using it
84+
let details = "Invalid JSONC format";
85+
if (parseError instanceof Error) {
86+
details = parseError.message;
87+
} else if (typeof parseError === 'object' && parseError !== null && 'message' in parseError) {
88+
// Handle cases where it might be an error-like object but not an Error instance
89+
details = String((parseError as { message: unknown }).message);
90+
}
6291
return NextResponse.json(
63-
{ error: "Schema file is not valid JSON" },
92+
{ error: "Schema file is not valid JSON", details: details }, // Use checked details
6493
{ status: 500 },
6594
);
6695
}
67-
96+
// Return the original content if parsing succeeded
6897
return NextResponse.json({ content: fileContent });
69-
} catch (error: any) {
98+
} catch (error: unknown) {
7099
console.error(
71100
`[API /api/schemas/${name}] Error reading schema file:`,
72101
error,
73102
);
74-
if (error.code === "ENOENT") {
103+
// Add type guards for accessing properties
104+
let errorCode: string | undefined;
105+
let errorMessage: string | undefined = "Unknown error reading file";
106+
107+
if (typeof error === 'object' && error !== null) {
108+
if ('code' in error) {
109+
errorCode = String(error.code); // Convert potential non-string code
110+
}
111+
if (error instanceof Error) {
112+
errorMessage = error.message;
113+
}
114+
}
115+
116+
if (errorCode === "ENOENT") {
75117
return NextResponse.json({ error: "Schema not found" }, { status: 404 });
76118
}
77119
return NextResponse.json(
78-
{ error: "Failed to read schema file", details: error.message },
120+
{ error: "Failed to read schema file", details: errorMessage }, // Use guarded message
79121
{ status: 500 },
80122
);
81123
}

app/api/schemas/route.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
import { NextResponse } from "next/server";
2-
import fs from "fs";
2+
import fs from "fs/promises";
33
import path from "path";
44

55
export async function GET() {
66
try {
77
const schemasDir = path.join(process.cwd(), "schemas", "v1");
8-
const files = fs.readdirSync(schemasDir).filter((f) => f.endsWith(".json"));
9-
const schemas = files.map((filename) => ({
8+
console.log(`[API /api/schemas] Reading directory: ${schemasDir}`);
9+
10+
const allFiles = await fs.readdir(schemasDir);
11+
console.log(`[API /api/schemas] Found files: ${allFiles.join(', ')}`);
12+
13+
const jsonFiles = allFiles.filter((f) => typeof f === 'string' && f.endsWith(".json"));
14+
console.log(`[API /api/schemas] Filtered JSON files: ${jsonFiles.join(', ')}`);
15+
16+
const schemas = jsonFiles.map((filename) => ({
1017
name: filename.replace(/\.json$/, ""),
1118
filename,
12-
path: `/schemas/v1/${filename}`,
1319
}));
14-
return NextResponse.json({ schemas });
15-
} catch (error) {
20+
21+
console.log(`[API /api/schemas] Mapped schemas:`, schemas);
22+
23+
return NextResponse.json({ schemas: schemas });
24+
} catch (error: unknown) {
25+
console.error("[API /api/schemas] Error:", error);
1626
const message = error instanceof Error ? error.message : String(error);
17-
return NextResponse.json({ error: message }, { status: 500 });
27+
return NextResponse.json(
28+
{ error: "Failed to retrieve schemas", details: message },
29+
{ status: 500 },
30+
);
1831
}
1932
}

0 commit comments

Comments
 (0)