@cmflow/atlas 3.4.0-beta.21 → 3.4.0-beta.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/atlas.mjs +539 -482
- package/dist/bin/atlas.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{propertyExtractionService-8BjqpPFa.mjs → propertyExtractionService-BLat-Hsf.mjs} +45 -33
- package/dist/propertyExtractionService-BLat-Hsf.mjs.map +1 -0
- package/dist/{routeBackendTopologyService-CkOLUfcj.mjs → routeBackendTopologyService-CQyRyoSN.mjs} +52 -20
- package/dist/routeBackendTopologyService-CQyRyoSN.mjs.map +1 -0
- package/dist/rules/lodashGetRule.d.mts +1 -1
- package/dist/rules/mappingUtilityRule.d.mts +1 -1
- package/dist/rules/memberGetFieldRule.d.mts +1 -1
- package/dist/{types-Af0_VOnh.d.mts → types-D5fngFrb.d.mts} +3 -1
- package/dist/workers/routeBackendTopologyWorker.mjs +1 -1
- package/package.json +1 -1
- package/dist/propertyExtractionService-8BjqpPFa.mjs.map +0 -1
- package/dist/routeBackendTopologyService-CkOLUfcj.mjs.map +0 -1
package/dist/bin/atlas.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import { fileURLToPath as __atlasFileURLToPath } from "node:url";
|
|
|
3
3
|
const __filename = __atlasFileURLToPath(import.meta.url);
|
|
4
4
|
import { n as __require, r as __toESM, t as __commonJSMin } from "../rolldown-runtime-CGR6nZuH.mjs";
|
|
5
5
|
import { a as note, c as select, d as setUserConfig, f as userConfig$1, i as log, l as isCancel, n as cancel, o as outro, r as intro, s as progress, t as taskProgressService, u as getUserConfig } from "../taskProgressService-CAC_RIoa.mjs";
|
|
6
|
-
import { a as
|
|
7
|
-
import { i as loadOpenApiDocument, n as extractOpenApiInputProperties, r as extractOpenApiOutputProperties } from "../propertyExtractionService-
|
|
6
|
+
import { a as inferBackendNameFromFile, c as shouldKeepAnalysisFile, i as resolveModulePath, l as normalizeFilePath, n as generateBackendTopologyArtifactsInWorkers, o as isKnownBackendType, r as matchesRouteSelector, s as filterAnalysisFiles, u as globby } from "../routeBackendTopologyService-CQyRyoSN.mjs";
|
|
7
|
+
import { i as loadOpenApiDocument, n as extractOpenApiInputProperties, r as extractOpenApiOutputProperties } from "../propertyExtractionService-BLat-Hsf.mjs";
|
|
8
8
|
import { n as require_auth_errors, r as require_token_error, t as require_token_util } from "../token-util-Dnzm6rU4.mjs";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
@@ -2888,6 +2888,233 @@ const r = (r = {}) => (i) => {
|
|
|
2888
2888
|
} };
|
|
2889
2889
|
};
|
|
2890
2890
|
|
|
2891
|
+
//#endregion
|
|
2892
|
+
//#region src/utils/isRecord.ts
|
|
2893
|
+
function isRecord$1(value) {
|
|
2894
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
//#endregion
|
|
2898
|
+
//#region src/services/directus/utils/isServiceUnavailableError.ts
|
|
2899
|
+
function isServiceUnavailableError(error) {
|
|
2900
|
+
if (!isRecord$1(error)) return false;
|
|
2901
|
+
if ((isRecord$1(error.response) ? error.response.status : void 0) === 503) return true;
|
|
2902
|
+
return Array.isArray(error.errors) && error.errors.some((item) => isRecord$1(item) && isRecord$1(item.extensions) && item.extensions.code === "SERVICE_UNAVAILABLE");
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
//#endregion
|
|
2906
|
+
//#region src/services/directus/utils/isTransientNetworkError.ts
|
|
2907
|
+
function isTransientNetworkError(error) {
|
|
2908
|
+
if (error instanceof TypeError && error.message === "fetch failed") return true;
|
|
2909
|
+
if (!isRecord$1(error)) return false;
|
|
2910
|
+
if (error.message === "fetch failed") return true;
|
|
2911
|
+
const cause = isRecord$1(error.cause) ? error.cause : void 0;
|
|
2912
|
+
return cause?.code === "ECONNRESET" || cause?.code === "ECONNREFUSED" || cause?.code === "ETIMEDOUT" || cause?.code === "UND_ERR_CONNECT_TIMEOUT";
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
//#endregion
|
|
2916
|
+
//#region src/services/directus/utils/retryDirectusRequest.ts
|
|
2917
|
+
const DIRECTUS_RETRY_MAX_ATTEMPTS = 6;
|
|
2918
|
+
const DIRECTUS_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
2919
|
+
const DIRECTUS_RETRY_MAX_DELAY_MS = 8e3;
|
|
2920
|
+
async function retryDirectusRequest(request, options = {}) {
|
|
2921
|
+
const maxAttempts = options.maxAttempts ?? DIRECTUS_RETRY_MAX_ATTEMPTS;
|
|
2922
|
+
const initialDelayMs = options.initialDelayMs ?? DIRECTUS_RETRY_INITIAL_DELAY_MS;
|
|
2923
|
+
const maxDelayMs = options.maxDelayMs ?? DIRECTUS_RETRY_MAX_DELAY_MS;
|
|
2924
|
+
const sleep = options.sleep || ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
2925
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) try {
|
|
2926
|
+
return await request();
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
if (!isServiceUnavailableError(error) && !isTransientNetworkError(error) || attempt === maxAttempts) throw error;
|
|
2929
|
+
const delayMs = Math.min(initialDelayMs * 2 ** (attempt - 1), maxDelayMs);
|
|
2930
|
+
taskProgressService.report(`Directus request temporarily unavailable; retrying ${attempt + 1}/${maxAttempts} in ${(delayMs / 1e3).toFixed(1)}s`);
|
|
2931
|
+
await sleep(delayMs);
|
|
2932
|
+
}
|
|
2933
|
+
throw new Error("Directus retry attempts exhausted");
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
//#endregion
|
|
2937
|
+
//#region src/services/directus/createClient.ts
|
|
2938
|
+
async function createClient() {
|
|
2939
|
+
const baseUrl = getUserConfig().directusUrl;
|
|
2940
|
+
const token = getUserConfig().directusToken;
|
|
2941
|
+
if (!token) throw new Error("Directus push requested but Directus token is missing");
|
|
2942
|
+
const client = t$1(baseUrl).with(r()).with(e$3(token));
|
|
2943
|
+
return { request: (command) => retryDirectusRequest(() => client.request(command)) };
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
//#endregion
|
|
2947
|
+
//#region src/services/directus/queries/deleteOrphanedPropertyLinks.ts
|
|
2948
|
+
const DIRECTUS_DELETE_WORKERS = 2;
|
|
2949
|
+
async function deleteOrphanedPropertyLinks(client, orphanedLinks) {
|
|
2950
|
+
const links = [...orphanedLinks.input.map((link) => ({
|
|
2951
|
+
collection: "route_input_properties_backend_properties",
|
|
2952
|
+
id: link.id
|
|
2953
|
+
})), ...orphanedLinks.output.map((link) => ({
|
|
2954
|
+
collection: "route_output_properties_backend_properties",
|
|
2955
|
+
id: link.id
|
|
2956
|
+
}))];
|
|
2957
|
+
let nextIndex = 0;
|
|
2958
|
+
await Promise.all(Array.from({ length: Math.min(DIRECTUS_DELETE_WORKERS, links.length) }, async () => {
|
|
2959
|
+
while (nextIndex < links.length) {
|
|
2960
|
+
const link = links[nextIndex++];
|
|
2961
|
+
await client.request(r$1(link.collection, link.id));
|
|
2962
|
+
}
|
|
2963
|
+
}));
|
|
2964
|
+
return links.length;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
//#endregion
|
|
2968
|
+
//#region src/services/directus/queries/findOrphanedPropertyLinks.ts
|
|
2969
|
+
async function findOrphanedPropertyLinks(client) {
|
|
2970
|
+
const [inputProperties, outputProperties, backendProperties, inputLinks, outputLinks] = await Promise.all([
|
|
2971
|
+
client.request(n$1("route_input_properties", {
|
|
2972
|
+
fields: ["id"],
|
|
2973
|
+
limit: -1
|
|
2974
|
+
})),
|
|
2975
|
+
client.request(n$1("route_output_properties", {
|
|
2976
|
+
fields: ["id"],
|
|
2977
|
+
limit: -1
|
|
2978
|
+
})),
|
|
2979
|
+
client.request(n$1("backend_properties", {
|
|
2980
|
+
fields: ["id"],
|
|
2981
|
+
limit: -1
|
|
2982
|
+
})),
|
|
2983
|
+
client.request(n$1("route_input_properties_backend_properties", {
|
|
2984
|
+
fields: [
|
|
2985
|
+
"id",
|
|
2986
|
+
"route_input_properties_id",
|
|
2987
|
+
"backend_properties_id"
|
|
2988
|
+
],
|
|
2989
|
+
limit: -1
|
|
2990
|
+
})),
|
|
2991
|
+
client.request(n$1("route_output_properties_backend_properties", {
|
|
2992
|
+
fields: [
|
|
2993
|
+
"id",
|
|
2994
|
+
"route_output_properties_id",
|
|
2995
|
+
"backend_properties_id"
|
|
2996
|
+
],
|
|
2997
|
+
limit: -1
|
|
2998
|
+
}))
|
|
2999
|
+
]);
|
|
3000
|
+
const ids = (items) => new Set(items.map((item) => String(item.id)));
|
|
3001
|
+
const inputIds = ids(inputProperties);
|
|
3002
|
+
const outputIds = ids(outputProperties);
|
|
3003
|
+
const backendIds = ids(backendProperties);
|
|
3004
|
+
return {
|
|
3005
|
+
input: inputLinks.filter((link) => !inputIds.has(String(link.route_input_properties_id)) || !backendIds.has(String(link.backend_properties_id))),
|
|
3006
|
+
output: outputLinks.filter((link) => !outputIds.has(String(link.route_output_properties_id)) || !backendIds.has(String(link.backend_properties_id)))
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
//#endregion
|
|
3011
|
+
//#region src/services/directus/cleanDirectusOrphanedPropertyLinks.ts
|
|
3012
|
+
async function cleanDirectusOrphanedPropertyLinks(options) {
|
|
3013
|
+
taskProgressService.report("Connecting to Directus");
|
|
3014
|
+
const client = await createClient();
|
|
3015
|
+
taskProgressService.report("Finding orphaned property links");
|
|
3016
|
+
const orphanedLinks = await findOrphanedPropertyLinks(client);
|
|
3017
|
+
const orphanedCount = orphanedLinks.input.length + orphanedLinks.output.length;
|
|
3018
|
+
if (options.dryRun) {
|
|
3019
|
+
taskProgressService.report(`${orphanedCount} orphaned property links found (dry-run)`);
|
|
3020
|
+
return {
|
|
3021
|
+
orphanedLinks: orphanedCount,
|
|
3022
|
+
removedOrphanedLinks: 0
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
taskProgressService.report("Removing orphaned property links");
|
|
3026
|
+
const removedOrphanedLinks = await deleteOrphanedPropertyLinks(client, orphanedLinks);
|
|
3027
|
+
taskProgressService.report(`Removed ${removedOrphanedLinks} orphaned property links`);
|
|
3028
|
+
return {
|
|
3029
|
+
orphanedLinks: orphanedCount,
|
|
3030
|
+
removedOrphanedLinks
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
//#endregion
|
|
3035
|
+
//#region src/utils/normalizeLookupKey.ts
|
|
3036
|
+
function normalizeLookupKey(value) {
|
|
3037
|
+
return value.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
//#endregion
|
|
3041
|
+
//#region src/services/directus/cleanupOrphanedPropertyLinks.ts
|
|
3042
|
+
async function cleanupOrphanedPropertyLinks(client) {
|
|
3043
|
+
taskProgressService.report("Cleaning orphaned property links");
|
|
3044
|
+
const removed = await deleteOrphanedPropertyLinks(client, await findOrphanedPropertyLinks(client));
|
|
3045
|
+
taskProgressService.report(`Removed ${removed} orphaned property links`);
|
|
3046
|
+
return removed;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
//#endregion
|
|
3050
|
+
//#region src/services/directus/queries/ensureInputLink.ts
|
|
3051
|
+
async function ensureInputLink(client, inputPropertyId, backendPropertyId) {
|
|
3052
|
+
const [existing] = await client.request(n$1("route_input_properties_backend_properties", {
|
|
3053
|
+
filter: {
|
|
3054
|
+
route_input_properties_id: { _eq: inputPropertyId },
|
|
3055
|
+
backend_properties_id: { _eq: backendPropertyId }
|
|
3056
|
+
},
|
|
3057
|
+
fields: ["id"],
|
|
3058
|
+
limit: 1
|
|
3059
|
+
}));
|
|
3060
|
+
if (existing) return false;
|
|
3061
|
+
await client.request(n$2("route_input_properties_backend_properties", {
|
|
3062
|
+
route_input_properties_id: inputPropertyId,
|
|
3063
|
+
backend_properties_id: backendPropertyId
|
|
3064
|
+
}));
|
|
3065
|
+
return true;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
//#endregion
|
|
3069
|
+
//#region src/services/directus/queries/ensureOutputLink.ts
|
|
3070
|
+
async function ensureOutputLink(client, outputPropertyId, backendPropertyId) {
|
|
3071
|
+
const [existing] = await client.request(n$1("route_output_properties_backend_properties", {
|
|
3072
|
+
filter: {
|
|
3073
|
+
route_output_properties_id: { _eq: outputPropertyId },
|
|
3074
|
+
backend_properties_id: { _eq: backendPropertyId }
|
|
3075
|
+
},
|
|
3076
|
+
fields: ["id"],
|
|
3077
|
+
limit: 1
|
|
3078
|
+
}));
|
|
3079
|
+
if (existing) return false;
|
|
3080
|
+
await client.request(n$2("route_output_properties_backend_properties", {
|
|
3081
|
+
route_output_properties_id: outputPropertyId,
|
|
3082
|
+
backend_properties_id: backendPropertyId
|
|
3083
|
+
}));
|
|
3084
|
+
return true;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
//#endregion
|
|
3088
|
+
//#region src/services/directus/queries/loadDirectusReferenceData.ts
|
|
3089
|
+
async function loadDirectusReferenceData(client) {
|
|
3090
|
+
const values = await client.request(n$1("predefined_values", {
|
|
3091
|
+
filter: { type: { _in: [
|
|
3092
|
+
"backend",
|
|
3093
|
+
"in_type",
|
|
3094
|
+
"out_type"
|
|
3095
|
+
] } },
|
|
3096
|
+
fields: [
|
|
3097
|
+
"id",
|
|
3098
|
+
"label",
|
|
3099
|
+
"type"
|
|
3100
|
+
],
|
|
3101
|
+
limit: -1
|
|
3102
|
+
}));
|
|
3103
|
+
const backendIds = /* @__PURE__ */ new Map();
|
|
3104
|
+
const inTypeIds = /* @__PURE__ */ new Map();
|
|
3105
|
+
const outTypeIds = /* @__PURE__ */ new Map();
|
|
3106
|
+
for (const value of values) {
|
|
3107
|
+
const normalizedKeys = [normalizeLookupKey(value.id), normalizeLookupKey(value.label)];
|
|
3108
|
+
const target = value.type === "backend" ? backendIds : value.type === "in_type" ? inTypeIds : outTypeIds;
|
|
3109
|
+
for (const key of normalizedKeys) target.set(key, value.id);
|
|
3110
|
+
}
|
|
3111
|
+
return {
|
|
3112
|
+
backendIds,
|
|
3113
|
+
inTypeIds,
|
|
3114
|
+
outTypeIds
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
|
|
2891
3118
|
//#endregion
|
|
2892
3119
|
//#region src/utils/catalogueStats.ts
|
|
2893
3120
|
function calculateNeedsReviewPercentage(inputProperties, outputProperties, needsReview) {
|
|
@@ -2896,10 +3123,36 @@ function calculateNeedsReviewPercentage(inputProperties, outputProperties, needs
|
|
|
2896
3123
|
}
|
|
2897
3124
|
|
|
2898
3125
|
//#endregion
|
|
2899
|
-
//#region src/
|
|
3126
|
+
//#region src/utils/hash/stableKey.ts
|
|
3127
|
+
function stableKey(...parts) {
|
|
3128
|
+
return createHash("sha1").update(parts.join("::")).digest("hex");
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
//#endregion
|
|
3132
|
+
//#region src/utils/normalizeField.ts
|
|
3133
|
+
function normalizeField(value) {
|
|
3134
|
+
return value.replace(/\[locale\]/g, "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
//#endregion
|
|
3138
|
+
//#region src/utils/matchBackendProperty.ts
|
|
3139
|
+
function matchBackendProperty(field, properties) {
|
|
3140
|
+
if (!properties) return;
|
|
3141
|
+
const normalized = normalizeField(field);
|
|
3142
|
+
return properties.find((property) => {
|
|
3143
|
+
const reference = normalizeField(property.field);
|
|
3144
|
+
return normalized === reference || normalized.endsWith(reference) || reference.endsWith(normalized);
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
//#endregion
|
|
3149
|
+
//#region src/utils/normalizeCodeText.ts
|
|
2900
3150
|
function normalizeCodeText(value) {
|
|
2901
|
-
return value.replace(/\s+/g, " ").replace(/;$/, "")
|
|
3151
|
+
return value.replace(/\s+/g, " ").trim().replace(/;$/, "");
|
|
2902
3152
|
}
|
|
3153
|
+
|
|
3154
|
+
//#endregion
|
|
3155
|
+
//#region src/services/useCaseAnalysisService.ts
|
|
2903
3156
|
function describeRuleEffect(node) {
|
|
2904
3157
|
if (Node.isBlock(node)) return node.getStatements().map(describeRuleEffect).filter(Boolean).join("; ");
|
|
2905
3158
|
if (Node.isReturnStatement(node)) return node.getExpression() ? `return ${normalizeCodeText(node.getExpression().getText())}` : "return";
|
|
@@ -3015,7 +3268,7 @@ var AnalysisCacheService = class {
|
|
|
3015
3268
|
const analysisCacheService = new AnalysisCacheService();
|
|
3016
3269
|
|
|
3017
3270
|
//#endregion
|
|
3018
|
-
//#region src/services/
|
|
3271
|
+
//#region src/services/resolveBackendProperties.ts
|
|
3019
3272
|
const propertiesBySource = /* @__PURE__ */ new WeakMap();
|
|
3020
3273
|
async function resolveBackendProperties(sources) {
|
|
3021
3274
|
const resolved = await Promise.all(sources.map(async (source) => {
|
|
@@ -3032,17 +3285,6 @@ async function resolveBackendProperties(sources) {
|
|
|
3032
3285
|
}));
|
|
3033
3286
|
return new Map(resolved.filter((entry) => entry[1] !== void 0).map(([backend, properties]) => [backend, properties]));
|
|
3034
3287
|
}
|
|
3035
|
-
function matchBackendProperty(field, properties) {
|
|
3036
|
-
if (!properties) return;
|
|
3037
|
-
const normalized = normalizeField(field);
|
|
3038
|
-
return properties.find((property) => {
|
|
3039
|
-
const reference = normalizeField(property.field);
|
|
3040
|
-
return normalized === reference || normalized.endsWith(reference) || reference.endsWith(normalized);
|
|
3041
|
-
});
|
|
3042
|
-
}
|
|
3043
|
-
function normalizeField(value) {
|
|
3044
|
-
return value.replace(/\[locale\]/g, "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
3045
|
-
}
|
|
3046
3288
|
|
|
3047
3289
|
//#endregion
|
|
3048
3290
|
//#region src/services/backendRouteExtractionService.ts
|
|
@@ -3077,16 +3319,7 @@ function extractBackendRouteCandidates(params) {
|
|
|
3077
3319
|
}
|
|
3078
3320
|
|
|
3079
3321
|
//#endregion
|
|
3080
|
-
//#region src/utils/
|
|
3081
|
-
function stableKey$2(...parts) {
|
|
3082
|
-
return createHash("sha1").update(parts.join("::")).digest("hex");
|
|
3083
|
-
}
|
|
3084
|
-
function buildRouteKey(method, routePath) {
|
|
3085
|
-
return stableKey$2(method, routePath);
|
|
3086
|
-
}
|
|
3087
|
-
|
|
3088
|
-
//#endregion
|
|
3089
|
-
//#region src/utils/pathNormalization.ts
|
|
3322
|
+
//#region src/utils/path/pathNormalization.ts
|
|
3090
3323
|
function normalizePathForMatch(value) {
|
|
3091
3324
|
return value.replace(/\[\]/g, "").replace(/\{[^}]+\}/g, "").replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
3092
3325
|
}
|
|
@@ -4008,8 +4241,8 @@ async function analyzeCodebaseRouteContracts(params) {
|
|
|
4008
4241
|
if (Node.isArrayLiteralExpression(expression)) routeDeclarations.push(...extractRoutesFromArray(routeFile, expression));
|
|
4009
4242
|
}
|
|
4010
4243
|
}
|
|
4011
|
-
const routeDeclarationsByKey = dedupeByKey(routeDeclarations, (item) => stableKey
|
|
4012
|
-
const selectedRouteDeclarations = selectedRouteKeys ? routeDeclarationsByKey.filter((route) => selectedRouteKeys.has(stableKey
|
|
4244
|
+
const routeDeclarationsByKey = dedupeByKey(routeDeclarations, (item) => stableKey(item.method, item.path, item.file, item.handlerRef || ""));
|
|
4245
|
+
const selectedRouteDeclarations = selectedRouteKeys ? routeDeclarationsByKey.filter((route) => selectedRouteKeys.has(stableKey(route.method, route.path))) : routeDeclarationsByKey;
|
|
4013
4246
|
taskProgressService.log(`${selectedRouteDeclarations.length} route definitions selected from ${routeDeclarationsByKey.length} discovered`);
|
|
4014
4247
|
const documents = [];
|
|
4015
4248
|
const dependencyFilesByHandler = /* @__PURE__ */ new Map();
|
|
@@ -4028,7 +4261,7 @@ async function analyzeCodebaseRouteContracts(params) {
|
|
|
4028
4261
|
const routeLabel = `[${index + 1}/${selectedRouteDeclarations.length}] - ${routeDeclaration.method} ${routeDeclaration.path}`;
|
|
4029
4262
|
taskProgressService.log(`${routeLabel} ...`);
|
|
4030
4263
|
await yieldToEventLoop();
|
|
4031
|
-
const routeKey = stableKey
|
|
4264
|
+
const routeKey = stableKey(routeDeclaration.method, routeDeclaration.path);
|
|
4032
4265
|
const routeContract = routeContracts?.get(routeKey);
|
|
4033
4266
|
const operation = swagger?.paths?.[routeDeclaration.path]?.[routeDeclaration.method.toLowerCase()];
|
|
4034
4267
|
if (!routeContract && (!operation || !swagger)) continue;
|
|
@@ -4208,7 +4441,7 @@ function resolveApiPropertySource(document, property, direction, matches = []) {
|
|
|
4208
4441
|
return match ? `${match.sourceFile}:${match.line}` : matches[0] ? backendFieldSource(matches[0]) : document.routeFile || null;
|
|
4209
4442
|
}
|
|
4210
4443
|
function ensureBackendRouteRecord(backendRoutesByKey, document, candidate) {
|
|
4211
|
-
const key = stableKey
|
|
4444
|
+
const key = stableKey(document.key, candidate.backend, candidate.route);
|
|
4212
4445
|
const existing = backendRoutesByKey.get(key);
|
|
4213
4446
|
if (existing) return existing;
|
|
4214
4447
|
const created = {
|
|
@@ -4288,7 +4521,7 @@ async function buildCatalogue(documents) {
|
|
|
4288
4521
|
for (const property of document.input) {
|
|
4289
4522
|
const matches = matchCandidates(property, document, "input");
|
|
4290
4523
|
const apiSource = resolveApiFieldSourceCandidate(document, property, "input");
|
|
4291
|
-
const apiPropertyKey = stableKey
|
|
4524
|
+
const apiPropertyKey = stableKey(document.key, "input", property.path);
|
|
4292
4525
|
const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
|
|
4293
4526
|
const isTransverseWithoutMapping = isTransversalInput(property) && !matches.length;
|
|
4294
4527
|
const evidenceStatus = isTransverseWithoutMapping ? "confirmed" : evidenceStatusFromCandidates(matches);
|
|
@@ -4308,13 +4541,13 @@ async function buildCatalogue(documents) {
|
|
|
4308
4541
|
evidence_status: evidenceStatus
|
|
4309
4542
|
});
|
|
4310
4543
|
if (!matches.length && !isTransverseWithoutMapping) mappingEvidence.push({
|
|
4311
|
-
key: stableKey
|
|
4544
|
+
key: stableKey(apiPropertyKey, "needs_review"),
|
|
4312
4545
|
evidence_type: "static_analysis",
|
|
4313
4546
|
status: "needs_review",
|
|
4314
4547
|
confidence_score: 0,
|
|
4315
4548
|
comment: `No backend property candidate matched ${property.path}`,
|
|
4316
4549
|
input_property_key: apiPropertyKey,
|
|
4317
|
-
backend_property_key: stableKey
|
|
4550
|
+
backend_property_key: stableKey(document.key, "unmatched", "input", property.path),
|
|
4318
4551
|
source_file: document.routeFile || null
|
|
4319
4552
|
});
|
|
4320
4553
|
for (const match of matches) {
|
|
@@ -4323,7 +4556,7 @@ async function buildCatalogue(documents) {
|
|
|
4323
4556
|
route: "unknown",
|
|
4324
4557
|
sourceFile: match.sourceFile
|
|
4325
4558
|
}).key;
|
|
4326
|
-
const backendPropertyKey = stableKey
|
|
4559
|
+
const backendPropertyKey = stableKey(backendRouteKey, "input", match.resolvedProperty?.field || match.backendField);
|
|
4327
4560
|
backendPropertiesByKey.set(backendPropertyKey, {
|
|
4328
4561
|
key: backendPropertyKey,
|
|
4329
4562
|
backend_route_key: backendRouteKey,
|
|
@@ -4336,7 +4569,7 @@ async function buildCatalogue(documents) {
|
|
|
4336
4569
|
provenance: "code_analysis"
|
|
4337
4570
|
});
|
|
4338
4571
|
mappingEvidence.push({
|
|
4339
|
-
key: stableKey
|
|
4572
|
+
key: stableKey(apiPropertyKey, backendPropertyKey),
|
|
4340
4573
|
evidence_type: "static_analysis",
|
|
4341
4574
|
status: match.requiresReview ? "needs_review" : match.confidence >= 90 ? "confirmed" : "inferred",
|
|
4342
4575
|
confidence_score: match.confidence,
|
|
@@ -4351,7 +4584,7 @@ async function buildCatalogue(documents) {
|
|
|
4351
4584
|
if (isIgnoredOutput(property)) continue;
|
|
4352
4585
|
const matches = matchCandidates(property, document, "output");
|
|
4353
4586
|
const apiSource = resolveApiFieldSourceCandidate(document, property, "output");
|
|
4354
|
-
const apiPropertyKey = stableKey
|
|
4587
|
+
const apiPropertyKey = stableKey(document.key, "output", property.path);
|
|
4355
4588
|
const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
|
|
4356
4589
|
const evidenceStatus = evidenceStatusFromCandidates(matches);
|
|
4357
4590
|
const backendMappings = dedupeByKey(matches.map((match) => toBackendMapping(document, match)), (mapping) => `${mapping.backend}:${mapping.document || ""}:${mapping.method || "CALL"}:${mapping.route || ""}:${mapping.field}:${mapping.source_file}`);
|
|
@@ -4370,13 +4603,13 @@ async function buildCatalogue(documents) {
|
|
|
4370
4603
|
...matches.length ? {} : { unresolved_backend_candidates: unresolvedBackendCandidates(property, apiSource?.domainField, document, "output") }
|
|
4371
4604
|
});
|
|
4372
4605
|
if (!matches.length) mappingEvidence.push({
|
|
4373
|
-
key: stableKey
|
|
4606
|
+
key: stableKey(apiPropertyKey, "needs_review"),
|
|
4374
4607
|
evidence_type: "static_analysis",
|
|
4375
4608
|
status: "needs_review",
|
|
4376
4609
|
confidence_score: 0,
|
|
4377
4610
|
comment: `No backend property candidate matched ${property.path}`,
|
|
4378
4611
|
output_property_key: apiPropertyKey,
|
|
4379
|
-
backend_property_key: stableKey
|
|
4612
|
+
backend_property_key: stableKey(document.key, "unmatched", "output", property.path),
|
|
4380
4613
|
source_file: document.routeFile || null
|
|
4381
4614
|
});
|
|
4382
4615
|
for (const match of matches) {
|
|
@@ -4385,7 +4618,7 @@ async function buildCatalogue(documents) {
|
|
|
4385
4618
|
route: "unknown",
|
|
4386
4619
|
sourceFile: match.sourceFile
|
|
4387
4620
|
}).key;
|
|
4388
|
-
const backendPropertyKey = stableKey
|
|
4621
|
+
const backendPropertyKey = stableKey(backendRouteKey, "output", match.resolvedProperty?.field || match.backendField);
|
|
4389
4622
|
backendPropertiesByKey.set(backendPropertyKey, {
|
|
4390
4623
|
key: backendPropertyKey,
|
|
4391
4624
|
backend_route_key: backendRouteKey,
|
|
@@ -4398,7 +4631,7 @@ async function buildCatalogue(documents) {
|
|
|
4398
4631
|
provenance: "code_analysis"
|
|
4399
4632
|
});
|
|
4400
4633
|
mappingEvidence.push({
|
|
4401
|
-
key: stableKey
|
|
4634
|
+
key: stableKey(apiPropertyKey, backendPropertyKey),
|
|
4402
4635
|
evidence_type: "static_analysis",
|
|
4403
4636
|
status: match.requiresReview ? "needs_review" : match.confidence >= 90 ? "confirmed" : "inferred",
|
|
4404
4637
|
confidence_score: match.confidence,
|
|
@@ -4437,239 +4670,13 @@ async function buildCatalogue(documents) {
|
|
|
4437
4670
|
}
|
|
4438
4671
|
|
|
4439
4672
|
//#endregion
|
|
4440
|
-
//#region src/
|
|
4441
|
-
const ROUTE_STATUSES = { published: "published" };
|
|
4442
|
-
const LOCAL_SOURCE_FILE = "digital-api:tools/datasource-catalogue";
|
|
4443
|
-
const DIRECTUS_RETRY_MAX_ATTEMPTS = 6;
|
|
4444
|
-
const DIRECTUS_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
4445
|
-
const DIRECTUS_RETRY_MAX_DELAY_MS = 8e3;
|
|
4446
|
-
const DIRECTUS_DELETE_WORKERS = 2;
|
|
4447
|
-
function isRecord$3(value) {
|
|
4448
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4449
|
-
}
|
|
4450
|
-
function isServiceUnavailableError(error) {
|
|
4451
|
-
if (!isRecord$3(error)) return false;
|
|
4452
|
-
if ((isRecord$3(error.response) ? error.response.status : void 0) === 503) return true;
|
|
4453
|
-
return Array.isArray(error.errors) && error.errors.some((item) => isRecord$3(item) && isRecord$3(item.extensions) && item.extensions.code === "SERVICE_UNAVAILABLE");
|
|
4454
|
-
}
|
|
4455
|
-
function isTransientNetworkError(error) {
|
|
4456
|
-
if (error instanceof TypeError && error.message === "fetch failed") return true;
|
|
4457
|
-
if (!isRecord$3(error)) return false;
|
|
4458
|
-
if (error.message === "fetch failed") return true;
|
|
4459
|
-
const cause = isRecord$3(error.cause) ? error.cause : void 0;
|
|
4460
|
-
return cause?.code === "ECONNRESET" || cause?.code === "ECONNREFUSED" || cause?.code === "ETIMEDOUT" || cause?.code === "UND_ERR_CONNECT_TIMEOUT";
|
|
4461
|
-
}
|
|
4462
|
-
async function retryDirectusRequest(request, options = {}) {
|
|
4463
|
-
const maxAttempts = options.maxAttempts ?? DIRECTUS_RETRY_MAX_ATTEMPTS;
|
|
4464
|
-
const initialDelayMs = options.initialDelayMs ?? DIRECTUS_RETRY_INITIAL_DELAY_MS;
|
|
4465
|
-
const maxDelayMs = options.maxDelayMs ?? DIRECTUS_RETRY_MAX_DELAY_MS;
|
|
4466
|
-
const sleep = options.sleep || ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
4467
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) try {
|
|
4468
|
-
return await request();
|
|
4469
|
-
} catch (error) {
|
|
4470
|
-
if (!isServiceUnavailableError(error) && !isTransientNetworkError(error) || attempt === maxAttempts) throw error;
|
|
4471
|
-
const delayMs = Math.min(initialDelayMs * 2 ** (attempt - 1), maxDelayMs);
|
|
4472
|
-
taskProgressService.report(`Directus request temporarily unavailable; retrying ${attempt + 1}/${maxAttempts} in ${(delayMs / 1e3).toFixed(1)}s`);
|
|
4473
|
-
await sleep(delayMs);
|
|
4474
|
-
}
|
|
4475
|
-
throw new Error("Directus retry attempts exhausted");
|
|
4476
|
-
}
|
|
4477
|
-
function toRouteIdentifier(route) {
|
|
4478
|
-
return `${route.method} ${route.path}`;
|
|
4479
|
-
}
|
|
4480
|
-
function toBackendRouteIdentifier(route) {
|
|
4481
|
-
return toRouteIdentifier(route);
|
|
4482
|
-
}
|
|
4483
|
-
function groupBackendRoutesForDirectusSync(backendRoutes, apiRoutesByKey) {
|
|
4484
|
-
const groups = /* @__PURE__ */ new Map();
|
|
4485
|
-
for (const backendRoute of backendRoutes) {
|
|
4486
|
-
const apiRoute = apiRoutesByKey.get(backendRoute.route_key);
|
|
4487
|
-
const key = apiRoute ? `${backendRoute.backend}${apiRoute.method}${apiRoute.path}` : `${backendRoute.backend}missing-api-route${backendRoute.key}`;
|
|
4488
|
-
const group = groups.get(key);
|
|
4489
|
-
if (group) group.keys.push(backendRoute.key);
|
|
4490
|
-
else groups.set(key, {
|
|
4491
|
-
route: backendRoute,
|
|
4492
|
-
keys: [backendRoute.key]
|
|
4493
|
-
});
|
|
4494
|
-
}
|
|
4495
|
-
return [...groups.values()];
|
|
4496
|
-
}
|
|
4497
|
-
function selectCatalogueForDirectusPush(catalogue, routeSelector) {
|
|
4498
|
-
if (!routeSelector) return catalogue;
|
|
4499
|
-
const routes = catalogue.routes.filter((route) => route.method === routeSelector.method && route.path === routeSelector.path);
|
|
4500
|
-
if (!routes.length) throw new Error(`Route not found in catalogue: ${routeSelector.method} ${routeSelector.path}`);
|
|
4501
|
-
const routeKeys = new Set(routes.map((route) => route.key));
|
|
4502
|
-
const routeInputProperties = catalogue.route_input_properties.filter((property) => routeKeys.has(property.route_key));
|
|
4503
|
-
const routeOutputProperties = catalogue.route_output_properties.filter((property) => routeKeys.has(property.route_key));
|
|
4504
|
-
const backendRoutes = catalogue.backend_routes.filter((route) => routeKeys.has(route.route_key));
|
|
4505
|
-
const backendRouteKeys = new Set(backendRoutes.map((route) => route.key));
|
|
4506
|
-
const backendProperties = catalogue.backend_properties.filter((property) => backendRouteKeys.has(property.backend_route_key));
|
|
4507
|
-
const apiPropertyKeys = new Set([...routeInputProperties, ...routeOutputProperties].map((property) => property.key));
|
|
4508
|
-
const backendPropertyKeys = new Set(backendProperties.map((property) => property.key));
|
|
4509
|
-
return {
|
|
4510
|
-
...catalogue,
|
|
4511
|
-
routes,
|
|
4512
|
-
route_input_properties: routeInputProperties,
|
|
4513
|
-
route_output_properties: routeOutputProperties,
|
|
4514
|
-
backend_routes: backendRoutes,
|
|
4515
|
-
backend_properties: backendProperties,
|
|
4516
|
-
mapping_evidence: catalogue.mapping_evidence.filter((evidence) => backendPropertyKeys.has(evidence.backend_property_key) && (apiPropertyKeys.has(evidence.input_property_key || "") || apiPropertyKeys.has(evidence.output_property_key || ""))),
|
|
4517
|
-
rules: catalogue.rules.filter((rule) => routeKeys.has(rule.route_key)),
|
|
4518
|
-
aggregations: catalogue.aggregations.filter((aggregation) => routeKeys.has(aggregation.route_key))
|
|
4519
|
-
};
|
|
4520
|
-
}
|
|
4521
|
-
function inferInputType(field) {
|
|
4522
|
-
if (field.startsWith("params.")) return "PATH";
|
|
4523
|
-
if (field.startsWith("query.")) return "QUERY";
|
|
4524
|
-
if (field.startsWith("headers.")) return "HEADER";
|
|
4525
|
-
return "BODY";
|
|
4526
|
-
}
|
|
4527
|
-
function normalizeLookupKey(value) {
|
|
4528
|
-
return value.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
4529
|
-
}
|
|
4530
|
-
function normalizeDirectusPath(value) {
|
|
4531
|
-
return value.replace(/\?\.\[/g, "[").replace(/\?\./g, ".");
|
|
4532
|
-
}
|
|
4673
|
+
//#region src/utils/string/toPascalCase.ts
|
|
4533
4674
|
function toPascalCase(value) {
|
|
4534
|
-
return value.toLowerCase().replace(/(^|[^a-z0-9]+)([a-z0-9])/g, (_match, _separator, character) => character.toUpperCase());
|
|
4535
|
-
}
|
|
4536
|
-
function buildTranslationSummary(route) {
|
|
4537
|
-
const routeIdentifier = toRouteIdentifier(route);
|
|
4538
|
-
const primarySource = route.analysis_files[0] || route.source_file || LOCAL_SOURCE_FILE;
|
|
4539
|
-
return [{
|
|
4540
|
-
languages_code: "en-US",
|
|
4541
|
-
summary: routeIdentifier,
|
|
4542
|
-
description: `Generated from ${path.basename(primarySource)} on 2026-07-16`
|
|
4543
|
-
}];
|
|
4544
|
-
}
|
|
4545
|
-
async function createClient(baseUrl, token) {
|
|
4546
|
-
const client = t$1(baseUrl).with(r()).with(e$3(token));
|
|
4547
|
-
return { request: (request) => retryDirectusRequest(() => client.request(request)) };
|
|
4548
|
-
}
|
|
4549
|
-
function directusReadItems(collection, query) {
|
|
4550
|
-
return n$1(collection, query);
|
|
4551
|
-
}
|
|
4552
|
-
function directusCreateItem(collection, payload) {
|
|
4553
|
-
return n$2(collection, payload);
|
|
4554
|
-
}
|
|
4555
|
-
function directusUpdateItem(collection, id, payload) {
|
|
4556
|
-
return i(collection, id, payload);
|
|
4557
|
-
}
|
|
4558
|
-
function directusDeleteItem(collection, id) {
|
|
4559
|
-
return r$1(collection, id);
|
|
4560
|
-
}
|
|
4561
|
-
async function findOrphanedPropertyLinks(client) {
|
|
4562
|
-
const [inputProperties, outputProperties, backendProperties, inputLinks, outputLinks] = await Promise.all([
|
|
4563
|
-
client.request(directusReadItems("route_input_properties", {
|
|
4564
|
-
fields: ["id"],
|
|
4565
|
-
limit: -1
|
|
4566
|
-
})),
|
|
4567
|
-
client.request(directusReadItems("route_output_properties", {
|
|
4568
|
-
fields: ["id"],
|
|
4569
|
-
limit: -1
|
|
4570
|
-
})),
|
|
4571
|
-
client.request(directusReadItems("backend_properties", {
|
|
4572
|
-
fields: ["id"],
|
|
4573
|
-
limit: -1
|
|
4574
|
-
})),
|
|
4575
|
-
client.request(directusReadItems("route_input_properties_backend_properties", {
|
|
4576
|
-
fields: [
|
|
4577
|
-
"id",
|
|
4578
|
-
"route_input_properties_id",
|
|
4579
|
-
"backend_properties_id"
|
|
4580
|
-
],
|
|
4581
|
-
limit: -1
|
|
4582
|
-
})),
|
|
4583
|
-
client.request(directusReadItems("route_output_properties_backend_properties", {
|
|
4584
|
-
fields: [
|
|
4585
|
-
"id",
|
|
4586
|
-
"route_output_properties_id",
|
|
4587
|
-
"backend_properties_id"
|
|
4588
|
-
],
|
|
4589
|
-
limit: -1
|
|
4590
|
-
}))
|
|
4591
|
-
]);
|
|
4592
|
-
const ids = (items) => new Set(items.map((item) => String(item.id)));
|
|
4593
|
-
const inputIds = ids(inputProperties);
|
|
4594
|
-
const outputIds = ids(outputProperties);
|
|
4595
|
-
const backendIds = ids(backendProperties);
|
|
4596
|
-
return {
|
|
4597
|
-
input: inputLinks.filter((link) => !inputIds.has(String(link.route_input_properties_id)) || !backendIds.has(String(link.backend_properties_id))),
|
|
4598
|
-
output: outputLinks.filter((link) => !outputIds.has(String(link.route_output_properties_id)) || !backendIds.has(String(link.backend_properties_id)))
|
|
4599
|
-
};
|
|
4600
|
-
}
|
|
4601
|
-
async function deleteOrphanedPropertyLinks(client, orphanedLinks) {
|
|
4602
|
-
const links = [...orphanedLinks.input.map((link) => ({
|
|
4603
|
-
collection: "route_input_properties_backend_properties",
|
|
4604
|
-
id: link.id
|
|
4605
|
-
})), ...orphanedLinks.output.map((link) => ({
|
|
4606
|
-
collection: "route_output_properties_backend_properties",
|
|
4607
|
-
id: link.id
|
|
4608
|
-
}))];
|
|
4609
|
-
let nextIndex = 0;
|
|
4610
|
-
await Promise.all(Array.from({ length: Math.min(DIRECTUS_DELETE_WORKERS, links.length) }, async () => {
|
|
4611
|
-
while (nextIndex < links.length) {
|
|
4612
|
-
const link = links[nextIndex++];
|
|
4613
|
-
await client.request(directusDeleteItem(link.collection, link.id));
|
|
4614
|
-
}
|
|
4615
|
-
}));
|
|
4616
|
-
return links.length;
|
|
4617
|
-
}
|
|
4618
|
-
async function cleanupOrphanedPropertyLinks(client) {
|
|
4619
|
-
taskProgressService.report("Cleaning orphaned property links");
|
|
4620
|
-
const removed = await deleteOrphanedPropertyLinks(client, await findOrphanedPropertyLinks(client));
|
|
4621
|
-
taskProgressService.report(`Removed ${removed} orphaned property links`);
|
|
4622
|
-
return removed;
|
|
4623
|
-
}
|
|
4624
|
-
async function cleanDirectusOrphanedPropertyLinks(options) {
|
|
4625
|
-
taskProgressService.report("Connecting to Directus");
|
|
4626
|
-
const client = await createClient(options.baseUrl, options.token);
|
|
4627
|
-
taskProgressService.report("Finding orphaned property links");
|
|
4628
|
-
const orphanedLinks = await findOrphanedPropertyLinks(client);
|
|
4629
|
-
const orphanedCount = orphanedLinks.input.length + orphanedLinks.output.length;
|
|
4630
|
-
if (options.dryRun) {
|
|
4631
|
-
taskProgressService.report(`${orphanedCount} orphaned property links found (dry-run)`);
|
|
4632
|
-
return {
|
|
4633
|
-
orphanedLinks: orphanedCount,
|
|
4634
|
-
removedOrphanedLinks: 0
|
|
4635
|
-
};
|
|
4636
|
-
}
|
|
4637
|
-
taskProgressService.report("Removing orphaned property links");
|
|
4638
|
-
const removedOrphanedLinks = await deleteOrphanedPropertyLinks(client, orphanedLinks);
|
|
4639
|
-
taskProgressService.report(`Removed ${removedOrphanedLinks} orphaned property links`);
|
|
4640
|
-
return {
|
|
4641
|
-
orphanedLinks: orphanedCount,
|
|
4642
|
-
removedOrphanedLinks
|
|
4643
|
-
};
|
|
4644
|
-
}
|
|
4645
|
-
async function loadDirectusReferenceData(client) {
|
|
4646
|
-
const values = await client.request(directusReadItems("predefined_values", {
|
|
4647
|
-
filter: { type: { _in: [
|
|
4648
|
-
"backend",
|
|
4649
|
-
"in_type",
|
|
4650
|
-
"out_type"
|
|
4651
|
-
] } },
|
|
4652
|
-
fields: [
|
|
4653
|
-
"id",
|
|
4654
|
-
"label",
|
|
4655
|
-
"type"
|
|
4656
|
-
],
|
|
4657
|
-
limit: -1
|
|
4658
|
-
}));
|
|
4659
|
-
const backendIds = /* @__PURE__ */ new Map();
|
|
4660
|
-
const inTypeIds = /* @__PURE__ */ new Map();
|
|
4661
|
-
const outTypeIds = /* @__PURE__ */ new Map();
|
|
4662
|
-
for (const value of values) {
|
|
4663
|
-
const normalizedKeys = [normalizeLookupKey(value.id), normalizeLookupKey(value.label)];
|
|
4664
|
-
const target = value.type === "backend" ? backendIds : value.type === "in_type" ? inTypeIds : outTypeIds;
|
|
4665
|
-
for (const key of normalizedKeys) target.set(key, value.id);
|
|
4666
|
-
}
|
|
4667
|
-
return {
|
|
4668
|
-
backendIds,
|
|
4669
|
-
inTypeIds,
|
|
4670
|
-
outTypeIds
|
|
4671
|
-
};
|
|
4675
|
+
return value.toLowerCase().replace(/(^|[^a-z0-9]+)([a-z0-9])/g, (_match, _separator, character) => character.toUpperCase());
|
|
4672
4676
|
}
|
|
4677
|
+
|
|
4678
|
+
//#endregion
|
|
4679
|
+
//#region src/services/directus/queries/resolveBackendReference.ts
|
|
4673
4680
|
async function resolveBackendReference(client, references, backend) {
|
|
4674
4681
|
const lookupKey = normalizeLookupKey(backend);
|
|
4675
4682
|
const existingId = references.backendIds.get(lookupKey);
|
|
@@ -4678,7 +4685,7 @@ async function resolveBackendReference(client, references, backend) {
|
|
|
4678
4685
|
created: false
|
|
4679
4686
|
};
|
|
4680
4687
|
if (!isKnownBackendType(backend)) return { created: false };
|
|
4681
|
-
const created = await client.request(
|
|
4688
|
+
const created = await client.request(n$2("predefined_values", {
|
|
4682
4689
|
id: backend,
|
|
4683
4690
|
label: toPascalCase(backend),
|
|
4684
4691
|
type: "backend"
|
|
@@ -4690,42 +4697,72 @@ async function resolveBackendReference(client, references, backend) {
|
|
|
4690
4697
|
created: true
|
|
4691
4698
|
};
|
|
4692
4699
|
}
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4700
|
+
|
|
4701
|
+
//#endregion
|
|
4702
|
+
//#region src/services/directus/utils/normalizeDirectusPath.ts
|
|
4703
|
+
function normalizeDirectusPath(value) {
|
|
4704
|
+
return value.replace(/\?\.\[/g, "[").replace(/\?\./g, ".");
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4707
|
+
//#endregion
|
|
4708
|
+
//#region src/services/directus/queries/upsertBackendProperty.ts
|
|
4709
|
+
async function upsertBackendProperty(client, property, backendRouteId, typeId) {
|
|
4710
|
+
const path = normalizeDirectusPath(property.field);
|
|
4711
|
+
const [existing] = await client.request(n$1("backend_properties", {
|
|
4712
|
+
filter: {
|
|
4713
|
+
route: { _eq: backendRouteId },
|
|
4714
|
+
path: { _eq: path },
|
|
4715
|
+
type: { _eq: typeId }
|
|
4716
|
+
},
|
|
4717
|
+
fields: ["id"],
|
|
4698
4718
|
limit: 1
|
|
4699
4719
|
}));
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4720
|
+
if (existing) return existing.id;
|
|
4721
|
+
const created = await client.request(n$2("backend_properties", {
|
|
4722
|
+
route: backendRouteId,
|
|
4723
|
+
path: path || null,
|
|
4724
|
+
type: typeId,
|
|
4725
|
+
source_file: property.source_file || null,
|
|
4726
|
+
comments: property.provenance === "ai" ? "Inferred with AI" : null,
|
|
4727
|
+
content_name: null
|
|
4728
|
+
}));
|
|
4729
|
+
return typeof created === "string" ? created : created.id;
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
//#endregion
|
|
4733
|
+
//#region src/services/directus/utils/toBackendRouteIdentifier.ts
|
|
4734
|
+
function toBackendRouteIdentifier(route) {
|
|
4735
|
+
return `${route.method} ${route.path}`;
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
//#endregion
|
|
4739
|
+
//#region src/services/directus/queries/upsertBackendRoute.ts
|
|
4740
|
+
async function upsertBackendRoute(client, apiRoute, backendValueId) {
|
|
4741
|
+
const route = toBackendRouteIdentifier(apiRoute);
|
|
4742
|
+
const [existing] = await client.request(n$1("backend_routes", {
|
|
4743
|
+
filter: { route: { _eq: route } },
|
|
4744
|
+
fields: ["id"],
|
|
4745
|
+
limit: 1
|
|
4746
|
+
}));
|
|
4747
|
+
if (existing) return existing.id;
|
|
4748
|
+
const created = await client.request(n$2("backend_routes", {
|
|
4749
|
+
route,
|
|
4750
|
+
backend: backendValueId
|
|
4751
|
+
}));
|
|
4752
|
+
return typeof created === "string" ? created : created.id;
|
|
4714
4753
|
}
|
|
4754
|
+
|
|
4755
|
+
//#endregion
|
|
4756
|
+
//#region src/services/directus/queries/upsertInputProperty.ts
|
|
4715
4757
|
async function upsertInputProperty(client, property, routeId, inType) {
|
|
4716
4758
|
const field = normalizeDirectusPath(property.field);
|
|
4717
|
-
const [existing] = await client.request(
|
|
4759
|
+
const [existing] = await client.request(n$1("route_input_properties", {
|
|
4718
4760
|
filter: {
|
|
4719
4761
|
route_id: { _eq: routeId },
|
|
4720
4762
|
path: { _eq: field },
|
|
4721
4763
|
in_type: { _eq: inType }
|
|
4722
4764
|
},
|
|
4723
|
-
fields: [
|
|
4724
|
-
"id",
|
|
4725
|
-
"route_id",
|
|
4726
|
-
"path",
|
|
4727
|
-
"in_type"
|
|
4728
|
-
],
|
|
4765
|
+
fields: ["id"],
|
|
4729
4766
|
limit: 1
|
|
4730
4767
|
}));
|
|
4731
4768
|
const payload = {
|
|
@@ -4733,30 +4770,28 @@ async function upsertInputProperty(client, property, routeId, inType) {
|
|
|
4733
4770
|
path: field,
|
|
4734
4771
|
in_type: inType,
|
|
4735
4772
|
description: property.description || null,
|
|
4736
|
-
source_file: property.source_file ||
|
|
4773
|
+
source_file: property.source_file || null,
|
|
4737
4774
|
deprecated: false
|
|
4738
4775
|
};
|
|
4739
4776
|
if (!existing) {
|
|
4740
|
-
const created = await client.request(
|
|
4777
|
+
const created = await client.request(n$2("route_input_properties", payload));
|
|
4741
4778
|
return typeof created === "string" ? created : created.id;
|
|
4742
4779
|
}
|
|
4743
|
-
await client.request(
|
|
4780
|
+
await client.request(i("route_input_properties", existing.id, payload));
|
|
4744
4781
|
return existing.id;
|
|
4745
4782
|
}
|
|
4783
|
+
|
|
4784
|
+
//#endregion
|
|
4785
|
+
//#region src/services/directus/queries/upsertOutputProperty.ts
|
|
4746
4786
|
async function upsertOutputProperty(client, property, routeId, outType) {
|
|
4747
4787
|
const field = normalizeDirectusPath(property.field);
|
|
4748
|
-
const [existing] = await client.request(
|
|
4788
|
+
const [existing] = await client.request(n$1("route_output_properties", {
|
|
4749
4789
|
filter: {
|
|
4750
4790
|
route_id: { _eq: routeId },
|
|
4751
4791
|
path: { _eq: field },
|
|
4752
4792
|
out_type: { _eq: outType }
|
|
4753
4793
|
},
|
|
4754
|
-
fields: [
|
|
4755
|
-
"id",
|
|
4756
|
-
"route_id",
|
|
4757
|
-
"path",
|
|
4758
|
-
"out_type"
|
|
4759
|
-
],
|
|
4794
|
+
fields: ["id"],
|
|
4760
4795
|
limit: 1
|
|
4761
4796
|
}));
|
|
4762
4797
|
const payload = {
|
|
@@ -4764,101 +4799,110 @@ async function upsertOutputProperty(client, property, routeId, outType) {
|
|
|
4764
4799
|
path: field,
|
|
4765
4800
|
out_type: outType,
|
|
4766
4801
|
description: property.description || null,
|
|
4767
|
-
source_file: property.source_file ||
|
|
4802
|
+
source_file: property.source_file || null,
|
|
4768
4803
|
deprecated: false,
|
|
4769
4804
|
is_dynamic: field.includes("[]") || field.includes("{")
|
|
4770
4805
|
};
|
|
4771
4806
|
if (!existing) {
|
|
4772
|
-
const created = await client.request(
|
|
4807
|
+
const created = await client.request(n$2("route_output_properties", payload));
|
|
4773
4808
|
return typeof created === "string" ? created : created.id;
|
|
4774
4809
|
}
|
|
4775
|
-
await client.request(
|
|
4810
|
+
await client.request(i("route_output_properties", existing.id, payload));
|
|
4776
4811
|
return existing.id;
|
|
4777
4812
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4813
|
+
|
|
4814
|
+
//#endregion
|
|
4815
|
+
//#region src/services/directus/utils/buildTranslationSummary.ts
|
|
4816
|
+
function buildTranslationSummary(route) {
|
|
4817
|
+
const routeIdentifier = toBackendRouteIdentifier(route);
|
|
4818
|
+
const primarySource = route.analysis_files[0] || route.source_file;
|
|
4819
|
+
return [{
|
|
4820
|
+
languages_code: "en-US",
|
|
4821
|
+
summary: routeIdentifier,
|
|
4822
|
+
description: primarySource ? `Generated from ${path.basename(primarySource)}` : "Generated by Atlas"
|
|
4823
|
+
}];
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
//#endregion
|
|
4827
|
+
//#region src/services/directus/queries/upsertRoute.ts
|
|
4828
|
+
async function upsertRoute(client, route) {
|
|
4829
|
+
const routeIdentifier = toBackendRouteIdentifier(route);
|
|
4830
|
+
const [existing] = await client.request(n$1("routes", {
|
|
4831
|
+
filter: { route: { _eq: routeIdentifier } },
|
|
4832
|
+
fields: ["id"],
|
|
4787
4833
|
limit: 1
|
|
4788
4834
|
}));
|
|
4789
4835
|
const payload = {
|
|
4790
|
-
route:
|
|
4791
|
-
|
|
4836
|
+
route: routeIdentifier,
|
|
4837
|
+
status: "published",
|
|
4838
|
+
deprecated: false,
|
|
4839
|
+
first_published_version: route.version,
|
|
4840
|
+
last_published_version: route.version,
|
|
4841
|
+
translations: buildTranslationSummary(route)
|
|
4792
4842
|
};
|
|
4793
4843
|
if (!existing) {
|
|
4794
|
-
const created = await client.request(
|
|
4844
|
+
const created = await client.request(n$2("routes", payload));
|
|
4795
4845
|
return typeof created === "string" ? created : created.id;
|
|
4796
4846
|
}
|
|
4847
|
+
await client.request(i("routes", existing.id, payload));
|
|
4797
4848
|
return existing.id;
|
|
4798
4849
|
}
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
}
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
],
|
|
4814
|
-
limit: 1
|
|
4815
|
-
}));
|
|
4816
|
-
const payload = {
|
|
4817
|
-
route: backendRouteId,
|
|
4818
|
-
path: field || null,
|
|
4819
|
-
type: typeId,
|
|
4820
|
-
source_file: property.source_file || LOCAL_SOURCE_FILE,
|
|
4821
|
-
comments: property.provenance === "ai" ? "Inferred with AI" : null,
|
|
4822
|
-
content_name: null
|
|
4823
|
-
};
|
|
4824
|
-
if (!existing) {
|
|
4825
|
-
const created = await client.request(directusCreateItem("backend_properties", payload));
|
|
4826
|
-
return typeof created === "string" ? created : created.id;
|
|
4850
|
+
|
|
4851
|
+
//#endregion
|
|
4852
|
+
//#region src/services/directus/utils/groupBackendRoutesForDirectusSync.ts
|
|
4853
|
+
function groupBackendRoutesForDirectusSync(backendRoutes, apiRoutesByKey) {
|
|
4854
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4855
|
+
for (const backendRoute of backendRoutes) {
|
|
4856
|
+
const apiRoute = apiRoutesByKey.get(backendRoute.route_key);
|
|
4857
|
+
const key = apiRoute ? `${backendRoute.backend}\0${apiRoute.method}\0${apiRoute.path}` : `${backendRoute.backend}\0missing-api-route\0${backendRoute.key}`;
|
|
4858
|
+
const group = groups.get(key);
|
|
4859
|
+
if (group) group.keys.push(backendRoute.key);
|
|
4860
|
+
else groups.set(key, {
|
|
4861
|
+
route: backendRoute,
|
|
4862
|
+
keys: [backendRoute.key]
|
|
4863
|
+
});
|
|
4827
4864
|
}
|
|
4828
|
-
return
|
|
4865
|
+
return [...groups.values()];
|
|
4829
4866
|
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
}));
|
|
4839
|
-
if (existing) return false;
|
|
4840
|
-
await client.request(directusCreateItem("route_input_properties_backend_properties", {
|
|
4841
|
-
route_input_properties_id: inputPropertyId,
|
|
4842
|
-
backend_properties_id: backendPropertyId
|
|
4843
|
-
}));
|
|
4844
|
-
return true;
|
|
4867
|
+
|
|
4868
|
+
//#endregion
|
|
4869
|
+
//#region src/services/directus/utils/inferInputType.ts
|
|
4870
|
+
function inferInputType(field) {
|
|
4871
|
+
if (field.startsWith("params.")) return "PATH";
|
|
4872
|
+
if (field.startsWith("query.")) return "QUERY";
|
|
4873
|
+
if (field.startsWith("headers.")) return "HEADER";
|
|
4874
|
+
return "BODY";
|
|
4845
4875
|
}
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4876
|
+
|
|
4877
|
+
//#endregion
|
|
4878
|
+
//#region src/services/directus/utils/selectCatalogueForDirectusPush.ts
|
|
4879
|
+
function selectCatalogueForDirectusPush(catalogue, routeSelector) {
|
|
4880
|
+
if (!routeSelector) return catalogue;
|
|
4881
|
+
const routes = catalogue.routes.filter((route) => route.method === routeSelector.method && route.path === routeSelector.path);
|
|
4882
|
+
if (!routes.length) throw new Error(`Route not found in catalogue: ${routeSelector.method} ${routeSelector.path}`);
|
|
4883
|
+
const routeKeys = new Set(routes.map((route) => route.key));
|
|
4884
|
+
const routeInputProperties = catalogue.route_input_properties.filter((property) => routeKeys.has(property.route_key));
|
|
4885
|
+
const routeOutputProperties = catalogue.route_output_properties.filter((property) => routeKeys.has(property.route_key));
|
|
4886
|
+
const backendRoutes = catalogue.backend_routes.filter((route) => routeKeys.has(route.route_key));
|
|
4887
|
+
const backendRouteKeys = new Set(backendRoutes.map((route) => route.key));
|
|
4888
|
+
const backendProperties = catalogue.backend_properties.filter((property) => backendRouteKeys.has(property.backend_route_key));
|
|
4889
|
+
const apiPropertyKeys = new Set([...routeInputProperties, ...routeOutputProperties].map((property) => property.key));
|
|
4890
|
+
const backendPropertyKeys = new Set(backendProperties.map((property) => property.key));
|
|
4891
|
+
return {
|
|
4892
|
+
...catalogue,
|
|
4893
|
+
routes,
|
|
4894
|
+
route_input_properties: routeInputProperties,
|
|
4895
|
+
route_output_properties: routeOutputProperties,
|
|
4896
|
+
backend_routes: backendRoutes,
|
|
4897
|
+
backend_properties: backendProperties,
|
|
4898
|
+
mapping_evidence: catalogue.mapping_evidence.filter((evidence) => backendPropertyKeys.has(evidence.backend_property_key) && (apiPropertyKeys.has(evidence.input_property_key || "") || apiPropertyKeys.has(evidence.output_property_key || ""))),
|
|
4899
|
+
rules: catalogue.rules.filter((rule) => routeKeys.has(rule.route_key)),
|
|
4900
|
+
aggregations: catalogue.aggregations.filter((aggregation) => routeKeys.has(aggregation.route_key))
|
|
4901
|
+
};
|
|
4861
4902
|
}
|
|
4903
|
+
|
|
4904
|
+
//#endregion
|
|
4905
|
+
//#region src/services/directus/pushCatalogueToDirectus.ts
|
|
4862
4906
|
async function pushCatalogueToDirectus(catalogue, options) {
|
|
4863
4907
|
const pushedCollections = [
|
|
4864
4908
|
"predefined_values",
|
|
@@ -4883,7 +4927,7 @@ async function pushCatalogueToDirectus(catalogue, options) {
|
|
|
4883
4927
|
};
|
|
4884
4928
|
}
|
|
4885
4929
|
taskProgressService.report("Connecting to Directus");
|
|
4886
|
-
const client = await createClient(
|
|
4930
|
+
const client = await createClient();
|
|
4887
4931
|
taskProgressService.report("Loading predefined values from Directus");
|
|
4888
4932
|
const refs = await loadDirectusReferenceData(client);
|
|
4889
4933
|
const warnings = /* @__PURE__ */ new Set();
|
|
@@ -4917,7 +4961,7 @@ async function pushCatalogueToDirectus(catalogue, options) {
|
|
|
4917
4961
|
continue;
|
|
4918
4962
|
}
|
|
4919
4963
|
if (backendReference.created) pushedItems += 1;
|
|
4920
|
-
const backendRouteId = await upsertBackendRoute(client, route,
|
|
4964
|
+
const backendRouteId = await upsertBackendRoute(client, route, backendReference.id);
|
|
4921
4965
|
for (const key of keys) backendRouteIds.set(key, backendRouteId);
|
|
4922
4966
|
pushedItems += 1;
|
|
4923
4967
|
}
|
|
@@ -11724,7 +11768,7 @@ var require_dist$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
11724
11768
|
}));
|
|
11725
11769
|
|
|
11726
11770
|
//#endregion
|
|
11727
|
-
//#region src/utils/routeReview.ts
|
|
11771
|
+
//#region src/utils/route/routeReview.ts
|
|
11728
11772
|
var import_dist$1 = require_dist$1();
|
|
11729
11773
|
function sanitizePathSegment(value) {
|
|
11730
11774
|
return value.replace(/[{}]/g, "").replace(/[^a-z0-9._-]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "") || "root";
|
|
@@ -11898,9 +11942,6 @@ function relativizeRouteReviewDocument(document, repoRoot) {
|
|
|
11898
11942
|
|
|
11899
11943
|
//#endregion
|
|
11900
11944
|
//#region src/services/catalogueArtifactService.ts
|
|
11901
|
-
function stableKey$1(...parts) {
|
|
11902
|
-
return createHash("sha1").update(parts.join("::")).digest("hex");
|
|
11903
|
-
}
|
|
11904
11945
|
async function writeRouteReviewDocument(catalogue, outputDir, repoRoot, routeKey) {
|
|
11905
11946
|
const route = catalogue.routes.find((item) => item.key === routeKey);
|
|
11906
11947
|
if (!route) throw new Error(`Unable to write review document for unknown route key ${routeKey}`);
|
|
@@ -11932,7 +11973,7 @@ async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.
|
|
|
11932
11973
|
}
|
|
11933
11974
|
function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backendRoutesByKey, backendPropertiesByKey, mappingEvidence) {
|
|
11934
11975
|
const backendRoute = mapping.route || (mapping.document ? `DOCUMENT ${mapping.document}` : "unknown");
|
|
11935
|
-
const backendRouteKey = stableKey
|
|
11976
|
+
const backendRouteKey = stableKey(routeKey, mapping.backend, backendRoute);
|
|
11936
11977
|
const backendRouteRecord = backendRoutesByKey.get(backendRouteKey) || {
|
|
11937
11978
|
key: backendRouteKey,
|
|
11938
11979
|
backend: mapping.backend,
|
|
@@ -11942,7 +11983,7 @@ function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backend
|
|
|
11942
11983
|
provenance: "code_analysis"
|
|
11943
11984
|
};
|
|
11944
11985
|
backendRoutesByKey.set(backendRouteKey, backendRouteRecord);
|
|
11945
|
-
const backendPropertyKey = stableKey
|
|
11986
|
+
const backendPropertyKey = stableKey(backendRouteKey, direction, mapping.field);
|
|
11946
11987
|
backendPropertiesByKey.set(backendPropertyKey, {
|
|
11947
11988
|
key: backendPropertyKey,
|
|
11948
11989
|
backend_route_key: backendRouteKey,
|
|
@@ -11953,7 +11994,7 @@ function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backend
|
|
|
11953
11994
|
provenance: mapping.provenance || "code_analysis"
|
|
11954
11995
|
});
|
|
11955
11996
|
mappingEvidence.push({
|
|
11956
|
-
key: stableKey
|
|
11997
|
+
key: stableKey(apiPropertyKey, backendPropertyKey),
|
|
11957
11998
|
evidence_type: mapping.provenance === "ai" ? "inference" : "static_analysis",
|
|
11958
11999
|
status: mapping.confidence >= 90 ? "confirmed" : "inferred",
|
|
11959
12000
|
confidence_score: mapping.confidence,
|
|
@@ -11980,7 +12021,7 @@ function toCatalogueBackendMapping(mapping) {
|
|
|
11980
12021
|
}
|
|
11981
12022
|
function addDatasourceRoute(routeKey, datasource, backendRoutesByKey) {
|
|
11982
12023
|
const backendRoute = datasource.operation?.route || "unknown";
|
|
11983
|
-
const backendRouteKey = stableKey
|
|
12024
|
+
const backendRouteKey = stableKey(routeKey, datasource.type, backendRoute);
|
|
11984
12025
|
backendRoutesByKey.set(backendRouteKey, {
|
|
11985
12026
|
key: backendRouteKey,
|
|
11986
12027
|
backend: datasource.type,
|
|
@@ -12000,7 +12041,7 @@ function mergeRouteReviewDocuments(documents) {
|
|
|
12000
12041
|
const rules = [];
|
|
12001
12042
|
const aggregations = [];
|
|
12002
12043
|
for (const document of documents) {
|
|
12003
|
-
const routeKey = stableKey
|
|
12044
|
+
const routeKey = stableKey(document.route.method, document.route.path);
|
|
12004
12045
|
const mappedBackendNames = [...document.inputs, ...document.outputs].flatMap((property) => property.mapping?.backends?.map((mapping) => mapping.type) || []);
|
|
12005
12046
|
const backendNames = [.../* @__PURE__ */ new Set([...document.datasources.map((datasource) => datasource.type), ...mappedBackendNames])];
|
|
12006
12047
|
routes.push({
|
|
@@ -12015,7 +12056,7 @@ function mergeRouteReviewDocuments(documents) {
|
|
|
12015
12056
|
for (const datasource of document.datasources) addDatasourceRoute(routeKey, datasource, backendRoutesByKey);
|
|
12016
12057
|
const appendProperties = (direction, properties) => {
|
|
12017
12058
|
for (const property of properties) {
|
|
12018
|
-
const apiPropertyKey = stableKey
|
|
12059
|
+
const apiPropertyKey = stableKey(routeKey, direction, property.field);
|
|
12019
12060
|
const backendMappings = property.mapping?.backends?.map(toCatalogueBackendMapping) || [];
|
|
12020
12061
|
const catalogueProperty = {
|
|
12021
12062
|
key: apiPropertyKey,
|
|
@@ -12110,21 +12151,16 @@ async function readCatalogueFromDirectory(inputDir) {
|
|
|
12110
12151
|
}
|
|
12111
12152
|
|
|
12112
12153
|
//#endregion
|
|
12113
|
-
//#region src/services/directus/
|
|
12154
|
+
//#region src/services/directus/pushCatalogueDirectoryToDirectus.ts
|
|
12114
12155
|
async function pushCatalogueDirectoryToDirectus(params) {
|
|
12115
12156
|
const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
|
|
12116
12157
|
taskProgressService.report("Loading and reconstructing route YAML documents");
|
|
12117
12158
|
const { catalogue, files } = await readCatalogueFromDirectory(inputPath);
|
|
12118
12159
|
taskProgressService.report(`${files.length} route documents loaded, ${catalogue.backend_routes.length} backend routes reconstructed`);
|
|
12119
|
-
const directusUrl = params.directusUrl || process.env.DIRECTUS_URL || process.env.CMS_API_URL || getUserConfig().directusUrl;
|
|
12120
|
-
const directusToken = params.directusToken || process.env.DIRECTUS_TOKEN || process.env.CMS_DIRECTUS_TOKEN || process.env.CMS_API_TOKEN;
|
|
12121
|
-
if (!directusToken) throw new Error("Directus push requested but DIRECTUS_TOKEN/directus-token is missing");
|
|
12122
12160
|
return {
|
|
12123
12161
|
inputPath,
|
|
12124
12162
|
files,
|
|
12125
12163
|
result: await pushCatalogueToDirectus(catalogue, {
|
|
12126
|
-
baseUrl: directusUrl,
|
|
12127
|
-
token: directusToken,
|
|
12128
12164
|
dryRun: !params.write,
|
|
12129
12165
|
routeSelector: params.routeSelector
|
|
12130
12166
|
})
|
|
@@ -12132,7 +12168,13 @@ async function pushCatalogueDirectoryToDirectus(params) {
|
|
|
12132
12168
|
}
|
|
12133
12169
|
|
|
12134
12170
|
//#endregion
|
|
12135
|
-
//#region src/utils/
|
|
12171
|
+
//#region src/utils/route/isRouteSelector.ts
|
|
12172
|
+
function isRouteSelector(value) {
|
|
12173
|
+
return /^[A-Z]+\s+\//i.test(value.trim());
|
|
12174
|
+
}
|
|
12175
|
+
|
|
12176
|
+
//#endregion
|
|
12177
|
+
//#region src/utils/route/routeSelector.ts
|
|
12136
12178
|
function parseRouteSelector(value) {
|
|
12137
12179
|
if (!value) return;
|
|
12138
12180
|
const match = value.trim().match(/^([A-Z]+)\s+(\S+)$/i);
|
|
@@ -12146,18 +12188,12 @@ function parseRouteSelector(value) {
|
|
|
12146
12188
|
//#endregion
|
|
12147
12189
|
//#region src/commands/push.ts
|
|
12148
12190
|
const DEFAULT_CATALOGUE_DIRECTORY = ".tmp/datasource-catalogue";
|
|
12149
|
-
function isRouteSelectorArgument(value) {
|
|
12150
|
-
return /^[A-Z]+\s+\//i.test(value.trim());
|
|
12151
|
-
}
|
|
12152
|
-
function isRecord$2(value) {
|
|
12153
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12154
|
-
}
|
|
12155
12191
|
function formatPushError(error) {
|
|
12156
12192
|
if (error instanceof Error) return error.message;
|
|
12157
|
-
if (!isRecord$
|
|
12193
|
+
if (!isRecord$1(error)) return String(error);
|
|
12158
12194
|
const details = (Array.isArray(error.errors) ? error.errors : []).flatMap((item) => {
|
|
12159
|
-
if (!isRecord$
|
|
12160
|
-
return [`${isRecord$
|
|
12195
|
+
if (!isRecord$1(item) || typeof item.message !== "string") return [];
|
|
12196
|
+
return [`${isRecord$1(item.extensions) && typeof item.extensions.code === "string" ? `${item.extensions.code}: ` : ""}${item.message}`];
|
|
12161
12197
|
});
|
|
12162
12198
|
if (details.length) return `Directus API error: ${details.join("; ")}`;
|
|
12163
12199
|
if (typeof error.message === "string") return error.message;
|
|
@@ -12219,20 +12255,18 @@ function createPushStepClassifier() {
|
|
|
12219
12255
|
};
|
|
12220
12256
|
};
|
|
12221
12257
|
}
|
|
12222
|
-
var push_default = (program) => void program.command("push").argument("[catalogue-directory-or-route]", "Directory or route such as \"GET /v1/offers\"", DEFAULT_CATALOGUE_DIRECTORY).option("--route <route>", "Push one route only, for example \"POST /v0/accommodations_arrangement/check\"").option("--
|
|
12258
|
+
var push_default = (program) => void program.command("push").argument("[catalogue-directory-or-route]", "Directory or route such as \"GET /v1/offers\"", DEFAULT_CATALOGUE_DIRECTORY).option("--route <route>", "Push one route only, for example \"POST /v0/accommodations_arrangement/check\"").option("--write", "Persist changes to Directus", false).description("Push generated per-route YAML documents to Directus").action(async (catalogueDirectoryOrRoute, options) => {
|
|
12223
12259
|
intro("Datasource catalogue push");
|
|
12224
12260
|
const { cwd } = getUserConfig();
|
|
12225
12261
|
const progress = taskProgressService.createStepProgress(createPushStepClassifier());
|
|
12226
12262
|
try {
|
|
12227
|
-
const positionalRouteSelector =
|
|
12263
|
+
const positionalRouteSelector = isRouteSelector(catalogueDirectoryOrRoute) ? parseRouteSelector(catalogueDirectoryOrRoute) : void 0;
|
|
12228
12264
|
if (options.route && positionalRouteSelector) throw new Error("Specify the route either as the positional argument or with --route, not both");
|
|
12229
12265
|
const routeSelector = options.route ? parseRouteSelector(options.route) : positionalRouteSelector;
|
|
12230
12266
|
const catalogueDirectory = positionalRouteSelector ? DEFAULT_CATALOGUE_DIRECTORY : catalogueDirectoryOrRoute;
|
|
12231
12267
|
const pushResult = await progress.execute(() => pushCatalogueDirectoryToDirectus({
|
|
12232
12268
|
cwd,
|
|
12233
12269
|
catalogueDirectory,
|
|
12234
|
-
directusUrl: options.directusUrl,
|
|
12235
|
-
directusToken: options.directusToken,
|
|
12236
12270
|
routeSelector,
|
|
12237
12271
|
write: options.write
|
|
12238
12272
|
}));
|
|
@@ -12344,9 +12378,6 @@ async function writeBackendTopologyArtifact(artifact, outputDir) {
|
|
|
12344
12378
|
await fsPromises.writeFile(filePath, `${(0, import_dist$1.stringify)(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
|
|
12345
12379
|
return filePath;
|
|
12346
12380
|
}
|
|
12347
|
-
function isRecord$1(value) {
|
|
12348
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12349
|
-
}
|
|
12350
12381
|
function parseBackendTopologyArtifact(value, expectedMethod, expectedPath, filePath) {
|
|
12351
12382
|
if (!isRecord$1(value) || value.schema_version !== 3) throw new Error(`Invalid backend graph artifact schema: ${filePath}. Run generate:graph again.`);
|
|
12352
12383
|
const route = value.route;
|
|
@@ -12426,7 +12457,7 @@ async function generateHandler(cwd, routeArgument, options) {
|
|
|
12426
12457
|
completedTitle: "OpenAPI contract loaded"
|
|
12427
12458
|
}, () => loadOpenApiDocument(options.openapiUrl));
|
|
12428
12459
|
profile.add("OpenAPI", performance.now() - openApiStartedAt);
|
|
12429
|
-
const selectedRouteKeys = routeSelector ? /* @__PURE__ */ new Set([
|
|
12460
|
+
const selectedRouteKeys = routeSelector ? /* @__PURE__ */ new Set([stableKey(routeSelector.method, routeSelector.path)]) : void 0;
|
|
12430
12461
|
const totalRoutes = routeSelector ? 1 : Object.values(openApiDocument.paths || {}).reduce((total, pathItem) => total + Object.keys(pathItem || {}).filter((method) => /^(get|post|put|patch|delete|head|options)$/i.test(method)).length, 0);
|
|
12431
12462
|
const analysisStartedAt = performance.now();
|
|
12432
12463
|
progress$3.finish("OpenAPI contract loaded");
|
|
@@ -12492,12 +12523,16 @@ var generate_default = (program) => void program.command("generate:catalog").arg
|
|
|
12492
12523
|
});
|
|
12493
12524
|
|
|
12494
12525
|
//#endregion
|
|
12495
|
-
//#region src/
|
|
12496
|
-
function
|
|
12497
|
-
const
|
|
12498
|
-
if (!Number.isInteger(
|
|
12499
|
-
return
|
|
12526
|
+
//#region src/utils/number/parsePositiveInteger.ts
|
|
12527
|
+
function parsePositiveInteger(value, message = "Expected a positive integer") {
|
|
12528
|
+
const parsed = Number(value);
|
|
12529
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(message);
|
|
12530
|
+
return parsed;
|
|
12500
12531
|
}
|
|
12532
|
+
|
|
12533
|
+
//#endregion
|
|
12534
|
+
//#region src/commands/generateGraph.ts
|
|
12535
|
+
const parseWorkerCount$1 = (value) => parsePositiveInteger(value, "Worker count must be a positive integer");
|
|
12501
12536
|
async function generateGraphHandler(cwd, routeArgument, options) {
|
|
12502
12537
|
intro("Code source Graph generation");
|
|
12503
12538
|
const progress$2 = taskProgressService.createStepProgress((message) => {
|
|
@@ -12573,6 +12608,12 @@ var generateGraph_default = (program) => void program.command("generate:graph").
|
|
|
12573
12608
|
return generateGraphHandler(cwd, routeArgument, options);
|
|
12574
12609
|
});
|
|
12575
12610
|
|
|
12611
|
+
//#endregion
|
|
12612
|
+
//#region src/utils/number/formatPercentage.ts
|
|
12613
|
+
function formatPercentage(value, fractionDigits = 2) {
|
|
12614
|
+
return `${value.toFixed(fractionDigits)}%`;
|
|
12615
|
+
}
|
|
12616
|
+
|
|
12576
12617
|
//#endregion
|
|
12577
12618
|
//#region src/services/catalogueCoverageService.ts
|
|
12578
12619
|
/**
|
|
@@ -12595,7 +12636,7 @@ function validateCoverage(coverage, catalogues) {
|
|
|
12595
12636
|
});
|
|
12596
12637
|
}
|
|
12597
12638
|
function formatCoverageFailure(results) {
|
|
12598
|
-
return ["Coverage regression detected:", ...results.filter((result) => !result.passed).map((result) => `- ${result.route}: ${result.actual
|
|
12639
|
+
return ["Coverage regression detected:", ...results.filter((result) => !result.passed).map((result) => `- ${result.route}: ${formatPercentage(result.actual)} is below ${formatPercentage(result.expected)}`)].join("\n");
|
|
12599
12640
|
}
|
|
12600
12641
|
|
|
12601
12642
|
//#endregion
|
|
@@ -12620,13 +12661,13 @@ async function generateTestHandler(cwd) {
|
|
|
12620
12661
|
}, () => analyzeCodebaseRouteContracts({
|
|
12621
12662
|
cwd,
|
|
12622
12663
|
openApiDocument,
|
|
12623
|
-
selectedRouteKeys: /* @__PURE__ */ new Set([
|
|
12664
|
+
selectedRouteKeys: /* @__PURE__ */ new Set([stableKey(routeSelector.method, routeSelector.path)])
|
|
12624
12665
|
}));
|
|
12625
12666
|
if (documents.length !== 1) throw new Error(`Configured route was not found: ${expectation.route}`);
|
|
12626
12667
|
catalogues.set(expectation.route, await buildCatalogue(documents));
|
|
12627
12668
|
}
|
|
12628
12669
|
const results = validateCoverage(coverage, catalogues);
|
|
12629
|
-
log.message(results.map((result) => `${result.passed ? "✓" : "✗"} ${result.route}: ${result.actual
|
|
12670
|
+
log.message(results.map((result) => `${result.passed ? "✓" : "✗"} ${result.route}: ${formatPercentage(result.actual)} (minimum ${formatPercentage(result.expected)})`).join("\n"));
|
|
12630
12671
|
if (results.some((result) => !result.passed)) throw new Error(formatCoverageFailure(results));
|
|
12631
12672
|
}
|
|
12632
12673
|
var generateTest_default = (program) => void program.command("generate:test").description("Generate configured routes and verify their resolved-field coverage does not regress").action(async () => {
|
|
@@ -12642,6 +12683,25 @@ var generateTest_default = (program) => void program.command("generate:test").de
|
|
|
12642
12683
|
}
|
|
12643
12684
|
});
|
|
12644
12685
|
|
|
12686
|
+
//#endregion
|
|
12687
|
+
//#region src/utils/collection/forEachWithConcurrency.ts
|
|
12688
|
+
async function forEachWithConcurrency(items, workers, task) {
|
|
12689
|
+
let nextIndex = 0;
|
|
12690
|
+
const workerCount = Math.min(Math.max(1, workers), items.length);
|
|
12691
|
+
await Promise.all(Array.from({ length: workerCount }, async (_, workerId) => {
|
|
12692
|
+
while (nextIndex < items.length) {
|
|
12693
|
+
const item = items[nextIndex++];
|
|
12694
|
+
await task(item, workerId);
|
|
12695
|
+
}
|
|
12696
|
+
}));
|
|
12697
|
+
}
|
|
12698
|
+
|
|
12699
|
+
//#endregion
|
|
12700
|
+
//#region src/utils/source/formatSourceLocation.ts
|
|
12701
|
+
function formatSourceLocation(sourceFile, sourceLine) {
|
|
12702
|
+
return sourceLine ? `${sourceFile}:${sourceLine}` : sourceFile;
|
|
12703
|
+
}
|
|
12704
|
+
|
|
12645
12705
|
//#endregion
|
|
12646
12706
|
//#region ../../node_modules/@ai-sdk/provider/dist/index.js
|
|
12647
12707
|
var marker$2 = "vercel.ai.error";
|
|
@@ -27940,6 +28000,25 @@ var originalGenerateCallId6 = createIdGenerator({
|
|
|
27940
28000
|
});
|
|
27941
28001
|
var defaultDownload2 = createDownload();
|
|
27942
28002
|
|
|
28003
|
+
//#endregion
|
|
28004
|
+
//#region src/utils/collection/chunk.ts
|
|
28005
|
+
function chunk(items, size) {
|
|
28006
|
+
if (!Number.isInteger(size) || size <= 0) throw new Error("Chunk size must be a positive integer");
|
|
28007
|
+
return Array.from({ length: Math.ceil(items.length / size) }, (_, index) => items.slice(index * size, (index + 1) * size));
|
|
28008
|
+
}
|
|
28009
|
+
|
|
28010
|
+
//#endregion
|
|
28011
|
+
//#region src/utils/path/normalizeFieldPath.ts
|
|
28012
|
+
function normalizeFieldPath(value) {
|
|
28013
|
+
return (value || "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9.]/g, "").toLowerCase();
|
|
28014
|
+
}
|
|
28015
|
+
|
|
28016
|
+
//#endregion
|
|
28017
|
+
//#region src/utils/path/getTerminalField.ts
|
|
28018
|
+
function getTerminalField(value) {
|
|
28019
|
+
return normalizeFieldPath(value).split(".").filter(Boolean).at(-1) || "";
|
|
28020
|
+
}
|
|
28021
|
+
|
|
27943
28022
|
//#endregion
|
|
27944
28023
|
//#region ../../node_modules/@ai-sdk/openai-compatible/dist/index.js
|
|
27945
28024
|
function toCamelCase(str) {
|
|
@@ -29359,38 +29438,32 @@ const MAX_EXCERPT_CHARACTERS = 16e3;
|
|
|
29359
29438
|
const EXCERPT_RADIUS = 7;
|
|
29360
29439
|
const PROPERTIES_PER_BATCH = 8;
|
|
29361
29440
|
const MAX_CANDIDATES_PER_BATCH = 48;
|
|
29362
|
-
function normalizeFieldForRanking(field) {
|
|
29363
|
-
return (field || "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9.]/g, "").toLowerCase();
|
|
29364
|
-
}
|
|
29365
|
-
function terminalFieldForRanking(field) {
|
|
29366
|
-
return normalizeFieldForRanking(field).split(".").filter(Boolean).at(-1) || "";
|
|
29367
|
-
}
|
|
29368
29441
|
/**
|
|
29369
29442
|
* Guards against a model picking the right reasoning but the wrong array index: the chosen
|
|
29370
29443
|
* backend field must share a term with the API property, either as an exact terminal match
|
|
29371
29444
|
* or as the container of a nested object property (e.g. "address.number" <-> "address").
|
|
29372
29445
|
*/
|
|
29373
29446
|
function candidateSharesTermWithProperty(candidate, property) {
|
|
29374
|
-
const propertySegments =
|
|
29375
|
-
const domainSegments =
|
|
29447
|
+
const propertySegments = normalizeFieldPath(property.field).split(".").filter(Boolean);
|
|
29448
|
+
const domainSegments = normalizeFieldPath(property.domainField).split(".").filter(Boolean);
|
|
29376
29449
|
const propertyTerminal = propertySegments.at(-1) || "";
|
|
29377
29450
|
const propertyRoot = propertySegments[0] || "";
|
|
29378
|
-
const backendTerminal =
|
|
29379
|
-
const apiPathTerminal =
|
|
29451
|
+
const backendTerminal = getTerminalField(candidate.backendField);
|
|
29452
|
+
const apiPathTerminal = getTerminalField(candidate.apiPathCandidate);
|
|
29380
29453
|
if (backendTerminal && (backendTerminal === propertyTerminal || domainSegments.includes(backendTerminal))) return true;
|
|
29381
29454
|
if (apiPathTerminal && apiPathTerminal === propertyTerminal) return true;
|
|
29382
29455
|
return Boolean(propertySegments.length > 1 && backendTerminal && backendTerminal === propertyRoot);
|
|
29383
29456
|
}
|
|
29384
29457
|
function scopeCandidatesToProperties(document, properties) {
|
|
29385
29458
|
const candidates = document.backendFieldCandidates.filter((candidate) => !candidate.direction || properties.some((property) => property.direction === candidate.direction)).map((candidate, index) => {
|
|
29386
|
-
const apiPath =
|
|
29387
|
-
const backendTerminal =
|
|
29459
|
+
const apiPath = normalizeFieldPath(candidate.apiPathCandidate);
|
|
29460
|
+
const backendTerminal = getTerminalField(candidate.backendField);
|
|
29388
29461
|
return {
|
|
29389
29462
|
candidate,
|
|
29390
29463
|
index,
|
|
29391
29464
|
relevance: Math.max(...properties.map((property) => {
|
|
29392
|
-
const field =
|
|
29393
|
-
const terminal =
|
|
29465
|
+
const field = normalizeFieldPath(property.field);
|
|
29466
|
+
const terminal = getTerminalField(property.field);
|
|
29394
29467
|
if (apiPath && (apiPath.endsWith(field) || field.endsWith(apiPath))) return 1e3;
|
|
29395
29468
|
if (terminal && backendTerminal === terminal) return 100;
|
|
29396
29469
|
return 0;
|
|
@@ -29446,7 +29519,7 @@ async function buildPrompt(document, properties) {
|
|
|
29446
29519
|
direction: candidate.direction || null,
|
|
29447
29520
|
mapper_type: candidate.mapperType || null,
|
|
29448
29521
|
deterministic_review_reason: candidate.reviewReason || null,
|
|
29449
|
-
source_file: candidate.
|
|
29522
|
+
source_file: formatSourceLocation(candidate.sourceFile, candidate.sourceLine),
|
|
29450
29523
|
static_confidence: candidate.confidence
|
|
29451
29524
|
}));
|
|
29452
29525
|
const sourceExcerpts = await readSourceExcerpts(document);
|
|
@@ -29585,9 +29658,6 @@ function parseSuggestions(content, document, properties, minimumConfidence) {
|
|
|
29585
29658
|
}
|
|
29586
29659
|
};
|
|
29587
29660
|
}
|
|
29588
|
-
function chunkProperties(properties) {
|
|
29589
|
-
return Array.from({ length: Math.ceil(properties.length / PROPERTIES_PER_BATCH) }, (_, index) => properties.slice(index * PROPERTIES_PER_BATCH, (index + 1) * PROPERTIES_PER_BATCH));
|
|
29590
|
-
}
|
|
29591
29661
|
async function inferMissingMappings(document, properties, config) {
|
|
29592
29662
|
if (!config.enabled || !properties.length || !document.backendFieldCandidates.length) return {
|
|
29593
29663
|
suggestions: [],
|
|
@@ -29613,7 +29683,7 @@ async function inferMissingMappings(document, properties, config) {
|
|
|
29613
29683
|
apiKey: process.env.LITELLM_API_KEY
|
|
29614
29684
|
});
|
|
29615
29685
|
const results = [];
|
|
29616
|
-
for (const batch of
|
|
29686
|
+
for (const batch of chunk(properties, PROPERTIES_PER_BATCH)) {
|
|
29617
29687
|
const scopedDocument = scopeCandidatesToProperties(document, batch);
|
|
29618
29688
|
let content;
|
|
29619
29689
|
try {
|
|
@@ -29663,11 +29733,8 @@ async function inferMissingMappings(document, properties, config) {
|
|
|
29663
29733
|
|
|
29664
29734
|
//#endregion
|
|
29665
29735
|
//#region src/services/catalogueInferenceService.ts
|
|
29666
|
-
function stableKey(...parts) {
|
|
29667
|
-
return createHash("sha1").update(parts.join("::")).digest("hex");
|
|
29668
|
-
}
|
|
29669
29736
|
function candidateSource(candidate) {
|
|
29670
|
-
return candidate.
|
|
29737
|
+
return formatSourceLocation(candidate.sourceFile, candidate.sourceLine);
|
|
29671
29738
|
}
|
|
29672
29739
|
function findBackendRoute(document, suggestion) {
|
|
29673
29740
|
const candidates = document.backendRouteCandidates.filter((candidate) => candidate.backend === suggestion.candidate.backend);
|
|
@@ -29791,15 +29858,6 @@ function refreshStats(catalogue) {
|
|
|
29791
29858
|
function formatInferenceFailure(error) {
|
|
29792
29859
|
return (error instanceof Error ? error.message : String(error)).split(" Raw response:")[0];
|
|
29793
29860
|
}
|
|
29794
|
-
async function runWithConcurrency(items, workers, task) {
|
|
29795
|
-
let nextIndex = 0;
|
|
29796
|
-
await Promise.all(Array.from({ length: Math.min(Math.max(1, workers), items.length) }, async (_, workerId) => {
|
|
29797
|
-
while (nextIndex < items.length) {
|
|
29798
|
-
const item = items[nextIndex++];
|
|
29799
|
-
await task(item, workerId);
|
|
29800
|
-
}
|
|
29801
|
-
}));
|
|
29802
|
-
}
|
|
29803
29861
|
async function inferCatalogueDirectory(params) {
|
|
29804
29862
|
const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
|
|
29805
29863
|
const outputPath = path.resolve(params.cwd, params.outputDirectory || params.catalogueDirectory);
|
|
@@ -29808,7 +29866,8 @@ async function inferCatalogueDirectory(params) {
|
|
|
29808
29866
|
ensureNeedsReviewEvidence(catalogue);
|
|
29809
29867
|
const needsReviewBefore = [...catalogue.route_input_properties, ...catalogue.route_output_properties].filter((property) => property.evidence_status === "needs_review").length;
|
|
29810
29868
|
const needsReviewPercentageBefore = calculateNeedsReviewPercentage(catalogue.stats.input_properties, catalogue.stats.output_properties, needsReviewBefore);
|
|
29811
|
-
const
|
|
29869
|
+
const routeSelector = params.routeSelector;
|
|
29870
|
+
const selectedRoute = routeSelector ? catalogue.routes.find((route) => matchesRouteSelector(route, routeSelector)) : void 0;
|
|
29812
29871
|
if (params.routeSelector && !selectedRoute) throw new Error(`Route not found in catalogue artifacts: ${params.routeSelector.method} ${params.routeSelector.path}`);
|
|
29813
29872
|
const scopeProperties = [...catalogue.route_input_properties, ...catalogue.route_output_properties].filter((property) => !selectedRoute || property.route_key === selectedRoute.key);
|
|
29814
29873
|
const needsReviewInScopeBefore = scopeProperties.filter((property) => property.evidence_status === "needs_review").length;
|
|
@@ -29876,7 +29935,7 @@ async function inferCatalogueDirectory(params) {
|
|
|
29876
29935
|
duplicate: 0,
|
|
29877
29936
|
candidate_mismatch: 0
|
|
29878
29937
|
};
|
|
29879
|
-
await
|
|
29938
|
+
await forEachWithConcurrency(routeKeys, Math.min(params.inference.workers, 10), async (routeKey, workerId) => {
|
|
29880
29939
|
const route = catalogue.routes.find((item) => item.key === routeKey);
|
|
29881
29940
|
const reportRoute = (phase) => {
|
|
29882
29941
|
params.onWorkerProgress?.({
|
|
@@ -30029,6 +30088,14 @@ async function inferCatalogueDirectory(params) {
|
|
|
30029
30088
|
};
|
|
30030
30089
|
}
|
|
30031
30090
|
|
|
30091
|
+
//#endregion
|
|
30092
|
+
//#region src/utils/number/parsePositiveNumber.ts
|
|
30093
|
+
function parsePositiveNumber(value, message = "Expected a positive number") {
|
|
30094
|
+
const parsed = Number(value);
|
|
30095
|
+
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(message);
|
|
30096
|
+
return parsed;
|
|
30097
|
+
}
|
|
30098
|
+
|
|
30032
30099
|
//#endregion
|
|
30033
30100
|
//#region src/commands/infer.ts
|
|
30034
30101
|
function createInferenceStepClassifier() {
|
|
@@ -30056,11 +30123,6 @@ function createInferenceStepClassifier() {
|
|
|
30056
30123
|
};
|
|
30057
30124
|
};
|
|
30058
30125
|
}
|
|
30059
|
-
function parsePositiveNumber(value) {
|
|
30060
|
-
const parsed = Number(value);
|
|
30061
|
-
if (!Number.isFinite(parsed) || parsed <= 0) throw new InvalidArgumentError$2("Expected a positive number");
|
|
30062
|
-
return parsed;
|
|
30063
|
-
}
|
|
30064
30126
|
function parseConfidence(value) {
|
|
30065
30127
|
const parsed = parsePositiveNumber(value);
|
|
30066
30128
|
if (parsed > 100) throw new InvalidArgumentError$2("Confidence must be between 1 and 100");
|
|
@@ -30071,7 +30133,7 @@ function parseWorkerCount(value) {
|
|
|
30071
30133
|
if (workers > 10) throw new InvalidArgumentError$2("A maximum of 10 inference workers is supported");
|
|
30072
30134
|
return workers;
|
|
30073
30135
|
}
|
|
30074
|
-
var infer_default = (program) => void program.command("infer").argument("[route-or-directory]", "Route selector (GET /v1/offers) or catalogue directory").option("-i, --input <directory>", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").option("-o, --output <directory>", "Output directory; defaults to updating the input directory").option("--model <name>", "AI model name").option("--ai-url <url>", "AI base URL").option("--min-confidence <number>", "Minimum confidence required to accept an ai/sdk mapping", parseConfidence).option("--timeout <milliseconds>", "Maximum duration of one route-level ai/sdk request", parsePositiveNumber).option("-w, --workers <count>", "Maximum number of concurrent route inference jobs (max 10)", parseWorkerCount).description("Use AI to review unresolved mappings in generated route YAML documents").action(async (routeOrDirectory, options) => {
|
|
30136
|
+
var infer_default = (program) => void program.command("infer").argument("[route-or-directory]", "Route selector (GET /v1/offers) or catalogue directory").option("-i, --input <directory>", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").option("-o, --output <directory>", "Output directory; defaults to updating the input directory").option("--model <name>", "AI model name").option("--ai-url <url>", "AI base URL").option("--min-confidence <number>", "Minimum confidence required to accept an ai/sdk mapping", parseConfidence).option("--timeout <milliseconds>", "Maximum duration of one route-level ai/sdk request", (value) => parsePositiveNumber(value)).option("-w, --workers <count>", "Maximum number of concurrent route inference jobs (max 10)", parseWorkerCount).description("Use AI to review unresolved mappings in generated route YAML documents").action(async (routeOrDirectory, options) => {
|
|
30075
30137
|
intro("Datasource catalogue inference");
|
|
30076
30138
|
const config = getUserConfig();
|
|
30077
30139
|
const inference = config.inference;
|
|
@@ -30250,11 +30312,6 @@ async function buildNeedsReviewRouteReport(params) {
|
|
|
30250
30312
|
|
|
30251
30313
|
//#endregion
|
|
30252
30314
|
//#region src/commands/needsReview.ts
|
|
30253
|
-
function parsePositiveInteger(value) {
|
|
30254
|
-
const parsed = Number(value);
|
|
30255
|
-
if (!Number.isInteger(parsed) || parsed <= 0) throw new InvalidArgumentError$2("Expected a positive integer");
|
|
30256
|
-
return parsed;
|
|
30257
|
-
}
|
|
30258
30315
|
function formatNeedsReviewTable(rows) {
|
|
30259
30316
|
const headers = [
|
|
30260
30317
|
"#",
|
|
@@ -30272,7 +30329,7 @@ function formatNeedsReviewTable(rows) {
|
|
|
30272
30329
|
String(row.outputNeedsReview),
|
|
30273
30330
|
String(row.inputProperties + row.outputProperties),
|
|
30274
30331
|
String(row.needsReview),
|
|
30275
|
-
|
|
30332
|
+
formatPercentage(row.needsReviewPercentage)
|
|
30276
30333
|
]);
|
|
30277
30334
|
const widths = headers.map((header, column) => Math.max(header.length, ...values.map((row) => row[column].length)));
|
|
30278
30335
|
const formatRow = (row) => row.map((cell, column) => column < 2 ? cell.padEnd(widths[column]) : cell.padStart(widths[column])).join(" | ");
|
|
@@ -30282,7 +30339,7 @@ function formatNeedsReviewTable(rows) {
|
|
|
30282
30339
|
...values.map(formatRow)
|
|
30283
30340
|
].join("\n");
|
|
30284
30341
|
}
|
|
30285
|
-
var needsReview_default = (program) => void program.command("needs-review").argument("[catalogue-directory]", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").addOption(new Option("--sort <metric>", "Sort candidates by review count or percentage").choices(["count", "percentage"]).default("count")).option("--limit <number>", "Maximum number of candidate routes to display", parsePositiveInteger).description("Display needs_review metrics grouped by route").action(async (catalogueDirectory, options) => {
|
|
30342
|
+
var needsReview_default = (program) => void program.command("needs-review").argument("[catalogue-directory]", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").addOption(new Option("--sort <metric>", "Sort candidates by review count or percentage").choices(["count", "percentage"]).default("count")).option("--limit <number>", "Maximum number of candidate routes to display", (value) => parsePositiveInteger(value)).description("Display needs_review metrics grouped by route").action(async (catalogueDirectory, options) => {
|
|
30286
30343
|
intro("Datasource catalogue needs review report");
|
|
30287
30344
|
const { cwd } = getUserConfig();
|
|
30288
30345
|
const progress = taskProgressService.createStepProgress();
|
|
@@ -30307,7 +30364,7 @@ var needsReview_default = (program) => void program.command("needs-review").argu
|
|
|
30307
30364
|
`Candidate routes: ${report.candidateRoutes}/${report.totalRoutes}`,
|
|
30308
30365
|
`Properties: ${report.totalProperties}`,
|
|
30309
30366
|
`Needs review: ${report.needsReview}`,
|
|
30310
|
-
`Needs review percentage: ${report.needsReviewPercentage
|
|
30367
|
+
`Needs review percentage: ${formatPercentage(report.needsReviewPercentage)}`,
|
|
30311
30368
|
options.limit ? `Rows displayed: ${report.rows.length}/${report.candidateRoutes}` : null
|
|
30312
30369
|
].filter(Boolean).join("\n"), "Summary");
|
|
30313
30370
|
outro("Datasource catalogue needs review report completed");
|
|
@@ -30326,9 +30383,6 @@ function sortPropertiesByStatus(properties) {
|
|
|
30326
30383
|
return rank(left.status) - rank(right.status) || left.field.localeCompare(right.field);
|
|
30327
30384
|
});
|
|
30328
30385
|
}
|
|
30329
|
-
function normalizeFilePath(filePath) {
|
|
30330
|
-
return filePath.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
30331
|
-
}
|
|
30332
30386
|
function isGraphDocument(value) {
|
|
30333
30387
|
if (typeof value !== "object" || value === null) return false;
|
|
30334
30388
|
const document = value;
|
|
@@ -30338,9 +30392,6 @@ function collectImpactedRoutes(params) {
|
|
|
30338
30392
|
const changedFiles = new Set(params.changedFiles.map(normalizeFilePath));
|
|
30339
30393
|
return params.graphs.filter((graph) => graph.analysisFiles.some((analysisFile) => changedFiles.has(normalizeFilePath(analysisFile)))).sort((left, right) => `${left.method} ${left.path}`.localeCompare(`${right.method} ${right.path}`));
|
|
30340
30394
|
}
|
|
30341
|
-
async function readChangedFiles(filePath) {
|
|
30342
|
-
return (await fsPromises.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
|
|
30343
|
-
}
|
|
30344
30395
|
async function readRouteGraphs(outputDirectory) {
|
|
30345
30396
|
const graphFiles = await globby("**/*.graph.yaml", {
|
|
30346
30397
|
cwd: outputDirectory,
|
|
@@ -30360,7 +30411,7 @@ async function readRouteGraphs(outputDirectory) {
|
|
|
30360
30411
|
}
|
|
30361
30412
|
function renderChangedRouteReport(params) {
|
|
30362
30413
|
const changedFiles = params.changedFiles.sort();
|
|
30363
|
-
const routeRows = params.routes.length ? params.routes.map((route) => `| ${route.method} ${route.path} | ${route.coverage
|
|
30414
|
+
const routeRows = params.routes.length ? params.routes.map((route) => `| ${route.method} ${route.path} | ${formatPercentage(route.coverage)} | ${route.needsReview}/${route.fields} |`).join("\n") : "| _Aucune route impactée_ | — | — |";
|
|
30364
30415
|
return [
|
|
30365
30416
|
"<!-- datasource-catalogue-report -->",
|
|
30366
30417
|
"## Datasource mapping report",
|
|
@@ -30415,6 +30466,12 @@ function renderChangedRouteReport(params) {
|
|
|
30415
30466
|
].join("\n");
|
|
30416
30467
|
}
|
|
30417
30468
|
|
|
30469
|
+
//#endregion
|
|
30470
|
+
//#region src/utils/fs/readChangedFiles.ts
|
|
30471
|
+
async function readChangedFiles(filePath) {
|
|
30472
|
+
return (await fsPromises.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
|
|
30473
|
+
}
|
|
30474
|
+
|
|
30418
30475
|
//#endregion
|
|
30419
30476
|
//#region src/commands/reportChanged.ts
|
|
30420
30477
|
async function reportChangedHandler(cwd, options) {
|
|
@@ -30432,7 +30489,7 @@ async function reportChangedHandler(cwd, options) {
|
|
|
30432
30489
|
const documents = await analyzeCodebaseRouteContracts({
|
|
30433
30490
|
cwd,
|
|
30434
30491
|
openApiDocument,
|
|
30435
|
-
selectedRouteKeys: /* @__PURE__ */ new Set([
|
|
30492
|
+
selectedRouteKeys: /* @__PURE__ */ new Set([stableKey(route.method, route.path)]),
|
|
30436
30493
|
routeAnalysisScope: ({ method, path: routePath }) => loadBackendTopologyAnalysisScope(outputDirectory, cwd, method, routePath)
|
|
30437
30494
|
});
|
|
30438
30495
|
if (documents.length !== 1) throw new Error("Route was not found in the OpenAPI contract");
|