feat: add Go migration compatibility foundation
This commit is contained in:
1 parent
7de3300034
commit
716a8031b1
27 files changed
+2582
No files matched your search
@@ -0,0 +1,74 @@
|
||||
import { readFile, readdir } from "node:fs/promises";
|
||||
import { dirname, join, relative, sep } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
type RouteContract = {
|
||||
version: 1;
|
||||
routes: Array<{
|
||||
method: "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
|
||||
path: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
const repositoryRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
const appRoot = join(repositoryRoot, "app");
|
||||
const contractPath = join(repositoryRoot, "contracts", "http", "route-surface.v1.json");
|
||||
const exportedMethod = /^\s*export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\b/gm;
|
||||
|
||||
async function findRouteFiles(directory: string): Promise<string[]> {
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
const nested = await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const path = join(directory, entry.name);
|
||||
if (entry.isDirectory()) return findRouteFiles(path);
|
||||
return entry.isFile() && entry.name === "route.ts" ? [path] : [];
|
||||
}),
|
||||
);
|
||||
return nested.flat().sort();
|
||||
}
|
||||
|
||||
function routePath(routeFile: string): string {
|
||||
const routeDirectory = relative(appRoot, dirname(routeFile));
|
||||
const segments = routeDirectory.split(sep).map((segment) => {
|
||||
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
||||
return `{${segment.slice(4, -1)}...}`;
|
||||
}
|
||||
if (segment.startsWith("[") && segment.endsWith("]")) {
|
||||
return `{${segment.slice(1, -1)}}`;
|
||||
}
|
||||
return segment;
|
||||
});
|
||||
return `/${segments.join("/")}`;
|
||||
}
|
||||
|
||||
async function deriveRouteSurface(routeFiles: string[]): Promise<RouteContract["routes"]> {
|
||||
const routes = await Promise.all(
|
||||
routeFiles.map(async (routeFile) => {
|
||||
const source = await readFile(routeFile, "utf8");
|
||||
return Array.from(source.matchAll(exportedMethod), (match) => ({
|
||||
method: match[1] as RouteContract["routes"][number]["method"],
|
||||
path: routePath(routeFile),
|
||||
}));
|
||||
}),
|
||||
);
|
||||
return routes.flat().sort((left, right) => {
|
||||
const leftKey = `${left.path}\u0000${left.method}`;
|
||||
const rightKey = `${right.path}\u0000${right.method}`;
|
||||
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
||||
});
|
||||
}
|
||||
|
||||
describe("HTTP route surface contract", () => {
|
||||
it("exactly records every exported Next route method without loading handlers", async () => {
|
||||
const routeFiles = await findRouteFiles(appRoot);
|
||||
const actual = await deriveRouteSurface(routeFiles);
|
||||
const contract = JSON.parse(await readFile(contractPath, "utf8")) as RouteContract;
|
||||
|
||||
expect(routeFiles).toHaveLength(47);
|
||||
expect(actual).toHaveLength(66);
|
||||
expect(contract.version).toBe(1);
|
||||
expect(contract.routes).toEqual(actual);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user