@cmflow/atlas 3.4.0-beta.22 → 3.4.0-beta.24
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 +10 -8
- package/dist/{types-D5fngFrb.d.mts → UserConfig-aqAuodcW.d.mts} +30 -14
- package/dist/bin/atlas.mjs +83 -33
- package/dist/bin/atlas.mjs.map +1 -1
- package/dist/defineExpressionRule-GhkeHHT8.mjs.map +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{propertyExtractionService-BLat-Hsf.mjs → propertyExtractionService-CTQFyLdy.mjs} +45 -16
- package/dist/propertyExtractionService-CTQFyLdy.mjs.map +1 -0
- package/dist/{routeBackendTopologyService-CQyRyoSN.mjs → routeBackendTopologyService-NPO-ZHyY.mjs} +27 -18
- package/dist/{routeBackendTopologyService-CQyRyoSN.mjs.map → routeBackendTopologyService-NPO-ZHyY.mjs.map} +1 -1
- 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/{taskProgressService-CAC_RIoa.mjs → taskProgressService-BRoIJQbK.mjs} +8 -5
- package/dist/taskProgressService-BRoIJQbK.mjs.map +1 -0
- package/dist/workers/routeBackendTopologyWorker.mjs +23 -18
- package/dist/workers/routeBackendTopologyWorker.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/propertyExtractionService-BLat-Hsf.mjs.map +0 -1
- package/dist/taskProgressService-CAC_RIoa.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ atlas init
|
|
|
42
42
|
atlas --config /path/to/api/atlas.config.ts init
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
By default, Atlas loads `atlas.config.ts` from the directory where the command is launched. Use `--config` (or `-c`) to reference another configuration file. Use `--
|
|
45
|
+
By default, Atlas loads `atlas.config.ts` from the directory where the command is launched. Use `--config` (or `-c`) to reference another configuration file. Use `--analysis-dir` only to override the API source directory to analyze.
|
|
46
46
|
|
|
47
47
|
The configuration imports `defineConfig` from Atlas:
|
|
48
48
|
|
|
@@ -50,6 +50,7 @@ The configuration imports `defineConfig` from Atlas:
|
|
|
50
50
|
import { defineConfig } from "@cmflow/atlas";
|
|
51
51
|
|
|
52
52
|
export default defineConfig({
|
|
53
|
+
outputDir: "./tmp/atlas",
|
|
53
54
|
envs: {
|
|
54
55
|
XM_API_URL: "https://xm.example/openapi.json"
|
|
55
56
|
},
|
|
@@ -57,12 +58,13 @@ export default defineConfig({
|
|
|
57
58
|
openapiTimeoutMs: 60_000,
|
|
58
59
|
directusUrl: "https://cms.api.clubmed",
|
|
59
60
|
analysis: {
|
|
61
|
+
rootDir: "./app",
|
|
60
62
|
backends: ["ICC", "CMS", "CMS_B2C"]
|
|
61
63
|
}
|
|
62
64
|
});
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
Atlas reads `compilerOptions.paths` from the `tsconfig.json` in the directory where the command is run (`process.cwd()`) and uses them as module aliases. `
|
|
67
|
+
Atlas reads `compilerOptions.paths` from the `tsconfig.json` in the directory where the command is run (`process.cwd()`) and uses them as module aliases. `analysis.rootDir` only defines the source tree to analyze and may therefore point to a subdirectory. Add `resolver.alias` only to override or complement those aliases.
|
|
66
68
|
|
|
67
69
|
## Backend sources
|
|
68
70
|
|
|
@@ -294,8 +296,8 @@ DIRECTUS_TOKEN=replace-with-a-static-token
|
|
|
294
296
|
Generate the topology first, then the route catalogue:
|
|
295
297
|
|
|
296
298
|
```bash
|
|
297
|
-
atlas --
|
|
298
|
-
atlas --
|
|
299
|
+
atlas --analysis-dir /path/to/api generate:graph
|
|
300
|
+
atlas --analysis-dir /path/to/api generate:catalog --output .tmp/datasource-catalogue
|
|
299
301
|
```
|
|
300
302
|
|
|
301
303
|
Each route produces a YAML document and a `.graph.yaml` topology artifact. The catalogue requires a valid graph and leaves ambiguous fields marked `needs_review`.
|
|
@@ -303,14 +305,14 @@ Each route produces a YAML document and a `.graph.yaml` topology artifact. The c
|
|
|
303
305
|
Check configured coverage baselines:
|
|
304
306
|
|
|
305
307
|
```bash
|
|
306
|
-
atlas --
|
|
308
|
+
atlas --analysis-dir /path/to/api generate:test
|
|
307
309
|
```
|
|
308
310
|
|
|
309
311
|
Review unresolved mappings or run optional AI inference:
|
|
310
312
|
|
|
311
313
|
```bash
|
|
312
|
-
atlas --
|
|
313
|
-
atlas --
|
|
314
|
+
atlas --analysis-dir /path/to/api needs-review --sort percentage --limit 25
|
|
315
|
+
atlas --analysis-dir /path/to/api infer .tmp/datasource-catalogue
|
|
314
316
|
```
|
|
315
317
|
|
|
316
318
|
The inference pass only reads analysis files referenced by each route graph. Accepted suggestions remain marked `inferred`; suggestions below the configured confidence threshold are retained for manual review.
|
|
@@ -318,7 +320,7 @@ The inference pass only reads analysis files referenced by each route graph. Acc
|
|
|
318
320
|
Push route artifacts to Directus with an explicit write flag:
|
|
319
321
|
|
|
320
322
|
```bash
|
|
321
|
-
atlas --
|
|
323
|
+
atlas --analysis-dir /path/to/api push .tmp/datasource-catalogue --write
|
|
322
324
|
```
|
|
323
325
|
|
|
324
326
|
Without `--write`, the command performs a dry run. Use `clean-orphans` to inspect Directus links that no longer reference an API or backend property.
|
|
@@ -16,12 +16,21 @@ type FieldExtractionRule = {
|
|
|
16
16
|
};
|
|
17
17
|
declare function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule;
|
|
18
18
|
//#endregion
|
|
19
|
-
//#region src/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
//#region src/interfaces/Direction.d.ts
|
|
20
|
+
type Direction = "input" | "output";
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/interfaces/PropertyLocation.d.ts
|
|
23
|
+
type PropertyLocation = "query" | "path" | "header" | "body";
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/interfaces/PropertyMetadata.d.ts
|
|
26
|
+
interface PropertyMetadata {
|
|
27
|
+
direction?: Direction;
|
|
28
|
+
location?: PropertyLocation;
|
|
29
|
+
type?: string;
|
|
23
30
|
}
|
|
24
|
-
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/interfaces/BackendProperty.d.ts
|
|
33
|
+
type BackendProperty = PropertyMetadata & ({
|
|
25
34
|
document: string;
|
|
26
35
|
field: string;
|
|
27
36
|
description?: string;
|
|
@@ -30,20 +39,25 @@ type BackendProperty = {
|
|
|
30
39
|
method: string;
|
|
31
40
|
field: string;
|
|
32
41
|
description?: string;
|
|
33
|
-
};
|
|
42
|
+
});
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/interfaces/BackendSource.d.ts
|
|
34
45
|
interface BackendSource {
|
|
35
46
|
name: string;
|
|
36
47
|
resolve?: () => Promise<BackendProperty[]>;
|
|
37
48
|
rules?: FieldExtractionRule[];
|
|
38
49
|
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/config/interfaces/UserConfig.d.ts
|
|
52
|
+
interface NeutralExpressionMatcher {
|
|
53
|
+
prefix: string;
|
|
54
|
+
apiMapping?: boolean;
|
|
55
|
+
}
|
|
39
56
|
interface UserConfig {
|
|
40
57
|
/** Values injectable into backend source resolvers through `constant`. */
|
|
41
58
|
envs?: Record<string, unknown>;
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
* Can be set via the ATLAS_CWD environment variable.
|
|
45
|
-
*/
|
|
46
|
-
repoRoot?: string;
|
|
59
|
+
/** Directory where Atlas writes generated graph and catalogue artifacts, resolved from `process.cwd()`. */
|
|
60
|
+
outputDir: string;
|
|
47
61
|
/**
|
|
48
62
|
* Default OpenAPI document URL used to extract the public contract exposed by the API.
|
|
49
63
|
* The CLI can still override this value with `--openapi-url`.
|
|
@@ -80,10 +94,12 @@ interface UserConfig {
|
|
|
80
94
|
* Static analysis options used to keep generated artifacts focused on business-relevant files and fields.
|
|
81
95
|
*/
|
|
82
96
|
analysis: {
|
|
97
|
+
/** Directory containing the application source tree to analyze, resolved from `process.cwd()`. */
|
|
98
|
+
rootDir: string;
|
|
83
99
|
/** Backend identifiers used by the API project. */
|
|
84
100
|
backends: Array<string | BackendSource>;
|
|
85
101
|
/**
|
|
86
|
-
* Glob patterns excluded from `analysis_files`.
|
|
102
|
+
* Glob patterns, relative to the project working directory, excluded from `analysis_files`.
|
|
87
103
|
* Use this to hide technical plumbing files that add noise to route review documents.
|
|
88
104
|
*/
|
|
89
105
|
excluded: string[];
|
|
@@ -120,5 +136,5 @@ interface UserConfig {
|
|
|
120
136
|
};
|
|
121
137
|
}
|
|
122
138
|
//#endregion
|
|
123
|
-
export { defineExpressionRule as a, FieldExtractionRule as i, BackendSource as n,
|
|
124
|
-
//# sourceMappingURL=
|
|
139
|
+
export { defineExpressionRule as a, FieldExtractionRule as i, BackendSource as n, BackendProperty as r, UserConfig as t };
|
|
140
|
+
//# sourceMappingURL=UserConfig-aqAuodcW.d.mts.map
|
package/dist/bin/atlas.mjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
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
|
-
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-
|
|
6
|
-
import { a as
|
|
7
|
-
import { i as loadOpenApiDocument, n as extractOpenApiInputProperties, r as extractOpenApiOutputProperties } from "../propertyExtractionService-
|
|
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-BRoIJQbK.mjs";
|
|
6
|
+
import { a as resolveModulePath, c as filterAnalysisFiles, d as globby, i as matchesRouteSelector, l as shouldKeepAnalysisFile, n as generateBackendTopologyArtifactsInWorkers, o as inferBackendNameFromFile, s as isKnownBackendType, u as normalizeFilePath } from "../routeBackendTopologyService-NPO-ZHyY.mjs";
|
|
7
|
+
import { i as loadOpenApiDocument, n as extractOpenApiInputProperties, r as extractOpenApiOutputProperties } from "../propertyExtractionService-CTQFyLdy.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";
|
|
@@ -4154,7 +4154,7 @@ function collectReachableBackendFunctionNames(entrySourceFiles, backendSourceFil
|
|
|
4154
4154
|
}
|
|
4155
4155
|
return reachable;
|
|
4156
4156
|
}
|
|
4157
|
-
function resolveHandlerFile(routeSourceFile, handlerRef, resolvePath = (sourceFilePath, moduleSpecifier) => resolveModulePath(sourceFilePath, moduleSpecifier, userConfig.resolver.alias, userConfig.
|
|
4157
|
+
function resolveHandlerFile(routeSourceFile, handlerRef, resolvePath = (sourceFilePath, moduleSpecifier) => resolveModulePath(sourceFilePath, moduleSpecifier, userConfig.resolver.alias, userConfig.analysis.rootDir)) {
|
|
4158
4158
|
if (!handlerRef) return {};
|
|
4159
4159
|
if (!handlerRef.includes(".")) {
|
|
4160
4160
|
if (routeSourceFile.getFunction(handlerRef) || routeSourceFile.getVariableDeclaration(handlerRef)) return {
|
|
@@ -4184,7 +4184,7 @@ function resolveHandlerFile(routeSourceFile, handlerRef, resolvePath = (sourceFi
|
|
|
4184
4184
|
async function yieldToEventLoop() {
|
|
4185
4185
|
await new Promise((resolve) => setImmediate(resolve));
|
|
4186
4186
|
}
|
|
4187
|
-
async function collectDependencyFiles(project, entryFilePath, routeLabel, resolvePath = (sourceFilePath, moduleSpecifier) => resolveModulePath(sourceFilePath, moduleSpecifier, userConfig.resolver.alias, userConfig.
|
|
4187
|
+
async function collectDependencyFiles(project, entryFilePath, routeLabel, resolvePath = (sourceFilePath, moduleSpecifier) => resolveModulePath(sourceFilePath, moduleSpecifier, userConfig.resolver.alias, userConfig.analysis.rootDir)) {
|
|
4188
4188
|
const queue = [entryFilePath];
|
|
4189
4189
|
const visited = /* @__PURE__ */ new Set();
|
|
4190
4190
|
while (queue.length) {
|
|
@@ -4214,7 +4214,7 @@ async function analyzeCodebaseRouteContracts(params) {
|
|
|
4214
4214
|
const { cwd, openApiDocument: swagger, selectedRouteKeys, routeContracts, onDocument, routeAnalysisScope } = params;
|
|
4215
4215
|
const backendProperties = await resolveBackendProperties(userConfig.analysis.backends);
|
|
4216
4216
|
taskProgressService.log("Discovering route files");
|
|
4217
|
-
const routeFiles = await globby(["
|
|
4217
|
+
const routeFiles = await globby(["_api/**/routes.@(js|ts)", "legacy/**/routes.@(js|ts)"], {
|
|
4218
4218
|
cwd,
|
|
4219
4219
|
absolute: true
|
|
4220
4220
|
});
|
|
@@ -4250,7 +4250,7 @@ async function analyzeCodebaseRouteContracts(params) {
|
|
|
4250
4250
|
const resolveCachedModulePath = (sourceFilePath, moduleSpecifier) => {
|
|
4251
4251
|
const key = `${sourceFilePath}${moduleSpecifier}`;
|
|
4252
4252
|
if (resolvedModulePaths.has(key)) return resolvedModulePaths.get(key);
|
|
4253
|
-
const resolved = resolveModulePath(sourceFilePath, moduleSpecifier, userConfig.resolver.alias,
|
|
4253
|
+
const resolved = resolveModulePath(sourceFilePath, moduleSpecifier, userConfig.resolver.alias, process.cwd());
|
|
4254
4254
|
resolvedModulePaths.set(key, resolved);
|
|
4255
4255
|
return resolved;
|
|
4256
4256
|
};
|
|
@@ -12153,7 +12153,7 @@ async function readCatalogueFromDirectory(inputDir) {
|
|
|
12153
12153
|
//#endregion
|
|
12154
12154
|
//#region src/services/directus/pushCatalogueDirectoryToDirectus.ts
|
|
12155
12155
|
async function pushCatalogueDirectoryToDirectus(params) {
|
|
12156
|
-
const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
|
|
12156
|
+
const inputPath = path.resolve(params.cwd, params.catalogueDirectory ?? getUserConfig().outputDir);
|
|
12157
12157
|
taskProgressService.report("Loading and reconstructing route YAML documents");
|
|
12158
12158
|
const { catalogue, files } = await readCatalogueFromDirectory(inputPath);
|
|
12159
12159
|
taskProgressService.report(`${files.length} route documents loaded, ${catalogue.backend_routes.length} backend routes reconstructed`);
|
|
@@ -12187,7 +12187,6 @@ function parseRouteSelector(value) {
|
|
|
12187
12187
|
|
|
12188
12188
|
//#endregion
|
|
12189
12189
|
//#region src/commands/push.ts
|
|
12190
|
-
const DEFAULT_CATALOGUE_DIRECTORY = ".tmp/datasource-catalogue";
|
|
12191
12190
|
function formatPushError(error) {
|
|
12192
12191
|
if (error instanceof Error) return error.message;
|
|
12193
12192
|
if (!isRecord$1(error)) return String(error);
|
|
@@ -12255,15 +12254,15 @@ function createPushStepClassifier() {
|
|
|
12255
12254
|
};
|
|
12256
12255
|
};
|
|
12257
12256
|
}
|
|
12258
|
-
var push_default = (program) => void program.command("push").argument("[catalogue-directory-or-route]", "Directory or route such as \"GET /v1/offers\""
|
|
12257
|
+
var push_default = (program) => void program.command("push").argument("[catalogue-directory-or-route]", "Directory or route such as \"GET /v1/offers\"").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) => {
|
|
12259
12258
|
intro("Datasource catalogue push");
|
|
12260
12259
|
const { cwd } = getUserConfig();
|
|
12261
12260
|
const progress = taskProgressService.createStepProgress(createPushStepClassifier());
|
|
12262
12261
|
try {
|
|
12263
|
-
const positionalRouteSelector = isRouteSelector(catalogueDirectoryOrRoute) ? parseRouteSelector(catalogueDirectoryOrRoute) : void 0;
|
|
12262
|
+
const positionalRouteSelector = catalogueDirectoryOrRoute && isRouteSelector(catalogueDirectoryOrRoute) ? parseRouteSelector(catalogueDirectoryOrRoute) : void 0;
|
|
12264
12263
|
if (options.route && positionalRouteSelector) throw new Error("Specify the route either as the positional argument or with --route, not both");
|
|
12265
12264
|
const routeSelector = options.route ? parseRouteSelector(options.route) : positionalRouteSelector;
|
|
12266
|
-
const catalogueDirectory = positionalRouteSelector ?
|
|
12265
|
+
const catalogueDirectory = positionalRouteSelector ? void 0 : catalogueDirectoryOrRoute;
|
|
12267
12266
|
const pushResult = await progress.execute(() => pushCatalogueDirectoryToDirectus({
|
|
12268
12267
|
cwd,
|
|
12269
12268
|
catalogueDirectory,
|
|
@@ -12435,6 +12434,28 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
|
|
|
12435
12434
|
};
|
|
12436
12435
|
}
|
|
12437
12436
|
|
|
12437
|
+
//#endregion
|
|
12438
|
+
//#region src/services/backendMetadataArtifactService.ts
|
|
12439
|
+
function backendMetadataFileName(backend) {
|
|
12440
|
+
return `${backend.toLowerCase()}.yaml`;
|
|
12441
|
+
}
|
|
12442
|
+
async function writeBackendMetadataArtifacts(params) {
|
|
12443
|
+
const directory = path.join(params.outputDir, "backends");
|
|
12444
|
+
await fsPromises.mkdir(directory, { recursive: true });
|
|
12445
|
+
return Promise.all(params.sources.map(async (source) => {
|
|
12446
|
+
const filePath = path.join(directory, backendMetadataFileName(source.name));
|
|
12447
|
+
const properties = params.propertiesByBackend.get(source.name) || [];
|
|
12448
|
+
await fsPromises.writeFile(filePath, `${(0, import_dist$1.stringify)({
|
|
12449
|
+
schema_version: 1,
|
|
12450
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12451
|
+
backend: source.name,
|
|
12452
|
+
resolved: Boolean(source.resolve),
|
|
12453
|
+
properties
|
|
12454
|
+
})}\n`);
|
|
12455
|
+
return filePath;
|
|
12456
|
+
}));
|
|
12457
|
+
}
|
|
12458
|
+
|
|
12438
12459
|
//#endregion
|
|
12439
12460
|
//#region src/commands/generate.ts
|
|
12440
12461
|
async function generateHandler(cwd, routeArgument, options) {
|
|
@@ -12443,7 +12464,7 @@ async function generateHandler(cwd, routeArgument, options) {
|
|
|
12443
12464
|
const routeSelector = parseRouteSelector(routeArgument);
|
|
12444
12465
|
const profile = createAnalysisProfile(Boolean(options.profile));
|
|
12445
12466
|
try {
|
|
12446
|
-
const outputDir = path.resolve(cwd, options.output);
|
|
12467
|
+
const outputDir = path.resolve(cwd, options.output ?? getUserConfig().outputDir);
|
|
12447
12468
|
await progress$3.run({
|
|
12448
12469
|
id: "artifacts",
|
|
12449
12470
|
title: "Preparing route documents",
|
|
@@ -12457,6 +12478,20 @@ async function generateHandler(cwd, routeArgument, options) {
|
|
|
12457
12478
|
completedTitle: "OpenAPI contract loaded"
|
|
12458
12479
|
}, () => loadOpenApiDocument(options.openapiUrl));
|
|
12459
12480
|
profile.add("OpenAPI", performance.now() - openApiStartedAt);
|
|
12481
|
+
const backendMetadataStartedAt = performance.now();
|
|
12482
|
+
await progress$3.run({
|
|
12483
|
+
id: "backend-metadata",
|
|
12484
|
+
title: "Loading backend metadata",
|
|
12485
|
+
completedTitle: "Backend metadata loaded"
|
|
12486
|
+
}, async () => {
|
|
12487
|
+
const config = getUserConfig();
|
|
12488
|
+
await writeBackendMetadataArtifacts({
|
|
12489
|
+
outputDir,
|
|
12490
|
+
sources: config.analysis.backends,
|
|
12491
|
+
propertiesByBackend: await resolveBackendProperties(config.analysis.backends)
|
|
12492
|
+
});
|
|
12493
|
+
});
|
|
12494
|
+
profile.add("Backend metadata", performance.now() - backendMetadataStartedAt);
|
|
12460
12495
|
const selectedRouteKeys = routeSelector ? /* @__PURE__ */ new Set([stableKey(routeSelector.method, routeSelector.path)]) : void 0;
|
|
12461
12496
|
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);
|
|
12462
12497
|
const analysisStartedAt = performance.now();
|
|
@@ -12474,7 +12509,7 @@ async function generateHandler(cwd, routeArgument, options) {
|
|
|
12474
12509
|
selectedRouteKeys,
|
|
12475
12510
|
routeAnalysisScope: async ({ method, path: routePath }) => {
|
|
12476
12511
|
try {
|
|
12477
|
-
return await loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePath);
|
|
12512
|
+
return await loadBackendTopologyAnalysisScope(outputDir, process.cwd(), method, routePath);
|
|
12478
12513
|
} catch (error) {
|
|
12479
12514
|
const message = error instanceof Error ? error.message : String(error);
|
|
12480
12515
|
taskProgressService.log(`Warning: ${message} Route skipped.`);
|
|
@@ -12486,7 +12521,7 @@ async function generateHandler(cwd, routeArgument, options) {
|
|
|
12486
12521
|
analyzedRoutes += 1;
|
|
12487
12522
|
analysisProgress.advance(1, `Analyzing · [${analyzedRoutes}/${totalRoutes}] routes · ${document.method} ${document.path} ...`);
|
|
12488
12523
|
const routeCatalogue = await buildCatalogue([document]);
|
|
12489
|
-
await writeRouteReviewDocument(routeCatalogue, outputDir, cwd, document.key);
|
|
12524
|
+
await writeRouteReviewDocument(routeCatalogue, outputDir, process.cwd(), document.key);
|
|
12490
12525
|
profile.add("Artifacts", performance.now() - artifactStartedAt);
|
|
12491
12526
|
}
|
|
12492
12527
|
});
|
|
@@ -12514,7 +12549,7 @@ async function generateHandler(cwd, routeArgument, options) {
|
|
|
12514
12549
|
process.exit(1);
|
|
12515
12550
|
}
|
|
12516
12551
|
}
|
|
12517
|
-
var generate_default = (program) => void program.command("generate:catalog").argument("[route]", "Route selector in the format \"GET /v1/products\"").option("-o, --output <directory>", "Directory where catalogue artifacts are generated
|
|
12552
|
+
var generate_default = (program) => void program.command("generate:catalog").argument("[route]", "Route selector in the format \"GET /v1/products\"").option("-o, --output <directory>", "Directory where catalogue artifacts are generated, overriding outputDir").option("--openapi-url <url>", "OpenAPI document URL").option("--profile", "Display elapsed time by generation stage").description("Analyze the codebase and generate one YAML review document per route").action((routeArgument, options) => {
|
|
12518
12553
|
const config = getUserConfig();
|
|
12519
12554
|
return generateHandler(config.cwd, routeArgument, {
|
|
12520
12555
|
...options,
|
|
@@ -12559,7 +12594,19 @@ async function generateGraphHandler(cwd, routeArgument, options) {
|
|
|
12559
12594
|
});
|
|
12560
12595
|
try {
|
|
12561
12596
|
const routeSelector = parseRouteSelector(routeArgument);
|
|
12562
|
-
const outputDir = path.resolve(cwd, options.output);
|
|
12597
|
+
const outputDir = path.resolve(cwd, options.output ?? getUserConfig().outputDir);
|
|
12598
|
+
const config = getUserConfig();
|
|
12599
|
+
await progress$2.run({
|
|
12600
|
+
id: "backend-metadata",
|
|
12601
|
+
title: "Loading backend metadata",
|
|
12602
|
+
completedTitle: "Backend metadata loaded"
|
|
12603
|
+
}, async () => {
|
|
12604
|
+
await writeBackendMetadataArtifacts({
|
|
12605
|
+
outputDir,
|
|
12606
|
+
sources: config.analysis.backends,
|
|
12607
|
+
propertiesByBackend: await resolveBackendProperties(config.analysis.backends)
|
|
12608
|
+
});
|
|
12609
|
+
});
|
|
12563
12610
|
const workerCount = options.workers ?? 2;
|
|
12564
12611
|
const files = [];
|
|
12565
12612
|
let routeProgress;
|
|
@@ -12603,7 +12650,7 @@ async function generateGraphHandler(cwd, routeArgument, options) {
|
|
|
12603
12650
|
process.exitCode = 1;
|
|
12604
12651
|
}
|
|
12605
12652
|
}
|
|
12606
|
-
var generateGraph_default = (program) => void program.command("generate:graph").argument("[route]", "Optional route selector in the format \"GET /v1/products\"").option("-o, --output <directory>", "Directory where backend graph artifacts are generated
|
|
12653
|
+
var generateGraph_default = (program) => void program.command("generate:graph").argument("[route]", "Optional route selector in the format \"GET /v1/products\"").option("-o, --output <directory>", "Directory where backend graph artifacts are generated, overriding outputDir").option("-w, --workers <count>", "Maximum number of topology workers", parseWorkerCount$1, 2).description("Generate weighted call graphs from API routes to backend types").action((routeArgument, options) => {
|
|
12607
12654
|
const { cwd } = getUserConfig();
|
|
12608
12655
|
return generateGraphHandler(cwd, routeArgument, options);
|
|
12609
12656
|
});
|
|
@@ -29859,6 +29906,7 @@ function formatInferenceFailure(error) {
|
|
|
29859
29906
|
return (error instanceof Error ? error.message : String(error)).split(" Raw response:")[0];
|
|
29860
29907
|
}
|
|
29861
29908
|
async function inferCatalogueDirectory(params) {
|
|
29909
|
+
const workspaceRoot = params.workspaceRoot || params.cwd;
|
|
29862
29910
|
const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
|
|
29863
29911
|
const outputPath = path.resolve(params.cwd, params.outputDirectory || params.catalogueDirectory);
|
|
29864
29912
|
taskProgressService.report("Loading route YAML documents");
|
|
@@ -29882,7 +29930,7 @@ async function inferCatalogueDirectory(params) {
|
|
|
29882
29930
|
const unresolvedProperties = [...catalogue.route_input_properties, ...catalogue.route_output_properties].filter((property) => property.evidence_status === "needs_review").filter((property) => !selectedRoute || property.route_key === selectedRoute.key);
|
|
29883
29931
|
if (outputPath !== inputPath) {
|
|
29884
29932
|
taskProgressService.report("Copying source YAML documents to the output directory");
|
|
29885
|
-
await writeCatalogueArtifacts(catalogue, outputPath,
|
|
29933
|
+
await writeCatalogueArtifacts(catalogue, outputPath, workspaceRoot);
|
|
29886
29934
|
}
|
|
29887
29935
|
if (!unresolvedProperties.length) return {
|
|
29888
29936
|
inputPath,
|
|
@@ -29953,12 +30001,12 @@ async function inferCatalogueDirectory(params) {
|
|
|
29953
30001
|
input: catalogue.route_input_properties.filter((property) => property.route_key === routeKey).map((property) => ({
|
|
29954
30002
|
path: property.field,
|
|
29955
30003
|
description: property.description,
|
|
29956
|
-
|
|
30004
|
+
location: "body"
|
|
29957
30005
|
})),
|
|
29958
30006
|
output: catalogue.route_output_properties.filter((property) => property.route_key === routeKey).map((property) => ({
|
|
29959
30007
|
path: property.field,
|
|
29960
30008
|
description: property.description,
|
|
29961
|
-
|
|
30009
|
+
location: "body"
|
|
29962
30010
|
}))
|
|
29963
30011
|
}]]);
|
|
29964
30012
|
reportRoute("Static analysis refresh");
|
|
@@ -29968,7 +30016,7 @@ async function inferCatalogueDirectory(params) {
|
|
|
29968
30016
|
routeContracts,
|
|
29969
30017
|
routeAnalysisScope: async ({ method, path: routePath }) => {
|
|
29970
30018
|
try {
|
|
29971
|
-
return await loadBackendTopologyAnalysisScope(inputPath,
|
|
30019
|
+
return await loadBackendTopologyAnalysisScope(inputPath, workspaceRoot, method, routePath);
|
|
29972
30020
|
} catch (error) {
|
|
29973
30021
|
const message = error instanceof Error ? error.message : String(error);
|
|
29974
30022
|
graphErrors.set(`${method} ${routePath}`, message);
|
|
@@ -29995,7 +30043,7 @@ async function inferCatalogueDirectory(params) {
|
|
|
29995
30043
|
if (!remainingRouteProperties.length) {
|
|
29996
30044
|
refreshStats(catalogue);
|
|
29997
30045
|
reportRoute("Update artifacts");
|
|
29998
|
-
await writeRouteReviewDocument(catalogue, outputPath,
|
|
30046
|
+
await writeRouteReviewDocument(catalogue, outputPath, workspaceRoot, routeKey);
|
|
29999
30047
|
params.onWorkerProgress?.({
|
|
30000
30048
|
workerId,
|
|
30001
30049
|
total: routeKeys.length,
|
|
@@ -30020,7 +30068,7 @@ async function inferCatalogueDirectory(params) {
|
|
|
30020
30068
|
taskProgressService.log(`Warning: AI inference failed for ${route.method} ${route.path}: ${formatInferenceFailure(error)}. Route kept for review.`);
|
|
30021
30069
|
refreshStats(catalogue);
|
|
30022
30070
|
reportRoute("Update artifacts");
|
|
30023
|
-
await writeRouteReviewDocument(catalogue, outputPath,
|
|
30071
|
+
await writeRouteReviewDocument(catalogue, outputPath, workspaceRoot, routeKey);
|
|
30024
30072
|
params.onWorkerProgress?.({
|
|
30025
30073
|
workerId,
|
|
30026
30074
|
total: routeKeys.length,
|
|
@@ -30048,7 +30096,7 @@ async function inferCatalogueDirectory(params) {
|
|
|
30048
30096
|
}
|
|
30049
30097
|
refreshStats(catalogue);
|
|
30050
30098
|
reportRoute("Update artifacts");
|
|
30051
|
-
await writeRouteReviewDocument(catalogue, outputPath,
|
|
30099
|
+
await writeRouteReviewDocument(catalogue, outputPath, workspaceRoot, routeKey);
|
|
30052
30100
|
params.onWorkerProgress?.({
|
|
30053
30101
|
workerId,
|
|
30054
30102
|
total: routeKeys.length,
|
|
@@ -30152,6 +30200,7 @@ var infer_default = (program) => void program.command("infer").argument("[route-
|
|
|
30152
30200
|
let completedRoutes = 0;
|
|
30153
30201
|
const result = await progress$1.execute(() => inferCatalogueDirectory({
|
|
30154
30202
|
cwd: config.cwd,
|
|
30203
|
+
workspaceRoot: process.cwd(),
|
|
30155
30204
|
catalogueDirectory,
|
|
30156
30205
|
routeSelector,
|
|
30157
30206
|
outputDirectory: options.output,
|
|
@@ -30214,7 +30263,7 @@ const configTemplate = `import { defineConfig } from "@cmflow/atlas";
|
|
|
30214
30263
|
|
|
30215
30264
|
export default defineConfig({
|
|
30216
30265
|
envs: {},
|
|
30217
|
-
|
|
30266
|
+
outputDir: "./.tmp/atlas",
|
|
30218
30267
|
openapiUrl: "https://api.example.com/openapi.json",
|
|
30219
30268
|
openapiTimeoutMs: 60_000,
|
|
30220
30269
|
directusUrl: "https://cms.api.clubmed",
|
|
@@ -30226,6 +30275,7 @@ export default defineConfig({
|
|
|
30226
30275
|
workers: 2
|
|
30227
30276
|
},
|
|
30228
30277
|
analysis: {
|
|
30278
|
+
rootDir: "./app",
|
|
30229
30279
|
backends: [],
|
|
30230
30280
|
excluded: [],
|
|
30231
30281
|
transversalInputs: [],
|
|
@@ -30476,8 +30526,8 @@ async function readChangedFiles(filePath) {
|
|
|
30476
30526
|
//#region src/commands/reportChanged.ts
|
|
30477
30527
|
async function reportChangedHandler(cwd, options) {
|
|
30478
30528
|
const config = getUserConfig();
|
|
30479
|
-
const outputDirectory = path.resolve(cwd, options.output);
|
|
30480
|
-
const changedFiles = await readChangedFiles(path.resolve(cwd, options.changedFiles));
|
|
30529
|
+
const outputDirectory = path.resolve(cwd, options.output ?? config.outputDir);
|
|
30530
|
+
const changedFiles = await readChangedFiles(path.resolve(process.cwd(), options.changedFiles));
|
|
30481
30531
|
const impactedRoutes = collectImpactedRoutes({
|
|
30482
30532
|
changedFiles,
|
|
30483
30533
|
graphs: await readRouteGraphs(outputDirectory)
|
|
@@ -30490,7 +30540,7 @@ async function reportChangedHandler(cwd, options) {
|
|
|
30490
30540
|
cwd,
|
|
30491
30541
|
openApiDocument,
|
|
30492
30542
|
selectedRouteKeys: /* @__PURE__ */ new Set([stableKey(route.method, route.path)]),
|
|
30493
|
-
routeAnalysisScope: ({ method, path: routePath }) => loadBackendTopologyAnalysisScope(outputDirectory, cwd, method, routePath)
|
|
30543
|
+
routeAnalysisScope: ({ method, path: routePath }) => loadBackendTopologyAnalysisScope(outputDirectory, process.cwd(), method, routePath)
|
|
30494
30544
|
});
|
|
30495
30545
|
if (documents.length !== 1) throw new Error("Route was not found in the OpenAPI contract");
|
|
30496
30546
|
const catalogue = await buildCatalogue(documents);
|
|
@@ -30522,12 +30572,12 @@ async function reportChangedHandler(cwd, options) {
|
|
|
30522
30572
|
changedFiles,
|
|
30523
30573
|
routes: coverages
|
|
30524
30574
|
});
|
|
30525
|
-
const reportPath = path.resolve(cwd, options.report);
|
|
30575
|
+
const reportPath = path.resolve(cwd, options.report ?? path.join(config.outputDir, "changed-routes-report.md"));
|
|
30526
30576
|
await fsPromises.mkdir(path.dirname(reportPath), { recursive: true });
|
|
30527
30577
|
await fsPromises.writeFile(reportPath, `${report}\n`, "utf8");
|
|
30528
30578
|
note(report, "Datasource mapping report");
|
|
30529
30579
|
}
|
|
30530
|
-
var reportChanged_default = (program) => void program.command("report:changed").requiredOption("--changed-files <file>", "Newline-delimited list of changed repository files").option("-o, --output <directory>", "Directory containing generated graph artifacts
|
|
30580
|
+
var reportChanged_default = (program) => void program.command("report:changed").requiredOption("--changed-files <file>", "Newline-delimited list of changed repository files").option("-o, --output <directory>", "Directory containing generated graph artifacts, overriding outputDir").option("--report <file>", "Markdown report output path; defaults to outputDir/changed-routes-report.md").description("Report deterministic mapping coverage for routes impacted by changed files").action(async (options) => {
|
|
30531
30581
|
intro("Datasource changed-route report");
|
|
30532
30582
|
const { cwd } = getUserConfig();
|
|
30533
30583
|
try {
|
|
@@ -30543,9 +30593,9 @@ var reportChanged_default = (program) => void program.command("report:changed").
|
|
|
30543
30593
|
//#region src/bin/atlas.ts
|
|
30544
30594
|
const program = new Command();
|
|
30545
30595
|
program.version("3.4.0-alpha.1");
|
|
30546
|
-
program.name("atlas").description("Manage the API-to-backend mapping catalogue").option("--
|
|
30596
|
+
program.name("atlas").description("Manage the API-to-backend mapping catalogue").option("--analysis-dir <path>", "Directory containing the API source tree to analyze").option("-c, --config <path>", "Path to atlas.config.ts", path.resolve(process.cwd(), "atlas.config.ts")).hook("preAction", async (_thisCommand, actionCommand) => {
|
|
30547
30597
|
if (actionCommand.name() === "init") return;
|
|
30548
|
-
const config = await userConfig$1(program.opts().config, program.opts().
|
|
30598
|
+
const config = await userConfig$1(program.opts().config, program.opts().analysisDir);
|
|
30549
30599
|
setUserConfig(config);
|
|
30550
30600
|
});
|
|
30551
30601
|
init_default(program, program);
|