@cmflow/atlas 3.4.0-beta.7 → 3.4.0-beta.9
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/README.md +168 -4
- package/dist/bin/atlas.mjs +179 -70
- package/dist/defineExpressionRule-Dfvzj6n2.mjs.map +1 -1
- package/dist/index.d.mts +32 -2
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{routeBackendTopologyService-D_aIIBbX.mjs → routeBackendTopologyService-DElirHSh.mjs} +44 -16
- package/dist/rules/cleanObjectRule.d.mts +1 -1
- package/dist/rules/cmsI18nFieldRule.d.mts +1 -1
- package/dist/rules/dateConversionRule.d.mts +1 -1
- package/dist/rules/lodashGetRule.d.mts +1 -1
- package/dist/rules/mappingUtilityRule.d.mts +1 -1
- package/dist/rules/mappingUtilityRule.mjs.map +1 -1
- package/dist/rules/memberGetFieldRule.d.mts +1 -1
- package/dist/rules/memberGetFieldRule.mjs +1 -1
- package/dist/rules/memberGetFieldRule.mjs.map +1 -1
- package/dist/rules/quableI18nFieldRule.d.mts +1 -1
- package/dist/{types-3y34Gf8R.d.mts → types-cYprdLUO.d.mts} +20 -3
- package/dist/workers/routeBackendTopologyWorker.mjs +1 -1
- package/knowledges/cms-and-directus-indirect-routes.md +5 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -59,11 +59,175 @@ export default defineConfig({
|
|
|
59
59
|
});
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Atlas reads `compilerOptions.paths` from the
|
|
62
|
+
Atlas reads `compilerOptions.paths` from the `tsconfig.json` in the directory where the command is run (`process.cwd()`) and uses them as module aliases. `repoRoot` only defines the source tree to analyze and may therefore point to a subdirectory. Add `resolver.alias` only to override or complement those aliases.
|
|
63
|
+
|
|
64
|
+
## Backend sources
|
|
65
|
+
|
|
66
|
+
Atlas extracts a candidate mapping from code, then optionally consolidates it against a strict backend field reference. Configure these references in the same `analysis.backends` list used to declare backends.
|
|
67
|
+
|
|
68
|
+
A backend can be either a string or a `BackendSource` created with `defineBackendSource`:
|
|
69
|
+
|
|
70
|
+
- a string (for example `"ICC"`) has no field reference; Atlas keeps the current code-analysis and optional inference flow;
|
|
71
|
+
- a `BackendSource` provides a `resolve` function that returns the backend's known properties. Atlas assigns its `name` as `backend` on every resolved property.
|
|
72
|
+
|
|
73
|
+
When a string and a source share the same name, the last declaration wins. This lets a source enrich a list of backend names, for example `[..., Object.values(BackendTypes), quableBackendSource]`.
|
|
74
|
+
|
|
75
|
+
The resolver returns one of the following shapes. Document-oriented backends have no route; REST backends have no document.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
type BackendProperty =
|
|
79
|
+
| {
|
|
80
|
+
document: string;
|
|
81
|
+
field: string;
|
|
82
|
+
description?: string;
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
route: string;
|
|
86
|
+
method: string;
|
|
87
|
+
field: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The generated mapping preserves this shape. In particular, `document` is a separate property: Atlas does not turn a Quable field into an artificial path such as `products.product_geographical_area`.
|
|
93
|
+
|
|
94
|
+
### Quable
|
|
95
|
+
|
|
96
|
+
Attach rules that are specific to Quable directly to its source. They inherit the source backend automatically and their candidates are validated by the Quable property list.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { defineBackendSource, httpClient, quableI18nFieldRule } from "@cmflow/atlas";
|
|
100
|
+
|
|
101
|
+
export const quable = defineBackendSource({
|
|
102
|
+
name: "QUABLE",
|
|
103
|
+
rules: [quableI18nFieldRule],
|
|
104
|
+
resolve: async () => {
|
|
105
|
+
const documentTypes = await httpClient.get<QuableDocumentType[]>("https://quable.example/document-types");
|
|
106
|
+
|
|
107
|
+
return documentTypes.flatMap((document) =>
|
|
108
|
+
document.properties.map((property) => ({
|
|
109
|
+
document: document.code,
|
|
110
|
+
field: property.code,
|
|
111
|
+
description: "récupéré via le backend resolver"
|
|
112
|
+
}))
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
For example, the resolver above may return:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
{
|
|
122
|
+
backend: "QUABLE",
|
|
123
|
+
document: "products",
|
|
124
|
+
field: "product_geographical_area",
|
|
125
|
+
description: "récupéré via le backend resolver"
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### CMS Directus
|
|
130
|
+
|
|
131
|
+
CMS Directus follows the same document-oriented contract. Its `cmsI18nFieldRule` is owned by the CMS source rather than registered as a global expression rule.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { defineBackendSource, cmsI18nFieldRule } from "@cmflow/atlas";
|
|
135
|
+
|
|
136
|
+
export const cmsDirectus = defineBackendSource({
|
|
137
|
+
name: "CMS",
|
|
138
|
+
rules: [cmsI18nFieldRule],
|
|
139
|
+
resolve: async () => directusResolver.listProperties()
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`directusResolver.listProperties()` returns items such as `{ document: "offers", field: "title", description: "…" }`.
|
|
144
|
+
|
|
145
|
+
### CMS Legacy
|
|
146
|
+
|
|
147
|
+
CMS Legacy is configured identically, with its own resolver and backend identifier. It may reuse `cmsI18nFieldRule` when the backend uses `CmsI18n` helpers.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { defineBackendSource, cmsI18nFieldRule } from "@cmflow/atlas";
|
|
151
|
+
|
|
152
|
+
export const cmsLegacy = defineBackendSource({
|
|
153
|
+
name: "CMS_LEGACY",
|
|
154
|
+
rules: [cmsI18nFieldRule],
|
|
155
|
+
resolve: async () => cmsLegacyResolver.listProperties()
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Custom REST backend with OpenAPI
|
|
160
|
+
|
|
161
|
+
`openapiSource` reuses Atlas's OpenAPI parsing utilities: downloading the document, resolving references, traversing operations, and extracting field descriptions. A project therefore only supplies the URL.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { constant, defineBackendSource, openapiSource } from "@cmflow/atlas";
|
|
165
|
+
|
|
166
|
+
export const xm = defineBackendSource({
|
|
167
|
+
name: "XM",
|
|
168
|
+
resolve: () =>
|
|
169
|
+
openapiSource({
|
|
170
|
+
url: constant<string>("XM_API_URL", "https://xm.example/openapi.json")
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`constant` resolves an environment variable directly from `process.env`, falling back to its second argument. No resolver context is required.
|
|
176
|
+
|
|
177
|
+
### Environment constants
|
|
178
|
+
|
|
179
|
+
Use `constant` when a backend source needs a configurable value such as a URL or an access token. The value is resolved when the source runs:
|
|
180
|
+
|
|
181
|
+
1. Atlas uses `process.env[name]` when it is set.
|
|
182
|
+
2. Otherwise, it returns the supplied default value.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { constant, defineBackendSource, openapiSource } from "@cmflow/atlas";
|
|
186
|
+
|
|
187
|
+
export const xmBackendSource = defineBackendSource({
|
|
188
|
+
name: "XM",
|
|
189
|
+
resolve: () =>
|
|
190
|
+
openapiSource({
|
|
191
|
+
url: constant<string>("XM_API_URL", "https://xm.example/openapi.json")
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
For example, override the default in CI or locally:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
XM_API_URL=https://xm.internal/openapi.json atlas backend-sources --backend XM
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This returns entries such as:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
{
|
|
206
|
+
backend: "XM",
|
|
207
|
+
route: "/path/to",
|
|
208
|
+
method: "GET",
|
|
209
|
+
field: "path.to.field",
|
|
210
|
+
description: "Description extraite du Swagger"
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Register strings and sources together:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { defineConfig } from "@cmflow/atlas";
|
|
218
|
+
import { cmsDirectus, cmsLegacy, quable, xm } from "./backend-sources";
|
|
219
|
+
|
|
220
|
+
export default defineConfig({
|
|
221
|
+
// …other Atlas options
|
|
222
|
+
analysis: {
|
|
223
|
+
backends: ["ICC", quable, cmsDirectus, cmsLegacy, xm]
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
```
|
|
63
227
|
|
|
64
228
|
## Custom expression rules
|
|
65
229
|
|
|
66
|
-
Use `defineExpressionRule` when a project-specific helper hides a backend field or wraps an expression that Atlas should follow. Add
|
|
230
|
+
Use `defineExpressionRule` when a project-specific helper hides a backend field or wraps an expression that Atlas should follow. Add a cross-backend rule to `analysis.rules` in `atlas.config.ts`. Attach a backend-specific rule, such as `quableI18nFieldRule` or `cmsI18nFieldRule`, to `defineBackendSource({ rules: [...] })` instead.
|
|
67
231
|
|
|
68
232
|
`match` is a cheap predicate that selects the `ts-morph` expression. `parse` returns the information Atlas should use: a backend field, a transparent wrapper, or both.
|
|
69
233
|
|
|
@@ -73,8 +237,7 @@ import { Node } from "ts-morph";
|
|
|
73
237
|
|
|
74
238
|
const localizedFieldRule = defineExpressionRule({
|
|
75
239
|
name: "localized-field",
|
|
76
|
-
match: (expression) =>
|
|
77
|
-
Node.isCallExpression(expression) && expression.getExpression().getText() === "localizedField",
|
|
240
|
+
match: (expression) => Node.isCallExpression(expression) && expression.getExpression().getText() === "localizedField",
|
|
78
241
|
parse: (expression) => {
|
|
79
242
|
if (!Node.isCallExpression(expression)) return undefined;
|
|
80
243
|
|
|
@@ -151,6 +314,7 @@ Without `--write`, the command performs a dry run. Use `clean-orphans` to inspec
|
|
|
151
314
|
|
|
152
315
|
```text
|
|
153
316
|
atlas init Create atlas.config.ts
|
|
317
|
+
atlas backend-sources Execute backend sources and display resolved metadata
|
|
154
318
|
atlas generate:graph Trace API routes to backends
|
|
155
319
|
atlas generate:catalog [route] Generate route review documents
|
|
156
320
|
atlas generate:test Check configured coverage baselines
|
package/dist/bin/atlas.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as isKnownBackendType, c as taskProgressService, d as setUserConfig, i as inferBackendNameFromFile, l as getUserConfig, n as generateBackendTopologyArtifactsInWorkers, o as filterAnalysisFiles, r as resolveModulePath, s as shouldKeepAnalysisFile, u as loadAtlasConfig } from "../routeBackendTopologyService-
|
|
2
|
+
import { a as isKnownBackendType, c as taskProgressService, d as setUserConfig, i as inferBackendNameFromFile, l as getUserConfig, n as generateBackendTopologyArtifactsInWorkers, o as filterAnalysisFiles, r as resolveModulePath, s as shouldKeepAnalysisFile, u as loadAtlasConfig } from "../routeBackendTopologyService-DElirHSh.mjs";
|
|
3
3
|
import { Command, InvalidArgumentError, Option } from "commander";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
6
|
-
import { cancel, intro, log, note, outro, progress } from "@clack/prompts";
|
|
6
|
+
import { cancel, intro, isCancel, log, note, outro, progress, select } from "@clack/prompts";
|
|
7
7
|
import fs from "node:fs/promises";
|
|
8
8
|
import { createDirectus, createItem, deleteItem, readItems, rest, staticToken, updateItem } from "@directus/sdk";
|
|
9
9
|
import { globby } from "globby";
|
|
@@ -48,16 +48,16 @@ function getAggregationTarget(call) {
|
|
|
48
48
|
let current = call;
|
|
49
49
|
while (current) {
|
|
50
50
|
const parent = current.getParent();
|
|
51
|
-
if (!parent) return
|
|
51
|
+
if (!parent) return;
|
|
52
52
|
if (Node.isVariableDeclaration(parent)) return normalizeCodeText(parent.getName());
|
|
53
53
|
if (Node.isReturnStatement(parent)) return "return";
|
|
54
|
-
if (Node.isStatement(parent)) return
|
|
54
|
+
if (Node.isStatement(parent)) return;
|
|
55
55
|
current = parent;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
function getAggregationCondition(call) {
|
|
59
59
|
const callback = call.getArguments().find((argument) => Node.isArrowFunction(argument) || Node.isFunctionExpression(argument));
|
|
60
|
-
if (!callback || !Node.isArrowFunction(callback) && !Node.isFunctionExpression(callback)) return
|
|
60
|
+
if (!callback || !Node.isArrowFunction(callback) && !Node.isFunctionExpression(callback)) return;
|
|
61
61
|
const body = callback.getBody();
|
|
62
62
|
if (!Node.isBlock(body)) return normalizeCodeText(body.getText());
|
|
63
63
|
const condition = body.getDescendantsOfKind(SyntaxKind.IfStatement)[0]?.getExpression();
|
|
@@ -135,29 +135,80 @@ var AnalysisCacheService = class {
|
|
|
135
135
|
};
|
|
136
136
|
const analysisCacheService = new AnalysisCacheService();
|
|
137
137
|
//#endregion
|
|
138
|
-
//#region src/services/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
//#region src/services/backendPropertyService.ts
|
|
139
|
+
const propertiesBySource = /* @__PURE__ */ new WeakMap();
|
|
140
|
+
async function resolveBackendProperties(sources) {
|
|
141
|
+
const resolved = await Promise.all(sources.map(async (source) => {
|
|
142
|
+
if (!source.resolve) return [source.name, void 0];
|
|
143
|
+
let properties = propertiesBySource.get(source);
|
|
144
|
+
if (!properties) {
|
|
145
|
+
properties = source.resolve().then((result) => result.map((property) => ({
|
|
146
|
+
...property,
|
|
147
|
+
backend: source.name
|
|
148
|
+
})));
|
|
149
|
+
propertiesBySource.set(source, properties);
|
|
150
|
+
}
|
|
151
|
+
return [source.name, await properties];
|
|
152
|
+
}));
|
|
153
|
+
return new Map(resolved.filter((entry) => entry[1] !== void 0).map(([backend, properties]) => [backend, properties]));
|
|
154
|
+
}
|
|
155
|
+
function matchBackendProperty(field, properties) {
|
|
156
|
+
if (!properties) return;
|
|
157
|
+
const normalized = normalizeField(field);
|
|
158
|
+
return properties.find((property) => {
|
|
159
|
+
const reference = normalizeField(property.field);
|
|
160
|
+
return normalized === reference || normalized.endsWith(reference) || reference.endsWith(normalized);
|
|
143
161
|
});
|
|
144
|
-
if (!response.ok) throw new Error(`Unable to fetch OpenAPI document from ${url}: ${response.status} ${response.statusText}`);
|
|
145
|
-
const reader = response.body?.getReader();
|
|
146
|
-
if (!reader) return response.json();
|
|
147
|
-
const decoder = new TextDecoder();
|
|
148
|
-
let content = "";
|
|
149
|
-
while (true) {
|
|
150
|
-
const { done, value } = await reader.read();
|
|
151
|
-
if (done) break;
|
|
152
|
-
content += decoder.decode(value, { stream: true });
|
|
153
|
-
taskProgressService.log(`Downloading (${(content.length / 1048576).toFixed(1)} MB)`);
|
|
154
|
-
}
|
|
155
|
-
taskProgressService.log("Parsing document");
|
|
156
|
-
return JSON.parse(content + decoder.decode());
|
|
157
162
|
}
|
|
163
|
+
function normalizeField(value) {
|
|
164
|
+
return value.replace(/\[locale\]/g, "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/services/http/httpClient.ts
|
|
168
|
+
var HttpClient = class {
|
|
169
|
+
async fetch(url, init) {
|
|
170
|
+
const headers = {
|
|
171
|
+
accept: "application/json",
|
|
172
|
+
...init?.headers
|
|
173
|
+
};
|
|
174
|
+
const response = await fetch(url, {
|
|
175
|
+
...init,
|
|
176
|
+
headers
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) throw new Error(`Unable to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
179
|
+
if (init?.onProgress) {
|
|
180
|
+
const reader = response.body?.getReader();
|
|
181
|
+
if (!reader) return response.json();
|
|
182
|
+
const decoder = new TextDecoder();
|
|
183
|
+
let content = "";
|
|
184
|
+
while (true) {
|
|
185
|
+
const { done, value } = await reader.read();
|
|
186
|
+
if (done) break;
|
|
187
|
+
content += decoder.decode(value, { stream: true });
|
|
188
|
+
init.onProgress(content);
|
|
189
|
+
}
|
|
190
|
+
return content + decoder.decode();
|
|
191
|
+
}
|
|
192
|
+
return response;
|
|
193
|
+
}
|
|
194
|
+
async get(url, init) {
|
|
195
|
+
return (await this.fetch(url, init)).json();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const httpClient = new HttpClient();
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/services/openapi/loadOpenApiDocument.ts
|
|
158
201
|
async function loadOpenApiDocument(url, timeoutMs) {
|
|
159
202
|
try {
|
|
160
|
-
|
|
203
|
+
const content = await httpClient.fetch(url, {
|
|
204
|
+
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : void 0,
|
|
205
|
+
onProgress(content) {
|
|
206
|
+
taskProgressService.log(`Downloading (${(content.length / 1048576).toFixed(1)} MB)`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
if (!content || typeof content === "object") return content;
|
|
210
|
+
taskProgressService.log("Parsing document");
|
|
211
|
+
return JSON.parse(content);
|
|
161
212
|
} catch (error) {
|
|
162
213
|
if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) throw new Error(`OpenAPI download timed out after ${timeoutMs}ms: ${url}`);
|
|
163
214
|
throw new Error(`Unable to fetch OpenAPI document from ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1188,6 +1239,7 @@ async function collectDependencyFiles(project, entryFilePath, routeLabel, resolv
|
|
|
1188
1239
|
}
|
|
1189
1240
|
async function analyzeCodebaseRouteContracts(params) {
|
|
1190
1241
|
const { cwd, openApiDocument: swagger, selectedRouteKeys, routeContracts, onDocument, routeAnalysisScope } = params;
|
|
1242
|
+
const backendProperties = await resolveBackendProperties(userConfig.analysis.backends);
|
|
1191
1243
|
taskProgressService.log("Discovering route files");
|
|
1192
1244
|
const routeFiles = await globby(["app/_api/**/routes.@(js|ts)", "app/legacy/**/routes.@(js|ts)"], {
|
|
1193
1245
|
cwd,
|
|
@@ -1277,7 +1329,15 @@ async function analyzeCodebaseRouteContracts(params) {
|
|
|
1277
1329
|
}), (item) => `${item.backend}:${item.method}:${item.route}:${item.sourceFile}`);
|
|
1278
1330
|
const backendGraphKey = `${backendSourceFiles.map((file) => file.getFilePath()).sort().join("|")}::${[...reachableBackendFunctionNames].sort().join("|")}`;
|
|
1279
1331
|
const backendFieldCandidates = backendFieldsByGraph.get(backendGraphKey) || (() => {
|
|
1280
|
-
const candidates = extractRouteBackendFieldCandidates(backendSourceFiles, reachableBackendFunctionNames, [...apiSourceFiles, ...useCaseSourceFiles])
|
|
1332
|
+
const candidates = extractRouteBackendFieldCandidates(backendSourceFiles, reachableBackendFunctionNames, [...apiSourceFiles, ...useCaseSourceFiles]).flatMap((candidate) => {
|
|
1333
|
+
const properties = backendProperties.get(candidate.backend);
|
|
1334
|
+
if (!properties) return [candidate];
|
|
1335
|
+
const resolvedProperty = matchBackendProperty(candidate.backendField, properties);
|
|
1336
|
+
return resolvedProperty ? [{
|
|
1337
|
+
...candidate,
|
|
1338
|
+
resolvedProperty
|
|
1339
|
+
}] : [];
|
|
1340
|
+
});
|
|
1281
1341
|
backendFieldsByGraph.set(backendGraphKey, candidates);
|
|
1282
1342
|
return candidates;
|
|
1283
1343
|
})();
|
|
@@ -1426,6 +1486,31 @@ function resolveBackendRouteCandidate(document, match) {
|
|
|
1426
1486
|
const sameBackendCandidates = document.backendRouteCandidates.filter((candidate) => candidate.backend === match.backend);
|
|
1427
1487
|
return sameBackendCandidates.find((candidate) => candidate.sourceFile === match.sourceFile) || sameBackendCandidates[0];
|
|
1428
1488
|
}
|
|
1489
|
+
function toBackendMapping(document, match) {
|
|
1490
|
+
const resolved = match.resolvedProperty;
|
|
1491
|
+
if (resolved && "document" in resolved) return {
|
|
1492
|
+
backend: match.backend,
|
|
1493
|
+
document: resolved.document,
|
|
1494
|
+
field: resolved.field,
|
|
1495
|
+
description: resolved.description,
|
|
1496
|
+
source_file: backendFieldSource(match),
|
|
1497
|
+
confidence: match.confidence,
|
|
1498
|
+
mapper_type: match.mapperType,
|
|
1499
|
+
reason: match.reviewReason
|
|
1500
|
+
};
|
|
1501
|
+
const backendRouteCandidate = resolved && "route" in resolved ? resolved : resolveBackendRouteCandidate(document, match);
|
|
1502
|
+
return {
|
|
1503
|
+
backend: match.backend,
|
|
1504
|
+
method: backendRouteCandidate?.method || null,
|
|
1505
|
+
route: backendRouteCandidate?.route || "unknown",
|
|
1506
|
+
field: resolved?.field || match.backendField,
|
|
1507
|
+
description: resolved?.description,
|
|
1508
|
+
source_file: backendFieldSource(match),
|
|
1509
|
+
confidence: match.confidence,
|
|
1510
|
+
mapper_type: match.mapperType,
|
|
1511
|
+
reason: match.reviewReason
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1429
1514
|
async function buildCatalogue(documents) {
|
|
1430
1515
|
const routes = documents.map((document) => ({
|
|
1431
1516
|
key: document.key,
|
|
@@ -1467,19 +1552,7 @@ async function buildCatalogue(documents) {
|
|
|
1467
1552
|
const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
|
|
1468
1553
|
const isTransverseWithoutMapping = isTransversalInput(property) && !matches.length;
|
|
1469
1554
|
const evidenceStatus = isTransverseWithoutMapping ? "confirmed" : evidenceStatusFromCandidates(matches);
|
|
1470
|
-
const backendMappings = dedupeByKey(matches.map((match) => {
|
|
1471
|
-
const backendRouteCandidate = resolveBackendRouteCandidate(document, match);
|
|
1472
|
-
return {
|
|
1473
|
-
backend: match.backend,
|
|
1474
|
-
method: backendRouteCandidate?.method || null,
|
|
1475
|
-
route: backendRouteCandidate?.route || "unknown",
|
|
1476
|
-
field: match.backendField,
|
|
1477
|
-
source_file: backendFieldSource(match),
|
|
1478
|
-
confidence: match.confidence,
|
|
1479
|
-
mapper_type: match.mapperType,
|
|
1480
|
-
reason: match.reviewReason
|
|
1481
|
-
};
|
|
1482
|
-
}), (mapping) => `${mapping.backend}:${mapping.method || "CALL"}:${mapping.route}:${mapping.field}:${mapping.source_file}`);
|
|
1555
|
+
const backendMappings = dedupeByKey(matches.map((match) => toBackendMapping(document, match)), (mapping) => `${mapping.backend}:${mapping.document || ""}:${mapping.method || "CALL"}:${mapping.route || ""}:${mapping.field}:${mapping.source_file}`);
|
|
1483
1556
|
routeInputProperties.push({
|
|
1484
1557
|
key: apiPropertyKey,
|
|
1485
1558
|
route_key: document.key,
|
|
@@ -1510,12 +1583,14 @@ async function buildCatalogue(documents) {
|
|
|
1510
1583
|
route: "unknown",
|
|
1511
1584
|
sourceFile: match.sourceFile
|
|
1512
1585
|
}).key;
|
|
1513
|
-
const backendPropertyKey = stableKey$2(backendRouteKey, "input", match.backendField);
|
|
1586
|
+
const backendPropertyKey = stableKey$2(backendRouteKey, "input", match.resolvedProperty?.field || match.backendField);
|
|
1514
1587
|
backendPropertiesByKey.set(backendPropertyKey, {
|
|
1515
1588
|
key: backendPropertyKey,
|
|
1516
1589
|
backend_route_key: backendRouteKey,
|
|
1517
1590
|
backend: match.backend,
|
|
1518
|
-
field: match.backendField,
|
|
1591
|
+
field: match.resolvedProperty?.field || match.backendField,
|
|
1592
|
+
document: match.resolvedProperty && "document" in match.resolvedProperty ? match.resolvedProperty.document : void 0,
|
|
1593
|
+
description: match.resolvedProperty?.description,
|
|
1519
1594
|
direction: "input",
|
|
1520
1595
|
source_file: backendFieldSource(match),
|
|
1521
1596
|
provenance: "code_analysis"
|
|
@@ -1539,19 +1614,7 @@ async function buildCatalogue(documents) {
|
|
|
1539
1614
|
const apiPropertyKey = stableKey$2(document.key, "output", property.path);
|
|
1540
1615
|
const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
|
|
1541
1616
|
const evidenceStatus = evidenceStatusFromCandidates(matches);
|
|
1542
|
-
const backendMappings = dedupeByKey(matches.map((match) => {
|
|
1543
|
-
const backendRouteCandidate = resolveBackendRouteCandidate(document, match);
|
|
1544
|
-
return {
|
|
1545
|
-
backend: match.backend,
|
|
1546
|
-
method: backendRouteCandidate?.method || null,
|
|
1547
|
-
route: backendRouteCandidate?.route || "unknown",
|
|
1548
|
-
field: match.backendField,
|
|
1549
|
-
source_file: backendFieldSource(match),
|
|
1550
|
-
confidence: match.confidence,
|
|
1551
|
-
mapper_type: match.mapperType,
|
|
1552
|
-
reason: match.reviewReason
|
|
1553
|
-
};
|
|
1554
|
-
}), (mapping) => `${mapping.backend}:${mapping.method || "CALL"}:${mapping.route}:${mapping.field}:${mapping.source_file}`);
|
|
1617
|
+
const backendMappings = dedupeByKey(matches.map((match) => toBackendMapping(document, match)), (mapping) => `${mapping.backend}:${mapping.document || ""}:${mapping.method || "CALL"}:${mapping.route || ""}:${mapping.field}:${mapping.source_file}`);
|
|
1555
1618
|
routeOutputProperties.push({
|
|
1556
1619
|
key: apiPropertyKey,
|
|
1557
1620
|
route_key: document.key,
|
|
@@ -1582,12 +1645,14 @@ async function buildCatalogue(documents) {
|
|
|
1582
1645
|
route: "unknown",
|
|
1583
1646
|
sourceFile: match.sourceFile
|
|
1584
1647
|
}).key;
|
|
1585
|
-
const backendPropertyKey = stableKey$2(backendRouteKey, "output", match.backendField);
|
|
1648
|
+
const backendPropertyKey = stableKey$2(backendRouteKey, "output", match.resolvedProperty?.field || match.backendField);
|
|
1586
1649
|
backendPropertiesByKey.set(backendPropertyKey, {
|
|
1587
1650
|
key: backendPropertyKey,
|
|
1588
1651
|
backend_route_key: backendRouteKey,
|
|
1589
1652
|
backend: match.backend,
|
|
1590
|
-
field: match.backendField,
|
|
1653
|
+
field: match.resolvedProperty?.field || match.backendField,
|
|
1654
|
+
document: match.resolvedProperty && "document" in match.resolvedProperty ? match.resolvedProperty.document : void 0,
|
|
1655
|
+
description: match.resolvedProperty?.description,
|
|
1591
1656
|
direction: "output",
|
|
1592
1657
|
source_file: backendFieldSource(match),
|
|
1593
1658
|
provenance: "code_analysis"
|
|
@@ -1631,7 +1696,7 @@ async function buildCatalogue(documents) {
|
|
|
1631
1696
|
};
|
|
1632
1697
|
}
|
|
1633
1698
|
//#endregion
|
|
1634
|
-
//#region src/services/directusSyncService.ts
|
|
1699
|
+
//#region src/services/directus/directusSyncService.ts
|
|
1635
1700
|
const ROUTE_STATUSES = { published: "published" };
|
|
1636
1701
|
const LOCAL_SOURCE_FILE = "digital-api:tools/datasource-catalogue";
|
|
1637
1702
|
const DIRECTUS_RETRY_MAX_ATTEMPTS = 6;
|
|
@@ -2167,10 +2232,10 @@ async function pushCatalogueToDirectus(catalogue, options) {
|
|
|
2167
2232
|
const removedOrphanedLinks = await cleanupOrphanedPropertyLinks(client);
|
|
2168
2233
|
taskProgressService.report("Finalizing Directus synchronization");
|
|
2169
2234
|
if (warnings.size) {
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
for (const warning of warnings)
|
|
2173
|
-
|
|
2235
|
+
taskProgressService.log("========================================");
|
|
2236
|
+
taskProgressService.log("BIG WARNING: missing predefined values or unresolved Directus links");
|
|
2237
|
+
for (const warning of warnings) taskProgressService.log(`- ${warning}`);
|
|
2238
|
+
taskProgressService.log("========================================");
|
|
2174
2239
|
}
|
|
2175
2240
|
return {
|
|
2176
2241
|
pushedCollections,
|
|
@@ -2210,9 +2275,11 @@ function buildReviewProperty(property) {
|
|
|
2210
2275
|
const backends = property.backend_mappings.map((mapping) => ({
|
|
2211
2276
|
type: mapping.backend,
|
|
2212
2277
|
field: mapping.field,
|
|
2278
|
+
document: mapping.document,
|
|
2279
|
+
description: mapping.description,
|
|
2213
2280
|
source: mapping.source_file,
|
|
2214
|
-
...mapping.route !== "unknown" || mapping.method ? { operation: {
|
|
2215
|
-
method: mapping.method,
|
|
2281
|
+
...mapping.route && (mapping.route !== "unknown" || mapping.method) ? { operation: {
|
|
2282
|
+
method: mapping.method || null,
|
|
2216
2283
|
route: mapping.route
|
|
2217
2284
|
} } : {},
|
|
2218
2285
|
confidence: mapping.confidence,
|
|
@@ -2384,16 +2451,17 @@ async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.
|
|
|
2384
2451
|
};
|
|
2385
2452
|
}
|
|
2386
2453
|
function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backendRoutesByKey, backendPropertiesByKey, mappingEvidence) {
|
|
2387
|
-
const
|
|
2388
|
-
const
|
|
2454
|
+
const backendRoute = mapping.route || (mapping.document ? `DOCUMENT ${mapping.document}` : "unknown");
|
|
2455
|
+
const backendRouteKey = stableKey$1(routeKey, mapping.backend, backendRoute);
|
|
2456
|
+
const backendRouteRecord = backendRoutesByKey.get(backendRouteKey) || {
|
|
2389
2457
|
key: backendRouteKey,
|
|
2390
2458
|
backend: mapping.backend,
|
|
2391
2459
|
route_key: routeKey,
|
|
2392
|
-
route: `${mapping.method || "CALL"} ${
|
|
2460
|
+
route: `${mapping.method || "CALL"} ${backendRoute}`,
|
|
2393
2461
|
source_file: mapping.source_file,
|
|
2394
2462
|
provenance: "code_analysis"
|
|
2395
2463
|
};
|
|
2396
|
-
backendRoutesByKey.set(backendRouteKey,
|
|
2464
|
+
backendRoutesByKey.set(backendRouteKey, backendRouteRecord);
|
|
2397
2465
|
const backendPropertyKey = stableKey$1(backendRouteKey, direction, mapping.field);
|
|
2398
2466
|
backendPropertiesByKey.set(backendPropertyKey, {
|
|
2399
2467
|
key: backendPropertyKey,
|
|
@@ -2419,8 +2487,10 @@ function toCatalogueBackendMapping(mapping) {
|
|
|
2419
2487
|
return {
|
|
2420
2488
|
backend: mapping.type,
|
|
2421
2489
|
method: mapping.operation?.method || null,
|
|
2422
|
-
route: mapping.operation
|
|
2490
|
+
...mapping.operation?.route ? { route: mapping.operation.route } : {},
|
|
2423
2491
|
field: mapping.field,
|
|
2492
|
+
document: mapping.document,
|
|
2493
|
+
description: mapping.description,
|
|
2424
2494
|
source_file: mapping.source,
|
|
2425
2495
|
confidence: mapping.confidence,
|
|
2426
2496
|
mapper_type: mapping.mapper_type,
|
|
@@ -2559,7 +2629,7 @@ async function readCatalogueFromDirectory(inputDir) {
|
|
|
2559
2629
|
};
|
|
2560
2630
|
}
|
|
2561
2631
|
//#endregion
|
|
2562
|
-
//#region src/services/directusPushService.ts
|
|
2632
|
+
//#region src/services/directus/directusPushService.ts
|
|
2563
2633
|
async function pushCatalogueDirectoryToDirectus(params) {
|
|
2564
2634
|
const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
|
|
2565
2635
|
taskProgressService.report("Loading and reconstructing route YAML documents");
|
|
@@ -2728,6 +2798,44 @@ var cleanOrphans_default = (program) => void program.command("clean-orphans").op
|
|
|
2728
2798
|
}
|
|
2729
2799
|
});
|
|
2730
2800
|
//#endregion
|
|
2801
|
+
//#region src/commands/backendSources.ts
|
|
2802
|
+
async function listBackendSourceMetadata(sources, backend) {
|
|
2803
|
+
const resolvableSources = sources.filter((source) => Boolean(source.resolve));
|
|
2804
|
+
const selectedSources = backend ? resolvableSources.filter((source) => source.name === backend) : resolvableSources;
|
|
2805
|
+
if (backend && !selectedSources.length) throw new Error(`Unknown backend source with a resolver: ${backend}`);
|
|
2806
|
+
if (!backend && !selectedSources.length) throw new Error("No backend source with a resolver is configured");
|
|
2807
|
+
const propertiesByBackend = await resolveBackendProperties(selectedSources);
|
|
2808
|
+
return selectedSources.map((source) => ({
|
|
2809
|
+
backend: source.name,
|
|
2810
|
+
properties: propertiesByBackend.get(source.name) || []
|
|
2811
|
+
}));
|
|
2812
|
+
}
|
|
2813
|
+
var backendSources_default = (program) => void program.command("backend-sources").option("--backend <name>", "Only execute one backend source").description("Execute configured backend sources and display their resolved property metadata").action(async (options) => {
|
|
2814
|
+
intro("Atlas backend source metadata");
|
|
2815
|
+
try {
|
|
2816
|
+
const sources = getUserConfig().analysis.backends.filter((source) => Boolean(source.resolve));
|
|
2817
|
+
const selectedBackend = options.backend || await select({
|
|
2818
|
+
message: "Which backend source do you want to execute?",
|
|
2819
|
+
options: sources.map((source) => ({
|
|
2820
|
+
value: source.name,
|
|
2821
|
+
label: source.name,
|
|
2822
|
+
hint: "resolver configured"
|
|
2823
|
+
}))
|
|
2824
|
+
});
|
|
2825
|
+
if (isCancel(selectedBackend)) {
|
|
2826
|
+
cancel("Backend source execution cancelled");
|
|
2827
|
+
process.exitCode = 1;
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
const metadata = await listBackendSourceMetadata(sources, selectedBackend);
|
|
2831
|
+
log.message(JSON.stringify(metadata, null, 2));
|
|
2832
|
+
outro("Backend source metadata generated");
|
|
2833
|
+
} catch (error) {
|
|
2834
|
+
cancel(error instanceof Error ? error.message : String(error));
|
|
2835
|
+
process.exitCode = 1;
|
|
2836
|
+
}
|
|
2837
|
+
});
|
|
2838
|
+
//#endregion
|
|
2731
2839
|
//#region src/services/analysisProfileService.ts
|
|
2732
2840
|
function createAnalysisProfile(enabled) {
|
|
2733
2841
|
const durations = /* @__PURE__ */ new Map();
|
|
@@ -3042,7 +3150,7 @@ var generateTest_default = (program) => void program.command("generate:test").de
|
|
|
3042
3150
|
}
|
|
3043
3151
|
});
|
|
3044
3152
|
//#endregion
|
|
3045
|
-
//#region src/services/aiSdkClient.ts
|
|
3153
|
+
//#region src/services/ai/aiSdkClient.ts
|
|
3046
3154
|
var AISdkClient = class {
|
|
3047
3155
|
#config;
|
|
3048
3156
|
constructor(config) {
|
|
@@ -3073,7 +3181,7 @@ var AISdkClient = class {
|
|
|
3073
3181
|
}
|
|
3074
3182
|
};
|
|
3075
3183
|
//#endregion
|
|
3076
|
-
//#region src/services/inferenceService.ts
|
|
3184
|
+
//#region src/services/ai/inferenceService.ts
|
|
3077
3185
|
const MAPPING_OUTPUT_SCHEMA = z.object({ mappings: z.array(z.object({
|
|
3078
3186
|
property_index: z.number().int(),
|
|
3079
3187
|
candidate_index: z.number().int(),
|
|
@@ -4209,6 +4317,7 @@ program.name("atlas").description("Manage the API-to-backend mapping catalogue")
|
|
|
4209
4317
|
setUserConfig(config);
|
|
4210
4318
|
});
|
|
4211
4319
|
init_default(program, program);
|
|
4320
|
+
backendSources_default(program);
|
|
4212
4321
|
generate_default(program);
|
|
4213
4322
|
generateGraph_default(program);
|
|
4214
4323
|
generateTest_default(program);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineExpressionRule-Dfvzj6n2.mjs","names":[],"sources":["../src/utils/defineExpressionRule.ts"],"sourcesContent":["import type { Expression } from \"ts-morph\";\nimport type { UserConfig } from \"../models/types\";\n\nexport type FieldExtractionRuleResult = {\n backendField?: string;\n transparent?: boolean;\n mapperType?: string;\n apiMapping?: boolean;\n};\n\nexport type FieldExtractionRule = {\n name: string;\n match: (expression: Expression, config: UserConfig) => boolean;\n parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;\n};\n\nexport function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule {\n return rule;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"defineExpressionRule-Dfvzj6n2.mjs","names":[],"sources":["../src/utils/defineExpressionRule.ts"],"sourcesContent":["import type { Expression } from \"ts-morph\";\nimport type { UserConfig } from \"../models/types\";\n\nexport type FieldExtractionRuleResult = {\n backendField?: string;\n transparent?: boolean;\n mapperType?: string;\n apiMapping?: boolean;\n};\n\nexport type FieldExtractionRule = {\n name: string;\n /** Higher-priority rules are evaluated first. Defaults to 0. */\n priority?: number;\n match: (expression: Expression, config: UserConfig) => boolean;\n parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;\n};\n\nexport function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule {\n return rule;\n}\n"],"mappings":"AAkBA,SAAgB,EAAqB,EAAgD,CACnF,OAAO,CACT"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as defineExpressionRule, n as BackendSource, r as UserConfig, t as BackendProperty } from "./types-cYprdLUO.mjs";
|
|
2
|
+
import { cmsI18nFieldRule } from "./rules/cmsI18nFieldRule.mjs";
|
|
3
|
+
import { quableI18nFieldRule } from "./rules/quableI18nFieldRule.mjs";
|
|
2
4
|
//#region src/utils/defineConfig.d.ts
|
|
3
5
|
declare function defineConfig(config: UserConfig): UserConfig;
|
|
4
6
|
//#endregion
|
|
5
|
-
|
|
7
|
+
//#region src/utils/defineBackendSource.d.ts
|
|
8
|
+
declare function defineBackendSource(source: BackendSource): BackendSource;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/utils/constant.d.ts
|
|
11
|
+
/**
|
|
12
|
+
* Resolves an environment variable, falling back to the supplied default value.
|
|
13
|
+
*
|
|
14
|
+
* This is intended for values declared directly in an Atlas configuration, such
|
|
15
|
+
* as URLs or tokens used by a backend source.
|
|
16
|
+
*/
|
|
17
|
+
declare function constant<T extends string = string>(name: string, defaultValue: T): string;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/services/http/httpClient.d.ts
|
|
20
|
+
declare class HttpClient {
|
|
21
|
+
fetch(url: string, init: RequestInit & {
|
|
22
|
+
onProgress: (content: string) => void;
|
|
23
|
+
}): Promise<string | unknown>;
|
|
24
|
+
fetch(url: string, init?: RequestInit): Promise<Response>;
|
|
25
|
+
get<T>(url: string, init?: RequestInit): Promise<T>;
|
|
26
|
+
}
|
|
27
|
+
declare const httpClient: HttpClient;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/services/openapi/openapiSource.d.ts
|
|
30
|
+
declare function openapiSource(params: {
|
|
31
|
+
url: string;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}): Promise<BackendProperty[]>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { type BackendProperty, type BackendSource, type UserConfig, cmsI18nFieldRule, constant, defineBackendSource, defineConfig, defineExpressionRule, httpClient, openapiSource, quableI18nFieldRule };
|
|
6
36
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"./defineExpressionRule-Dfvzj6n2.mjs";function t(e){return e}export{t as defineConfig,e as defineExpressionRule};
|
|
1
|
+
import{t as e}from"./defineExpressionRule-Dfvzj6n2.mjs";import{cmsI18nFieldRule as t}from"./rules/cmsI18nFieldRule.mjs";import{quableI18nFieldRule as n}from"./rules/quableI18nFieldRule.mjs";import{AsyncLocalStorage as r}from"node:async_hooks";import{spinner as i}from"@clack/prompts";function a(e){return e}function o(e){return e}function s(e,t){return process.env[e]??t}const c=new class{async fetch(e,t){let n={accept:`application/json`,...t?.headers},r=await fetch(e,{...t,headers:n});if(!r.ok)throw Error(`Unable to fetch ${e}: ${r.status} ${r.statusText}`);if(t?.onProgress){let e=r.body?.getReader();if(!e)return r.json();let n=new TextDecoder,i=``;for(;;){let{done:r,value:a}=await e.read();if(r)break;i+=n.decode(a,{stream:!0}),t.onProgress(i)}return i+n.decode()}return r}async get(e,t){return(await this.fetch(e,t)).json()}};function l(e){return e<1e3?`${e}ms`:`${(e/1e3).toFixed(1)}s`}const u=new class{#e=new r;attach(e,t){return this.#e.run(e,t)}log(e){this.#e.getStore()?.log(e)}report(e){let t=this.#e.getStore();(t?.report||t?.log)?.(e)}createStepProgress(e=e=>({id:e,title:e})){let t=i(),n,r=e=>{if(!n)return;let r=l(Date.now()-n.startedAt);t.stop(`${e||n.completedTitle||`${n.title} completed`} (${r})`),n=void 0},a=e=>{if(n?.id===e.id){t.message(e.detail?`${e.title}: ${e.detail}`:e.title);return}r(),n={id:e.id,title:e.title,completedTitle:e.completedTitle,startedAt:Date.now()},t.start(e.detail?`${e.title}: ${e.detail}`:e.title)},o=r=>this.attach({log:e=>t.message(n?`${n.title}: ${e}`:e),report:t=>a(e(t))},r);return{report(t){a(e(t))},start:a,execute:o,run:async(e,t)=>{a(e),await new Promise(e=>setImmediate(e));let n=await o(t);return r(),n},finish(e){r(e)},fail(e){if(!n)return;let r=l(Date.now()-n.startedAt);t.stop(`${e} (${r})`),n=void 0}}}};async function d(e,t){try{let n=await c.fetch(e,{signal:t?AbortSignal.timeout(t):void 0,onProgress(e){u.log(`Downloading (${(e.length/1048576).toFixed(1)} MB)`)}});return!n||typeof n==`object`?n:(u.log(`Parsing document`),JSON.parse(n))}catch(n){throw n instanceof Error&&(n.name===`AbortError`||n.name===`TimeoutError`)?Error(`OpenAPI download timed out after ${t}ms: ${e}`):Error(`Unable to fetch OpenAPI document from ${e}: ${n instanceof Error?n.message:String(n)}`)}}function f(e,t){if(!e||typeof e!=`object`||typeof e.$ref!=`string`||!e.$ref.startsWith(`#/`))return e;let n=t;for(let t of e.$ref.replace(`#/`,``).split(`/`).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`)))if(n=n?.[t],n===void 0)return e;return n}function p(e){return/^\d+$/.test(e)&&Number(e)>=200&&Number(e)<=299}function m(e,t){if(!e||typeof e!=`object`)return;if(e[`application/json`]?.schema)return f(e[`application/json`].schema,t);let n=Object.values(e).find(e=>e?.schema);return n?f(n.schema,t):void 0}function h(e,t,n=``){let r=f(e,t);if(!r||typeof r!=`object`)return[];if(Array.isArray(r.allOf))return r.allOf.flatMap(e=>h(e,t,n));if(r.type===`array`||r.items){let e=n?`${n}[]`:`[]`;return h(r.items,t,e)}let i=r.properties||{};return Object.keys(i).length?Object.entries(i).flatMap(([e,r])=>h(r,t,n?`${n}.${e}`:e)):n?[{path:n,description:r.description,deprecated:r.deprecated}]:[]}function g(e){return e===`query`?`QUERY`:e===`path`?`PATH`:e===`header`?`HEADER`:null}function _(e,t){let n=new Map,r=Array.isArray(e.parameters)?e.parameters:[];for(let e of r){let r=f(e,t),i=g(r?.in);if(!r||!i||!r.name)continue;let a=h(r.schema,t,r.name),o=a.length?a:[{path:r.name,description:r.description,deprecated:r.deprecated}];for(let e of o)n.set(`${i}:${e.path}`,{path:e.path,description:e.description||r.description,deprecated:e.deprecated??r.deprecated??!1,type:i})}let i=f(e.requestBody,t);if(i?.content){let e=m(i.content,t);for(let r of h(e,t))n.set(`BODY:${r.path}`,{path:r.path,description:r.description||i.description,deprecated:r.deprecated??!1,type:`BODY`})}return[...n.values()]}function v(e,t){let n=new Map,r=e?.responses||{};for(let[e,i]of Object.entries(r)){if(!p(e))continue;let r=f(i,t),a=m(r?.content,t);for(let e of h(a,t))n.set(e.path,{path:e.path,description:e.description||r?.description,deprecated:e.deprecated??!1,type:`RESPONSE_BODY`})}return[...n.values()]}function y(e){let t=new Map;for(let[n,r]of Object.entries(e.paths||{}))for(let[i,a]of Object.entries(r||{})){if(!/^(get|post|put|patch|delete|head|options)$/i.test(i))continue;let r=i.toUpperCase();for(let i of[..._(a,e),...v(a,e)]){let e=`${r}:${n}:${i.path}`;t.has(e)||t.set(e,{route:n,method:r,field:i.path,description:i.description})}}return[...t.values()]}async function b(e){return y(await d(e.url,e.timeoutMs))}export{t as cmsI18nFieldRule,s as constant,o as defineBackendSource,a as defineConfig,e as defineExpressionRule,c as httpClient,b as openapiSource,n as quableI18nFieldRule};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/defineConfig.ts"],"sourcesContent":["import type { UserConfig } from \"../models/types\";\n\nexport function defineConfig(config: UserConfig): UserConfig {\n return config;\n}\n"],"mappings":"wDAEA,SAAgB,EAAa,EAAgC,CAC3D,OAAO,CACT"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#storage"],"sources":["../src/utils/defineConfig.ts","../src/utils/defineBackendSource.ts","../src/utils/constant.ts","../src/services/http/httpClient.ts","../src/services/tasks/taskProgressService.ts","../src/services/openapi/loadOpenApiDocument.ts","../src/services/openapi/schemaService.ts","../src/services/openapi/propertyExtractionService.ts","../src/services/openapi/openapiSource.ts"],"sourcesContent":["import type { UserConfig } from \"../models/types\";\n\nexport function defineConfig(config: UserConfig): UserConfig {\n return config;\n}\n","import type { BackendSource } from \"../models/types\";\n\nexport function defineBackendSource(source: BackendSource): BackendSource {\n return source;\n}\n","/**\n * Resolves an environment variable, falling back to the supplied default value.\n *\n * This is intended for values declared directly in an Atlas configuration, such\n * as URLs or tokens used by a backend source.\n */\nexport function constant<T extends string = string>(name: string, defaultValue: T): string {\n return process.env[name] ?? defaultValue;\n}\n","class HttpClient {\n async fetch(url: string, init: RequestInit & { onProgress: (content: string) => void }): Promise<string | unknown>;\n async fetch(url: string, init?: RequestInit): Promise<Response>;\n async fetch(url: string, init?: RequestInit & { onProgress?: (content: string) => void }): Promise<Response | string | unknown> {\n const headers = { accept: \"application/json\", ...init?.headers };\n\n const response = await fetch(url, {\n ...init,\n headers\n });\n\n if (!response.ok) {\n throw new Error(`Unable to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n\n if (init?.onProgress) {\n const reader = response.body?.getReader();\n\n if (!reader) {\n return response.json() as unknown;\n }\n\n const decoder = new TextDecoder();\n let content = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n content += decoder.decode(value, { stream: true });\n\n init.onProgress(content);\n }\n\n return content + decoder.decode();\n }\n\n return response;\n }\n\n async get<T>(url: string, init?: RequestInit): Promise<T> {\n const response = await this.fetch(url, init);\n return response.json() as Promise<T>;\n }\n}\n\nexport const httpClient = new HttpClient();\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { spinner } from \"@clack/prompts\";\n\nexport type StepDescriptor = {\n id: string;\n title: string;\n completedTitle?: string;\n detail?: string;\n};\n\ntype TaskProgressSink = {\n log: (message: string) => void;\n report?: (message: string) => void;\n};\n\nfunction formatDuration(durationMs: number): string {\n if (durationMs < 1_000) {\n return `${durationMs}ms`;\n }\n return `${(durationMs / 1_000).toFixed(1)}s`;\n}\n\nclass TaskProgressService {\n readonly #storage = new AsyncLocalStorage<TaskProgressSink>();\n\n attach<T>(sink: TaskProgressSink, task: () => Promise<T>): Promise<T> {\n return this.#storage.run(sink, task);\n }\n\n log(message: string): void {\n this.#storage.getStore()?.log(message);\n }\n\n report(message: string): void {\n const sink = this.#storage.getStore();\n const reporter = sink?.report || sink?.log;\n reporter?.(message);\n }\n\n createStepProgress(classify: (message: string) => StepDescriptor = (message) => ({ id: message, title: message })) {\n const progress = spinner();\n let active: { id: string; title: string; completedTitle?: string; startedAt: number } | undefined;\n\n const finishActive = (label?: string) => {\n if (!active) {\n return;\n }\n const duration = formatDuration(Date.now() - active.startedAt);\n progress.stop(`${label || active.completedTitle || `${active.title} completed`} (${duration})`);\n active = undefined;\n };\n\n const start = (step: StepDescriptor) => {\n if (active?.id === step.id) {\n progress.message(step.detail ? `${step.title}: ${step.detail}` : step.title);\n return;\n }\n\n finishActive();\n active = {\n id: step.id,\n title: step.title,\n completedTitle: step.completedTitle,\n startedAt: Date.now()\n };\n progress.start(step.detail ? `${step.title}: ${step.detail}` : step.title);\n };\n\n const execute = <T>(task: () => Promise<T>): Promise<T> =>\n this.attach(\n {\n log: (message) => progress.message(active ? `${active.title}: ${message}` : message),\n report: (message) => start(classify(message))\n },\n task\n );\n\n return {\n report(message: string) {\n start(classify(message));\n },\n start,\n execute,\n run: async <T>(step: StepDescriptor, task: () => Promise<T>): Promise<T> => {\n start(step);\n await new Promise<void>((resolve) => setImmediate(resolve));\n const result = await execute(task);\n finishActive();\n return result;\n },\n finish(label?: string) {\n finishActive(label);\n },\n fail(label: string) {\n if (!active) {\n return;\n }\n const duration = formatDuration(Date.now() - active.startedAt);\n progress.stop(`${label} (${duration})`);\n active = undefined;\n }\n };\n }\n}\n\nexport const taskProgressService = new TaskProgressService();\n","import { httpClient } from \"../http/httpClient\";\nimport { taskProgressService } from \"../tasks/taskProgressService\";\n\nexport type OpenApiDocument = {\n info?: { version?: string };\n paths?: Record<string, Record<string, any>>;\n components?: Record<string, any>;\n};\n\nexport async function loadOpenApiDocument(url: string, timeoutMs?: number): Promise<OpenApiDocument> {\n try {\n const content = await httpClient.fetch(url, {\n signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,\n onProgress(content) {\n taskProgressService.log(`Downloading (${(content.length / 1_048_576).toFixed(1)} MB)`);\n }\n });\n\n if (!content || typeof content === \"object\") {\n return content as OpenApiDocument;\n }\n\n taskProgressService.log(\"Parsing document\");\n\n return JSON.parse(content as string) as OpenApiDocument;\n } catch (error) {\n if (error instanceof Error && (error.name === \"AbortError\" || error.name === \"TimeoutError\")) {\n throw new Error(`OpenAPI download timed out after ${timeoutMs}ms: ${url}`);\n }\n\n throw new Error(`Unable to fetch OpenAPI document from ${url}: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n","import type { OpenApiDocument } from \"./loadOpenApiDocument\";\n\nexport function resolveOpenApiReference(value: any, swagger: OpenApiDocument): any {\n if (!value || typeof value !== \"object\" || typeof value.$ref !== \"string\" || !value.$ref.startsWith(\"#/\")) {\n return value;\n }\n let current: any = swagger;\n for (const segment of value.$ref\n .replace(\"#/\", \"\")\n .split(\"/\")\n .map((part: string) => part.replace(/~1/g, \"/\").replace(/~0/g, \"~\"))) {\n current = current?.[segment];\n if (current === undefined) {\n return value;\n }\n }\n return current;\n}\n","import type { BackendProperty, ExtractedApiProperty } from \"../../models/types\";\nimport type { OpenApiDocument } from \"./loadOpenApiDocument\";\nimport { resolveOpenApiReference } from \"./schemaService\";\n\ntype OpenApiLeafProperty = {\n path: string;\n description?: string;\n deprecated?: boolean;\n};\n\nfunction isSuccessStatusCode(statusCode: string): boolean {\n return /^\\d+$/.test(statusCode) && Number(statusCode) >= 200 && Number(statusCode) <= 299;\n}\n\nfunction selectPreferredSchema(content: any, swagger: OpenApiDocument): any {\n if (!content || typeof content !== \"object\") {\n return undefined;\n }\n\n if (content[\"application/json\"]?.schema) {\n return resolveOpenApiReference(content[\"application/json\"].schema, swagger);\n }\n\n const firstSchema = Object.values(content).find((entry: any) => entry?.schema) as any;\n return firstSchema ? resolveOpenApiReference(firstSchema.schema, swagger) : undefined;\n}\n\nfunction extractLeafProperties(schema: any, swagger: OpenApiDocument, currentPath = \"\"): OpenApiLeafProperty[] {\n const resolvedSchema = resolveOpenApiReference(schema, swagger);\n\n if (!resolvedSchema || typeof resolvedSchema !== \"object\") {\n return [];\n }\n\n if (Array.isArray(resolvedSchema.allOf)) {\n return resolvedSchema.allOf.flatMap((item: any) => extractLeafProperties(item, swagger, currentPath));\n }\n\n if (resolvedSchema.type === \"array\" || resolvedSchema.items) {\n const arrayPath = currentPath ? `${currentPath}[]` : \"[]\";\n return extractLeafProperties(resolvedSchema.items, swagger, arrayPath);\n }\n\n const properties = resolvedSchema.properties || {};\n if (!Object.keys(properties).length) {\n return currentPath\n ? [\n {\n path: currentPath,\n description: resolvedSchema.description,\n deprecated: resolvedSchema.deprecated\n }\n ]\n : [];\n }\n\n return Object.entries(properties).flatMap(([propertyName, propertySchema]: [string, any]) => {\n const nextPath = currentPath ? `${currentPath}.${propertyName}` : propertyName;\n return extractLeafProperties(propertySchema, swagger, nextPath);\n });\n}\n\nfunction mapInputType(inType?: string): ExtractedApiProperty[\"type\"] | null {\n if (inType === \"query\") {\n return \"QUERY\";\n }\n if (inType === \"path\") {\n return \"PATH\";\n }\n if (inType === \"header\") {\n return \"HEADER\";\n }\n return null;\n}\n\nexport function extractOpenApiInputProperties(operation: any, swagger: OpenApiDocument): ExtractedApiProperty[] {\n const map = new Map<string, ExtractedApiProperty>();\n const parameters = Array.isArray(operation.parameters) ? operation.parameters : [];\n\n for (const rawParameter of parameters) {\n const parameter = resolveOpenApiReference(rawParameter, swagger);\n const inputType = mapInputType(parameter?.in);\n if (!parameter || !inputType || !parameter.name) {\n continue;\n }\n\n const properties = extractLeafProperties(parameter.schema, swagger, parameter.name);\n const resolvedProperties = properties.length\n ? properties\n : [\n {\n path: parameter.name,\n description: parameter.description,\n deprecated: parameter.deprecated\n }\n ];\n\n for (const property of resolvedProperties) {\n map.set(`${inputType}:${property.path}`, {\n path: property.path,\n description: property.description || parameter.description,\n deprecated: property.deprecated ?? parameter.deprecated ?? false,\n type: inputType\n });\n }\n }\n\n const requestBody = resolveOpenApiReference(operation.requestBody, swagger);\n if (requestBody?.content) {\n const schema = selectPreferredSchema(requestBody.content, swagger);\n for (const property of extractLeafProperties(schema, swagger)) {\n map.set(`BODY:${property.path}`, {\n path: property.path,\n description: property.description || requestBody.description,\n deprecated: property.deprecated ?? false,\n type: \"BODY\"\n });\n }\n }\n\n return [...map.values()];\n}\n\nexport function extractOpenApiOutputProperties(operation: any, swagger: OpenApiDocument): ExtractedApiProperty[] {\n const map = new Map<string, ExtractedApiProperty>();\n const responses = operation?.responses || {};\n\n for (const [statusCode, rawResponse] of Object.entries(responses)) {\n if (!isSuccessStatusCode(statusCode)) {\n continue;\n }\n\n const response = resolveOpenApiReference(rawResponse, swagger);\n const schema = selectPreferredSchema(response?.content, swagger);\n for (const property of extractLeafProperties(schema, swagger)) {\n map.set(property.path, {\n path: property.path,\n description: property.description || response?.description,\n deprecated: property.deprecated ?? false,\n type: \"RESPONSE_BODY\"\n });\n }\n }\n\n return [...map.values()];\n}\n\nexport function extractOpenApiBackendProperties(swagger: OpenApiDocument): BackendProperty[] {\n const properties = new Map<string, BackendProperty>();\n for (const [route, pathItem] of Object.entries(swagger.paths || {})) {\n for (const [rawMethod, operation] of Object.entries(pathItem || {})) {\n if (!/^(get|post|put|patch|delete|head|options)$/i.test(rawMethod)) {\n continue;\n }\n const method = rawMethod.toUpperCase();\n for (const property of [\n ...extractOpenApiInputProperties(operation, swagger),\n ...extractOpenApiOutputProperties(operation, swagger)\n ]) {\n const key = `${method}:${route}:${property.path}`;\n if (!properties.has(key)) {\n properties.set(key, {\n route,\n method,\n field: property.path,\n description: property.description\n });\n }\n }\n }\n }\n return [...properties.values()];\n}\n","import type { BackendProperty } from \"../../models/types\";\nimport { loadOpenApiDocument } from \"./loadOpenApiDocument\";\nimport { extractOpenApiBackendProperties } from \"./propertyExtractionService\";\n\nexport async function openapiSource(params: { url: string; timeoutMs?: number }): Promise<BackendProperty[]> {\n const document = await loadOpenApiDocument(params.url, params.timeoutMs);\n return extractOpenApiBackendProperties(document);\n}\n"],"mappings":"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"}
|
package/dist/{routeBackendTopologyService-D_aIIBbX.mjs → routeBackendTopologyService-DElirHSh.mjs}
RENAMED
|
@@ -10,14 +10,37 @@ import { Worker } from "node:worker_threads";
|
|
|
10
10
|
//#region src/utils/config.ts
|
|
11
11
|
let _config;
|
|
12
12
|
function setUserConfig(config) {
|
|
13
|
-
|
|
13
|
+
const backends = normalizeBackendSources(config.analysis.backends);
|
|
14
|
+
_config = {
|
|
15
|
+
...config,
|
|
16
|
+
analysis: {
|
|
17
|
+
...config.analysis,
|
|
18
|
+
backends,
|
|
19
|
+
rules: normalizeExpressionRules([...config.analysis.rules, ...backends.flatMap((backend) => backend.rules || [])])
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function normalizeBackendSources(backends) {
|
|
24
|
+
const sources = backends.map((backend) => typeof backend === "string" ? { name: backend } : backend);
|
|
25
|
+
const sourcesByName = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const source of sources) {
|
|
27
|
+
if (!source.name.trim()) throw new Error("Backend source names must not be empty");
|
|
28
|
+
sourcesByName.set(source.name, source);
|
|
29
|
+
}
|
|
30
|
+
return [...sourcesByName.values()];
|
|
31
|
+
}
|
|
32
|
+
function normalizeExpressionRules(rules) {
|
|
33
|
+
return [...new Set(rules)].map((rule, index) => ({
|
|
34
|
+
rule,
|
|
35
|
+
index
|
|
36
|
+
})).sort((left, right) => (right.rule.priority || 0) - (left.rule.priority || 0) || left.index - right.index).map(({ rule }) => rule);
|
|
14
37
|
}
|
|
15
38
|
function getUserConfig() {
|
|
16
39
|
if (!_config) throw new Error("Atlas config not loaded. Run Atlas from a directory containing atlas.config.ts or pass --config.");
|
|
17
40
|
return _config;
|
|
18
41
|
}
|
|
19
|
-
function getTsconfigAliases(
|
|
20
|
-
const tsconfigPath = path.join(
|
|
42
|
+
function getTsconfigAliases(workingDirectory = process.cwd()) {
|
|
43
|
+
const tsconfigPath = path.join(workingDirectory, "tsconfig.json");
|
|
21
44
|
if (!fs.existsSync(tsconfigPath)) return {};
|
|
22
45
|
const paths = new Project({
|
|
23
46
|
tsConfigFilePath: tsconfigPath,
|
|
@@ -39,12 +62,17 @@ async function loadAtlasConfig(configPath, projectRoot) {
|
|
|
39
62
|
} else mod = await import(pathToFileURL(filePath).href);
|
|
40
63
|
const config = mod.default ?? mod;
|
|
41
64
|
const repoRoot = projectRoot || config.repoRoot || process.cwd();
|
|
65
|
+
const backends = normalizeBackendSources(config.analysis.backends);
|
|
42
66
|
return {
|
|
43
67
|
...config,
|
|
44
68
|
repoRoot,
|
|
45
69
|
cwd: repoRoot,
|
|
70
|
+
analysis: {
|
|
71
|
+
...config.analysis,
|
|
72
|
+
backends
|
|
73
|
+
},
|
|
46
74
|
resolver: { alias: {
|
|
47
|
-
...getTsconfigAliases(
|
|
75
|
+
...getTsconfigAliases(),
|
|
48
76
|
...config.resolver?.alias
|
|
49
77
|
} }
|
|
50
78
|
};
|
|
@@ -53,7 +81,7 @@ async function loadAtlasConfig(configPath, projectRoot) {
|
|
|
53
81
|
}
|
|
54
82
|
}
|
|
55
83
|
//#endregion
|
|
56
|
-
//#region src/services/taskProgressService.ts
|
|
84
|
+
//#region src/services/tasks/taskProgressService.ts
|
|
57
85
|
function formatDuration(durationMs) {
|
|
58
86
|
if (durationMs < 1e3) return `${durationMs}ms`;
|
|
59
87
|
return `${(durationMs / 1e3).toFixed(1)}s`;
|
|
@@ -138,7 +166,7 @@ function filterAnalysisFiles(filePaths) {
|
|
|
138
166
|
//#endregion
|
|
139
167
|
//#region src/utils/isKnowBackendType.ts
|
|
140
168
|
function isKnownBackendType(value) {
|
|
141
|
-
return getUserConfig().analysis.backends.
|
|
169
|
+
return getUserConfig().analysis.backends.some((backend) => backend.name === value);
|
|
142
170
|
}
|
|
143
171
|
//#endregion
|
|
144
172
|
//#region src/services/backendSourceService.ts
|
|
@@ -234,14 +262,14 @@ function findLocalCallable(sourceFile, symbol) {
|
|
|
234
262
|
}
|
|
235
263
|
function resolveSourceFile(project, owner, moduleSpecifier, resolvePath) {
|
|
236
264
|
const resolvedPath = resolvePath(owner.getFilePath(), moduleSpecifier);
|
|
237
|
-
if (!resolvedPath) return
|
|
265
|
+
if (!resolvedPath) return;
|
|
238
266
|
const existing = project.getSourceFile(resolvedPath);
|
|
239
267
|
if (existing) return existing;
|
|
240
268
|
return isFilePath(resolvedPath) ? project.addSourceFileAtPathIfExists(resolvedPath) : void 0;
|
|
241
269
|
}
|
|
242
270
|
function resolveExportedCallable(project, sourceFile, symbol, resolvePath, seen = /* @__PURE__ */ new Set()) {
|
|
243
271
|
const key = `${sourceFile.getFilePath()}:${symbol}`;
|
|
244
|
-
if (seen.has(key)) return
|
|
272
|
+
if (seen.has(key)) return;
|
|
245
273
|
seen.add(key);
|
|
246
274
|
const local = findLocalCallable(sourceFile, symbol);
|
|
247
275
|
if (local) return {
|
|
@@ -263,7 +291,7 @@ function resolveExportedCallable(project, sourceFile, symbol, resolvePath, seen
|
|
|
263
291
|
}
|
|
264
292
|
function resolveConstructedMember(project, sourceFile, variableName, memberName, resolvePath) {
|
|
265
293
|
const initializer = sourceFile.getVariableDeclaration(variableName)?.getInitializer();
|
|
266
|
-
if (!initializer || !Node.isNewExpression(initializer)) return
|
|
294
|
+
if (!initializer || !Node.isNewExpression(initializer)) return;
|
|
267
295
|
const constructorName = initializer.getExpression().getText();
|
|
268
296
|
const localMethod = sourceFile.getClass(constructorName)?.getInstanceMethod(memberName);
|
|
269
297
|
if (localMethod) return {
|
|
@@ -324,7 +352,7 @@ function resolveTypedPropertyMethod(project, sourceFile, expression, resolvePath
|
|
|
324
352
|
const receiver = expression.getExpression();
|
|
325
353
|
if (!Node.isPropertyAccessExpression(receiver) || receiver.getExpression().getText() !== "this") return;
|
|
326
354
|
const propertyType = expression.getFirstAncestorByKind(SyntaxKind.ClassDeclaration)?.getProperty(receiver.getName())?.getTypeNode()?.getText().match(/[A-Za-z_$][A-Za-z0-9_$]*/)?.[0];
|
|
327
|
-
if (!propertyType) return
|
|
355
|
+
if (!propertyType) return;
|
|
328
356
|
for (const importDeclaration of sourceFile.getImportDeclarations()) {
|
|
329
357
|
const namedImport = importDeclaration.getNamedImports().find((item) => (item.getAliasNode()?.getText() || item.getName()) === propertyType);
|
|
330
358
|
if (!namedImport) continue;
|
|
@@ -361,10 +389,10 @@ function resolveReference(project, sourceFile, expression, resolvePath) {
|
|
|
361
389
|
const imported = resolveImportedReference(project, sourceFile, expressionText, resolvePath);
|
|
362
390
|
if (imported) return imported;
|
|
363
391
|
const declaration = expression.getSymbol()?.getAliasedSymbol()?.getDeclarations()[0] || expression.getSymbol()?.getDeclarations()[0];
|
|
364
|
-
if (!declaration) return
|
|
392
|
+
if (!declaration) return;
|
|
365
393
|
const callable = Node.isFunctionDeclaration(declaration) || Node.isMethodDeclaration(declaration) || Node.isVariableDeclaration(declaration) ? declaration : void 0;
|
|
366
|
-
if (!callable) return
|
|
367
|
-
if (Node.isVariableDeclaration(callable) && !isCallableVariable(callable)) return
|
|
394
|
+
if (!callable) return;
|
|
395
|
+
if (Node.isVariableDeclaration(callable) && !isCallableVariable(callable)) return;
|
|
368
396
|
const targetFile = callable.getSourceFile();
|
|
369
397
|
return {
|
|
370
398
|
declaration: callable,
|
|
@@ -404,7 +432,7 @@ function mappingDirection(symbol, layer) {
|
|
|
404
432
|
function callableFromTopologyNode(cwd, project, node) {
|
|
405
433
|
const filePath = path.resolve(cwd, node.source);
|
|
406
434
|
const sourceFile = project.getSourceFile(filePath) || project.addSourceFileAtPathIfExists(filePath);
|
|
407
|
-
if (!sourceFile) return
|
|
435
|
+
if (!sourceFile) return;
|
|
408
436
|
const declaration = [
|
|
409
437
|
...sourceFile.getFunctions(),
|
|
410
438
|
...sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration),
|
|
@@ -574,13 +602,13 @@ function traceBackendPaths(params) {
|
|
|
574
602
|
}
|
|
575
603
|
function stringProperty(object, name) {
|
|
576
604
|
const property = object.getProperty(name);
|
|
577
|
-
if (!property || !Node.isPropertyAssignment(property)) return
|
|
605
|
+
if (!property || !Node.isPropertyAssignment(property)) return;
|
|
578
606
|
const initializer = property.getInitializer();
|
|
579
607
|
return initializer && (Node.isStringLiteral(initializer) || Node.isNoSubstitutionTemplateLiteral(initializer)) ? initializer.getLiteralValue() : void 0;
|
|
580
608
|
}
|
|
581
609
|
function handlerProperty(object) {
|
|
582
610
|
const property = object.getProperty("handler");
|
|
583
|
-
if (!property || !Node.isPropertyAssignment(property)) return
|
|
611
|
+
if (!property || !Node.isPropertyAssignment(property)) return;
|
|
584
612
|
const initializer = property.getInitializer();
|
|
585
613
|
return initializer && (Node.isIdentifier(initializer) || Node.isPropertyAccessExpression(initializer)) ? initializer.getText() : void 0;
|
|
586
614
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as FieldExtractionRule } from "../types-cYprdLUO.mjs";
|
|
2
2
|
//#region src/rules/cmsI18nFieldRule.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* `CmsI18n#get`/`getAll` reads a localized field off the translations array passed to the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mappingUtilityRule.mjs","names":[],"sources":["../../src/rules/mappingUtilityRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nconst mapperTypes = new Set([\"string\", \"number\", \"boolean\", \"date\", \"datetime\", \"enum\", \"array\", \"object\"]);\n\nexport const mappingUtilityRule = defineExpressionRule({\n name: \"mapping-utility-wrapper\",\n match: (expression, config) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const callee = expression.getExpression().getText();\n return config.analysis.neutralExpressionMatchers.some((matcher) => callee.startsWith(matcher.prefix));\n },\n parse: (expression, config) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const callee = expression.getExpression().getText();\n const matcher = config.analysis.neutralExpressionMatchers.find((item) => callee.startsWith(item.prefix));\n const mapperType = callee.split(\".\").find((segment) => mapperTypes.has(segment));\n\n return {\n transparent: true,\n mapperType,\n apiMapping: matcher?.apiMapping ?? false\n };\n }\n});\n"],"mappings":"yFAGA,MAAM,EAAc,IAAI,IAAI,CAAC,SAAU,SAAU,UAAW,OAAQ,WAAY,OAAQ,QAAS,QAAQ,CAAC,EAE7F,EAAqB,EAAqB,CACrD,KAAM,0BACN,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,
|
|
1
|
+
{"version":3,"file":"mappingUtilityRule.mjs","names":[],"sources":["../../src/rules/mappingUtilityRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nconst mapperTypes = new Set([\"string\", \"number\", \"boolean\", \"date\", \"datetime\", \"enum\", \"array\", \"object\"]);\n\nexport const mappingUtilityRule = defineExpressionRule({\n name: \"mapping-utility-wrapper\",\n match: (expression, config) => {\n if (!Node.isCallExpression(expression)) {\n return false;\n }\n\n const callee = expression.getExpression().getText();\n return config.analysis.neutralExpressionMatchers.some((matcher) => callee.startsWith(matcher.prefix));\n },\n parse: (expression, config) => {\n if (!Node.isCallExpression(expression)) {\n return undefined;\n }\n\n const callee = expression.getExpression().getText();\n const matcher = config.analysis.neutralExpressionMatchers.find((item) => callee.startsWith(item.prefix));\n const mapperType = callee.split(\".\").find((segment) => mapperTypes.has(segment));\n\n return {\n transparent: true,\n mapperType,\n apiMapping: matcher?.apiMapping ?? false\n };\n }\n});\n"],"mappings":"yFAGA,MAAM,EAAc,IAAI,IAAI,CAAC,SAAU,SAAU,UAAW,OAAQ,WAAY,OAAQ,QAAS,QAAQ,CAAC,EAE7F,EAAqB,EAAqB,CACrD,KAAM,0BACN,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,MAAO,GAGT,IAAM,EAAS,EAAW,cAAc,CAAC,CAAC,QAAQ,EAClD,OAAO,EAAO,SAAS,0BAA0B,KAAM,GAAY,EAAO,WAAW,EAAQ,MAAM,CAAC,CACtG,EACA,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,IAAM,EAAS,EAAW,cAAc,CAAC,CAAC,QAAQ,EAC5C,EAAU,EAAO,SAAS,0BAA0B,KAAM,GAAS,EAAO,WAAW,EAAK,MAAM,CAAC,EAGvG,MAAO,CACL,YAAa,GACb,WAJiB,EAAO,MAAM,GAAG,CAAC,CAAC,KAAM,GAAY,EAAY,IAAI,CAAO,CAInE,EACT,WAAY,GAAS,YAAc,EACrC,CACF,CACF,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`member-get-field`,match:e=>{if(!t.isCallExpression(e))return!1;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));return t.isPropertyAccessExpression(n)&&[`get`,`getAll`].includes(n.getName())&&!!r},parse:e=>{if(!t.isCallExpression(e))return;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!(!t.isPropertyAccessExpression(n)||!t.isStringLiteral(r)&&!t.isNoSubstitutionTemplateLiteral(r)))return{backendField:`${n.getExpression().getText()}.${r.getLiteralValue()}`}}});export{n as memberGetFieldRule};
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`member-get-field`,priority:-100,match:e=>{if(!t.isCallExpression(e))return!1;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));return t.isPropertyAccessExpression(n)&&[`get`,`getAll`].includes(n.getName())&&!!r},parse:e=>{if(!t.isCallExpression(e))return;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!(!t.isPropertyAccessExpression(n)||!t.isStringLiteral(r)&&!t.isNoSubstitutionTemplateLiteral(r)))return{backendField:`${n.getExpression().getText()}.${r.getLiteralValue()}`}}});export{n as memberGetFieldRule};
|
|
2
2
|
//# sourceMappingURL=memberGetFieldRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memberGetFieldRule.mjs","names":[],"sources":["../../src/rules/memberGetFieldRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const memberGetFieldRule = defineExpressionRule({\n name: \"member-get-field\",\n match: (expression) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n return Node.isPropertyAccessExpression(target) && [\"get\", \"getAll\"].includes(target.getName()) && Boolean(field);\n },\n parse: (expression) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n if (!Node.isPropertyAccessExpression(target) || (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))) {\n return undefined;\n }\n\n return { backendField: `${target.getExpression().getText()}.${field.getLiteralValue()}` };\n }\n});\n"],"mappings":"yFAGA,MAAa,EAAqB,EAAqB,CACrD,KAAM,mBACN,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,
|
|
1
|
+
{"version":3,"file":"memberGetFieldRule.mjs","names":[],"sources":["../../src/rules/memberGetFieldRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const memberGetFieldRule = defineExpressionRule({\n name: \"member-get-field\",\n priority: -100,\n match: (expression) => {\n if (!Node.isCallExpression(expression)) {\n return false;\n }\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n return Node.isPropertyAccessExpression(target) && [\"get\", \"getAll\"].includes(target.getName()) && Boolean(field);\n },\n parse: (expression) => {\n if (!Node.isCallExpression(expression)) {\n return undefined;\n }\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n if (!Node.isPropertyAccessExpression(target) || (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))) {\n return undefined;\n }\n\n return { backendField: `${target.getExpression().getText()}.${field.getLiteralValue()}` };\n }\n});\n"],"mappings":"yFAGA,MAAa,EAAqB,EAAqB,CACrD,KAAM,mBACN,SAAU,KACV,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,MAAO,GAGT,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EACtG,OAAO,EAAK,2BAA2B,CAAM,GAAK,CAAC,MAAO,QAAQ,CAAC,CAAC,SAAS,EAAO,QAAQ,CAAC,GAAK,EAAQ,CAC5G,EACA,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EAClG,MAAC,EAAK,2BAA2B,CAAM,GAAM,CAAC,EAAK,gBAAgB,CAAK,GAAK,CAAC,EAAK,gCAAgC,CAAK,GAI5H,MAAO,CAAE,aAAc,GAAG,EAAO,cAAc,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAM,gBAAgB,GAAI,CAC1F,CACF,CAAC"}
|
|
@@ -8,6 +8,8 @@ type FieldExtractionRuleResult = {
|
|
|
8
8
|
};
|
|
9
9
|
type FieldExtractionRule = {
|
|
10
10
|
name: string;
|
|
11
|
+
/** Higher-priority rules are evaluated first. Defaults to 0. */
|
|
12
|
+
priority?: number;
|
|
11
13
|
match: (expression: Expression, config: UserConfig) => boolean;
|
|
12
14
|
parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;
|
|
13
15
|
};
|
|
@@ -18,6 +20,21 @@ interface NeutralExpressionMatcher {
|
|
|
18
20
|
prefix: string;
|
|
19
21
|
apiMapping?: boolean;
|
|
20
22
|
}
|
|
23
|
+
type BackendProperty = {
|
|
24
|
+
document: string;
|
|
25
|
+
field: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
} | {
|
|
28
|
+
route: string;
|
|
29
|
+
method: string;
|
|
30
|
+
field: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
};
|
|
33
|
+
interface BackendSource {
|
|
34
|
+
name: string;
|
|
35
|
+
resolve?: () => Promise<BackendProperty[]>;
|
|
36
|
+
rules?: FieldExtractionRule[];
|
|
37
|
+
}
|
|
21
38
|
interface UserConfig {
|
|
22
39
|
/**
|
|
23
40
|
* Absolute path to the root of the repository to analyze (e.g. the digital-api repo).
|
|
@@ -59,7 +76,7 @@ interface UserConfig {
|
|
|
59
76
|
*/
|
|
60
77
|
analysis: {
|
|
61
78
|
/** Backend identifiers used by the API project. */
|
|
62
|
-
backends: string
|
|
79
|
+
backends: Array<string | BackendSource>;
|
|
63
80
|
/**
|
|
64
81
|
* Glob patterns excluded from `analysis_files`.
|
|
65
82
|
* Use this to hide technical plumbing files that add noise to route review documents.
|
|
@@ -98,5 +115,5 @@ interface UserConfig {
|
|
|
98
115
|
};
|
|
99
116
|
}
|
|
100
117
|
//#endregion
|
|
101
|
-
export { FieldExtractionRule as n,
|
|
102
|
-
//# sourceMappingURL=types-
|
|
118
|
+
export { defineExpressionRule as a, FieldExtractionRule as i, BackendSource as n, UserConfig as r, BackendProperty as t };
|
|
119
|
+
//# sourceMappingURL=types-cYprdLUO.d.mts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as generateBackendTopologyArtifacts } from "../routeBackendTopologyService-
|
|
1
|
+
import { t as generateBackendTopologyArtifacts } from "../routeBackendTopologyService-DElirHSh.mjs";
|
|
2
2
|
import { parentPort, workerData } from "node:worker_threads";
|
|
3
3
|
//#region src/workers/routeBackendTopologyWorker.ts
|
|
4
4
|
const data = workerData;
|
|
@@ -97,9 +97,11 @@ Cette limite explique les valeurs `method: null` et `route: unknown` observées
|
|
|
97
97
|
Avec `@directus/sdk`, le code métier ne manipule généralement pas de route HTTP explicite. Il décrit une opération :
|
|
98
98
|
|
|
99
99
|
```ts
|
|
100
|
-
client.request(
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
client.request(
|
|
101
|
+
readItems("offers", {
|
|
102
|
+
fields: ["id", "label"]
|
|
103
|
+
})
|
|
104
|
+
);
|
|
103
105
|
```
|
|
104
106
|
|
|
105
107
|
La méthode et l'URL HTTP sont construites par le SDK. Chercher uniquement un littéral de route dans le code ne peut donc pas produire un résultat fiable.
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmflow/atlas",
|
|
3
|
-
"version": "3.4.0-beta.
|
|
3
|
+
"version": "3.4.0-beta.9",
|
|
4
4
|
"description": "API-to-backend mapping catalogue for Club Med Flow",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "romakita",
|
|
7
|
-
"email": "rom.lenzotti@gmail.com",
|
|
8
7
|
"bin": "./dist/bin/atlas.mjs",
|
|
9
8
|
"type": "module",
|
|
10
9
|
"main": "./dist/index.mjs",
|
|
@@ -46,6 +45,7 @@
|
|
|
46
45
|
"typescript": "^7.0.2",
|
|
47
46
|
"vitest": "^3.0.8"
|
|
48
47
|
},
|
|
48
|
+
"email": "rom.lenzotti@gmail.com",
|
|
49
49
|
"peerDependencies": {},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"tag": "beta"
|