修改未完成

This commit is contained in:
rain
2026-07-29 18:15:59 +08:00
parent 6fbcf21024
commit 8c2355a52c
59 changed files with 7995 additions and 1118 deletions
+104
View File
@@ -0,0 +1,104 @@
"use strict";
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const YAML = require("yaml");
const walk = (directory) => fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) =>
entry.isDirectory() ? walk(path.join(directory, entry.name)) : [path.join(directory, entry.name)],
);
const document = YAML.parse(fs.readFileSync("genealogy-app-openapi.yaml", "utf8"));
assert.strictEqual(document.openapi, "3.0.3", "backend contract must be OpenAPI 3.0.3");
const operations = new Set(
Object.entries(document.paths).flatMap(([endpoint, pathItem]) =>
Object.keys(pathItem)
.filter((method) => ["get", "post", "put", "delete", "patch"].includes(method))
.map((method) => `${method.toUpperCase()} ${endpoint}`),
),
);
const apiSource = fs.readFileSync("utils/api.js", "utf8");
const appApiStart = apiSource.indexOf("export const appApi = {");
assert.ok(appApiStart >= 0, "appApi owner is missing");
const appApiSource = apiSource.slice(appApiStart);
const methodMatches = [...appApiSource.matchAll(/^ async ([A-Za-z0-9_]+)\(/gm)];
const methodSources = new Map(
methodMatches.map((match, index) => [
match[1],
appApiSource.slice(match.index, methodMatches[index + 1]?.index),
]),
);
const normalizeEndpoint = (endpoint) => endpoint
.replace(/\$\{[^}]+\}/g, "{parameter}")
.replace(/\{[^/]+\}/g, "{parameter}");
const resolveOpenApiEndpoint = (endpoint) => Object.keys(document.paths).find(
(candidate) => normalizeEndpoint(candidate) === normalizeEndpoint(endpoint),
);
const collectLiteralOperations = (source) => {
const entries = [];
const add = (method, endpoint) => {
if (endpoint.includes("${relationPath}")) {
for (const relationPath of ["parents", "spouses", "siblings", "children"]) {
entries.push({ method, endpoint: endpoint.replace("${relationPath}", relationPath) });
}
return;
}
entries.push({ method, endpoint });
};
const literal = "([`'])";
const endpoint = "(/genealogy[^`']*)";
const readPattern = new RegExp(`readRemote(?:List|Object)\\(\\s*${literal}${endpoint}\\1`, "g");
for (const match of source.matchAll(readPattern)) add("GET", match[2]);
const writePattern = new RegExp(`writeRemote(?:Void|Object)\\(\\s*${literal}${endpoint}\\1\\s*,\\s*['\"](POST|PUT|DELETE|PATCH)['\"]`, "g");
for (const match of source.matchAll(writePattern)) add(match[3], match[2]);
const directPattern = new RegExp(`url:\\s*${literal}${endpoint}\\1\\s*,\\s*method:\\s*['\"](GET|POST|PUT|DELETE|PATCH)['\"]`, "g");
for (const match of source.matchAll(directPattern)) add(match[3], match[2]);
const conditionalMethodPattern = new RegExp(`url:\\s*${literal}${endpoint}\\1\\s*,\\s*method:\\s*[^?]+\\?\\s*['\"](POST|PUT|DELETE|PATCH)['\"]\\s*:\\s*['\"](POST|PUT|DELETE|PATCH)['\"]`, "g");
for (const match of source.matchAll(conditionalMethodPattern)) {
add(match[3], match[2]);
add(match[4], match[2]);
}
return entries;
};
const pageCalls = walk("pages")
.filter((file) => file.endsWith(".vue"))
.flatMap((file) => {
const source = fs.readFileSync(file, "utf8");
return [...source.matchAll(/appApi\.([A-Za-z0-9_]+)/g)].map((match) => ({
file: file.replace(/\\/g, "/"),
method: match[1],
}));
});
const routedPages = new Set(
JSON.parse(fs.readFileSync("pages.json", "utf8")).pages.map((page) => `${page.path}.vue`),
);
const unresolvedMethods = [];
const unmappedOperations = [];
for (const method of [...new Set(pageCalls.map((call) => call.method))]) {
const source = methodSources.get(method);
if (!source) {
unresolvedMethods.push(method);
continue;
}
const references = collectLiteralOperations(source);
if (!references.length) {
unresolvedMethods.push(method);
continue;
}
for (const reference of references) {
const endpoint = resolveOpenApiEndpoint(reference.endpoint);
if (!endpoint || !operations.has(`${reference.method} ${endpoint}`)) {
unmappedOperations.push(`${method}: ${reference.method} ${reference.endpoint}`);
}
}
}
assert.deepStrictEqual(unresolvedMethods, [], "page call has no inspectable adapter endpoint owner");
assert.deepStrictEqual(unmappedOperations, [], "page adapter endpoint diverges from current backend OpenAPI");
process.stdout.write(`PAGE-OPENAPI-CONTRACT PASS routed=${routedPages.size} apiPages=${new Set(pageCalls.map((call) => call.file)).size} calls=${pageCalls.length}\n`);