@cmflow/atlas 3.4.0-beta.6 → 3.4.0-beta.8

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.
@@ -1,156 +1,23 @@
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-DkNyCtKt.mjs";
2
3
  import { Command, InvalidArgumentError, Option } from "commander";
3
4
  import path from "node:path";
4
- import fs from "node:fs";
5
- import { pathToFileURL } from "node:url";
6
5
  import { Node, Project, SyntaxKind } from "ts-morph";
7
- import { cancel, intro, log, note, outro, progress, spinner } from "@clack/prompts";
8
- import fs$1 from "node:fs/promises";
6
+ import { cancel, intro, isCancel, log, note, outro, progress, select } from "@clack/prompts";
7
+ import fs from "node:fs/promises";
9
8
  import { createDirectus, createItem, deleteItem, readItems, rest, staticToken, updateItem } from "@directus/sdk";
10
- import { AsyncLocalStorage } from "node:async_hooks";
11
9
  import { globby } from "globby";
12
- import { minimatch } from "minimatch";
13
10
  import { createHash } from "node:crypto";
14
11
  import { parse, stringify } from "yaml";
15
- import { Worker } from "node:worker_threads";
16
12
  import { NoObjectGeneratedError, Output, generateText } from "ai";
17
13
  import { z } from "zod";
18
14
  import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
19
- //#region src/utils/config.ts
20
- let _config;
21
- function setUserConfig(config) {
22
- _config = config;
23
- }
24
- function getUserConfig() {
25
- if (!_config) throw new Error("Atlas config not loaded. Run Atlas from a directory containing atlas.config.ts or pass --config.");
26
- return _config;
27
- }
28
- function getTsconfigAliases(repoRoot) {
29
- const tsconfigPath = path.join(repoRoot, "tsconfig.json");
30
- if (!fs.existsSync(tsconfigPath)) return {};
31
- const paths = new Project({
32
- tsConfigFilePath: tsconfigPath,
33
- skipAddingFilesFromTsConfig: true
34
- }).getCompilerOptions().paths ?? {};
35
- return Object.fromEntries(Object.entries(paths).flatMap(([alias, targets]) => {
36
- const target = targets?.[0];
37
- if (!target) return [];
38
- return [[alias.replace(/\/\*$/, ""), target.replace(/^\.\//, "").replace(/\/\*$/, "")]];
39
- }));
40
- }
41
- async function loadAtlasConfig(configPath, projectRoot) {
42
- const filePath = path.resolve(configPath);
43
- try {
44
- let mod;
45
- if (filePath.endsWith(".ts")) {
46
- const { tsImport } = await import("tsx/esm/api");
47
- mod = await tsImport(filePath, import.meta.url);
48
- } else mod = await import(pathToFileURL(filePath).href);
49
- const config = mod.default ?? mod;
50
- const repoRoot = projectRoot || config.repoRoot || process.cwd();
51
- return {
52
- ...config,
53
- repoRoot,
54
- cwd: repoRoot,
55
- resolver: { alias: {
56
- ...getTsconfigAliases(repoRoot),
57
- ...config.resolver?.alias
58
- } }
59
- };
60
- } catch (error) {
61
- throw new Error(`Unable to load Atlas configuration at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
62
- }
63
- }
64
- //#endregion
65
- //#region src/services/taskProgressService.ts
66
- function formatDuration(durationMs) {
67
- if (durationMs < 1e3) return `${durationMs}ms`;
68
- return `${(durationMs / 1e3).toFixed(1)}s`;
69
- }
70
- var TaskProgressService = class {
71
- #storage = new AsyncLocalStorage();
72
- attach(sink, task) {
73
- return this.#storage.run(sink, task);
74
- }
75
- log(message) {
76
- this.#storage.getStore()?.log(message);
77
- }
78
- report(message) {
79
- const sink = this.#storage.getStore();
80
- (sink?.report || sink?.log)?.(message);
81
- }
82
- createStepProgress(classify = (message) => ({
83
- id: message,
84
- title: message
85
- })) {
86
- const progress = spinner();
87
- let active;
88
- const finishActive = (label) => {
89
- if (!active) return;
90
- const duration = formatDuration(Date.now() - active.startedAt);
91
- progress.stop(`${label || active.completedTitle || `${active.title} completed`} (${duration})`);
92
- active = void 0;
93
- };
94
- const start = (step) => {
95
- if (active?.id === step.id) {
96
- progress.message(step.detail ? `${step.title}: ${step.detail}` : step.title);
97
- return;
98
- }
99
- finishActive();
100
- active = {
101
- id: step.id,
102
- title: step.title,
103
- completedTitle: step.completedTitle,
104
- startedAt: Date.now()
105
- };
106
- progress.start(step.detail ? `${step.title}: ${step.detail}` : step.title);
107
- };
108
- const execute = (task) => this.attach({
109
- log: (message) => progress.message(active ? `${active.title}: ${message}` : message),
110
- report: (message) => start(classify(message))
111
- }, task);
112
- return {
113
- report(message) {
114
- start(classify(message));
115
- },
116
- start,
117
- execute,
118
- run: async (step, task) => {
119
- start(step);
120
- await new Promise((resolve) => setImmediate(resolve));
121
- const result = await execute(task);
122
- finishActive();
123
- return result;
124
- },
125
- finish(label) {
126
- finishActive(label);
127
- },
128
- fail(label) {
129
- if (!active) return;
130
- const duration = formatDuration(Date.now() - active.startedAt);
131
- progress.stop(`${label} (${duration})`);
132
- active = void 0;
133
- }
134
- };
135
- }
136
- };
137
- const taskProgressService = new TaskProgressService();
138
- //#endregion
139
15
  //#region src/utils/catalogueStats.ts
140
16
  function calculateNeedsReviewPercentage(inputProperties, outputProperties, needsReview) {
141
17
  const properties = inputProperties + outputProperties;
142
18
  return properties ? Number((needsReview / properties * 100).toFixed(2)) : 0;
143
19
  }
144
20
  //#endregion
145
- //#region src/services/analysisFileService.ts
146
- function shouldKeepAnalysisFile(filePath) {
147
- const normalizedProjectPath = filePath.replaceAll(path.sep, "/").replace(/^.*?(app\/)/, "app/");
148
- return !getUserConfig().analysis.excluded.some((pattern) => minimatch(normalizedProjectPath, pattern, { dot: true }));
149
- }
150
- function filterAnalysisFiles(filePaths) {
151
- return filePaths.filter(shouldKeepAnalysisFile);
152
- }
153
- //#endregion
154
21
  //#region src/services/useCaseAnalysisService.ts
155
22
  function normalizeCodeText(value) {
156
23
  return value.replace(/\s+/g, " ").replace(/;$/, "").trim();
@@ -181,16 +48,16 @@ function getAggregationTarget(call) {
181
48
  let current = call;
182
49
  while (current) {
183
50
  const parent = current.getParent();
184
- if (!parent) return void 0;
51
+ if (!parent) return;
185
52
  if (Node.isVariableDeclaration(parent)) return normalizeCodeText(parent.getName());
186
53
  if (Node.isReturnStatement(parent)) return "return";
187
- if (Node.isStatement(parent)) return void 0;
54
+ if (Node.isStatement(parent)) return;
188
55
  current = parent;
189
56
  }
190
57
  }
191
58
  function getAggregationCondition(call) {
192
59
  const callback = call.getArguments().find((argument) => Node.isArrowFunction(argument) || Node.isFunctionExpression(argument));
193
- if (!callback || !Node.isArrowFunction(callback) && !Node.isFunctionExpression(callback)) return void 0;
60
+ if (!callback || !Node.isArrowFunction(callback) && !Node.isFunctionExpression(callback)) return;
194
61
  const body = callback.getBody();
195
62
  if (!Node.isBlock(body)) return normalizeCodeText(body.getText());
196
63
  const condition = body.getDescendantsOfKind(SyntaxKind.IfStatement)[0]?.getExpression();
@@ -268,29 +135,80 @@ var AnalysisCacheService = class {
268
135
  };
269
136
  const analysisCacheService = new AnalysisCacheService();
270
137
  //#endregion
271
- //#region src/services/openapi/downloadService.ts
272
- async function downloadOpenApiDocument(url, timeoutMs = getUserConfig().openapiTimeoutMs) {
273
- const response = await fetch(url, {
274
- headers: { accept: "application/json" },
275
- signal: AbortSignal.timeout(timeoutMs)
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);
276
161
  });
277
- if (!response.ok) throw new Error(`Unable to fetch OpenAPI document from ${url}: ${response.status} ${response.statusText}`);
278
- const reader = response.body?.getReader();
279
- if (!reader) return response.json();
280
- const decoder = new TextDecoder();
281
- let content = "";
282
- while (true) {
283
- const { done, value } = await reader.read();
284
- if (done) break;
285
- content += decoder.decode(value, { stream: true });
286
- taskProgressService.log(`Downloading (${(content.length / 1048576).toFixed(1)} MB)`);
287
- }
288
- taskProgressService.log("Parsing document");
289
- return JSON.parse(content + decoder.decode());
290
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
291
201
  async function loadOpenApiDocument(url, timeoutMs) {
292
202
  try {
293
- return await downloadOpenApiDocument(url, timeoutMs);
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);
294
212
  } catch (error) {
295
213
  if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) throw new Error(`OpenAPI download timed out after ${timeoutMs}ms: ${url}`);
296
214
  throw new Error(`Unable to fetch OpenAPI document from ${url}: ${error instanceof Error ? error.message : String(error)}`);
@@ -391,22 +309,6 @@ function extractOpenApiOutputProperties(operation, swagger) {
391
309
  return [...map.values()];
392
310
  }
393
311
  //#endregion
394
- //#region src/utils/isKnowBackendType.ts
395
- function isKnownBackendType(value) {
396
- return getUserConfig().analysis.backends.includes(value);
397
- }
398
- //#endregion
399
- //#region src/services/backendSourceService.ts
400
- function inferBackendNameFromFile(sourceFile) {
401
- const declaredType = sourceFile.getFullText().match(/BackendTypes\.([A-Z0-9_]+)/)?.[1];
402
- if (declaredType) return isKnownBackendType(declaredType) ? declaredType : null;
403
- const parts = sourceFile.getFilePath().split(path.sep);
404
- const backIndex = parts.lastIndexOf("back");
405
- const infrastructureIndex = parts.lastIndexOf("_infra");
406
- const candidate = backIndex >= 0 ? parts[backIndex + 1] : infrastructureIndex >= 0 ? parts[infrastructureIndex + 2] : void 0;
407
- return candidate && isKnownBackendType(candidate.toUpperCase()) ? candidate.toUpperCase() : null;
408
- }
409
- //#endregion
410
312
  //#region src/services/backendRouteExtractionService.ts
411
313
  function looksLikeBackendRoute(value) {
412
314
  return value.startsWith("/") || /^https?:\/\//.test(value) || /^graphql$/i.test(value);
@@ -457,51 +359,6 @@ function normalizeDescription(description) {
457
359
  return description?.trim() || "";
458
360
  }
459
361
  //#endregion
460
- //#region src/utils/resolveAliasPath.ts
461
- function resolveAliasPath(moduleSpecifier, aliases, rootPath) {
462
- for (const [alias, target] of Object.entries(aliases)) {
463
- if (moduleSpecifier !== alias && !moduleSpecifier.startsWith(`${alias}/`)) continue;
464
- const modulePath = moduleSpecifier.slice(alias.length).replace(/^\//, "");
465
- return path.join(rootPath, target, modulePath);
466
- }
467
- return null;
468
- }
469
- //#endregion
470
- //#region src/utils/tryResolveWithExtensions.ts
471
- function tryResolveWithExtensions(basePath) {
472
- const ext = path.extname(basePath);
473
- const withoutExt = ext ? basePath.slice(0, -ext.length) : basePath;
474
- const candidates = [
475
- basePath,
476
- ext === ".js" ? `${withoutExt}.ts` : null,
477
- ext === ".ts" ? `${withoutExt}.js` : null,
478
- ext === ".mjs" ? `${withoutExt}.mts` : null,
479
- ext === ".mts" ? `${withoutExt}.mjs` : null,
480
- `${basePath}.ts`,
481
- `${basePath}.js`,
482
- `${basePath}.mts`,
483
- `${basePath}.mjs`,
484
- path.join(withoutExt, "index.ts"),
485
- path.join(withoutExt, "index.js"),
486
- path.join(basePath, "index.ts"),
487
- path.join(basePath, "index.js")
488
- ].filter((candidate) => Boolean(candidate));
489
- for (const candidate of candidates) try {
490
- const normalized = path.normalize(candidate);
491
- if (fs.existsSync(normalized)) return normalized;
492
- } catch {
493
- continue;
494
- }
495
- return null;
496
- }
497
- //#endregion
498
- //#region src/utils/resolveModulePath.ts
499
- function resolveModulePath(sourceFilePath, moduleSpecifier, aliases, rootPath) {
500
- if (moduleSpecifier.startsWith(".")) return tryResolveWithExtensions(path.resolve(path.dirname(sourceFilePath), moduleSpecifier));
501
- const aliased = resolveAliasPath(moduleSpecifier, aliases, rootPath);
502
- return aliased ? tryResolveWithExtensions(aliased) : null;
503
- }
504
- //#endregion
505
362
  //#region src/utils/dedupeByKey.ts
506
363
  function dedupeByKey(items, keyFn) {
507
364
  const map = /* @__PURE__ */ new Map();
@@ -1382,6 +1239,7 @@ async function collectDependencyFiles(project, entryFilePath, routeLabel, resolv
1382
1239
  }
1383
1240
  async function analyzeCodebaseRouteContracts(params) {
1384
1241
  const { cwd, openApiDocument: swagger, selectedRouteKeys, routeContracts, onDocument, routeAnalysisScope } = params;
1242
+ const backendProperties = await resolveBackendProperties(userConfig.analysis.backends);
1385
1243
  taskProgressService.log("Discovering route files");
1386
1244
  const routeFiles = await globby(["app/_api/**/routes.@(js|ts)", "app/legacy/**/routes.@(js|ts)"], {
1387
1245
  cwd,
@@ -1443,11 +1301,12 @@ async function analyzeCodebaseRouteContracts(params) {
1443
1301
  }) : void 0;
1444
1302
  if (routeAnalysisScope && !topologyScope) continue;
1445
1303
  taskProgressService.log(topologyScope ? `${routeLabel} ... Loading dependencies from backend graph` : `${routeLabel} ... Collecting dependencies`);
1446
- const analysisFiles = filterAnalysisFiles(dedupeByKey(await (topologyScope ? topologyScope.analysisFiles : handler.handlerFile ? dependencyFilesByHandler.get(handler.handlerFile) || (() => {
1304
+ const dependencyFiles = topologyScope ? topologyScope.analysisFiles : handler.handlerFile ? dependencyFilesByHandler.get(handler.handlerFile) || (() => {
1447
1305
  const files = collectDependencyFiles(project, handler.handlerFile, routeLabel, resolveCachedModulePath);
1448
1306
  dependencyFilesByHandler.set(handler.handlerFile, files);
1449
1307
  return files;
1450
- })() : [routeDeclaration.file]), (value) => value));
1308
+ })() : [routeDeclaration.file];
1309
+ const analysisFiles = filterAnalysisFiles(dedupeByKey(await dependencyFiles, (value) => value));
1451
1310
  taskProgressService.log(`${routeLabel} ... Extracting mappings from ${analysisFiles.length} relevant files`);
1452
1311
  await yieldToEventLoop();
1453
1312
  const backendSourceFiles = analysisFiles.filter((file) => file.includes(`${path.sep}app${path.sep}_infra${path.sep}back${path.sep}`)).map((file) => project.getSourceFile(file) || project.addSourceFileAtPath(file)).filter((sourceFile) => {
@@ -1470,7 +1329,15 @@ async function analyzeCodebaseRouteContracts(params) {
1470
1329
  }), (item) => `${item.backend}:${item.method}:${item.route}:${item.sourceFile}`);
1471
1330
  const backendGraphKey = `${backendSourceFiles.map((file) => file.getFilePath()).sort().join("|")}::${[...reachableBackendFunctionNames].sort().join("|")}`;
1472
1331
  const backendFieldCandidates = backendFieldsByGraph.get(backendGraphKey) || (() => {
1473
- 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
+ });
1474
1341
  backendFieldsByGraph.set(backendGraphKey, candidates);
1475
1342
  return candidates;
1476
1343
  })();
@@ -1619,6 +1486,31 @@ function resolveBackendRouteCandidate(document, match) {
1619
1486
  const sameBackendCandidates = document.backendRouteCandidates.filter((candidate) => candidate.backend === match.backend);
1620
1487
  return sameBackendCandidates.find((candidate) => candidate.sourceFile === match.sourceFile) || sameBackendCandidates[0];
1621
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
+ }
1622
1514
  async function buildCatalogue(documents) {
1623
1515
  const routes = documents.map((document) => ({
1624
1516
  key: document.key,
@@ -1660,19 +1552,7 @@ async function buildCatalogue(documents) {
1660
1552
  const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
1661
1553
  const isTransverseWithoutMapping = isTransversalInput(property) && !matches.length;
1662
1554
  const evidenceStatus = isTransverseWithoutMapping ? "confirmed" : evidenceStatusFromCandidates(matches);
1663
- const backendMappings = dedupeByKey(matches.map((match) => {
1664
- const backendRouteCandidate = resolveBackendRouteCandidate(document, match);
1665
- return {
1666
- backend: match.backend,
1667
- method: backendRouteCandidate?.method || null,
1668
- route: backendRouteCandidate?.route || "unknown",
1669
- field: match.backendField,
1670
- source_file: backendFieldSource(match),
1671
- confidence: match.confidence,
1672
- mapper_type: match.mapperType,
1673
- reason: match.reviewReason
1674
- };
1675
- }), (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}`);
1676
1556
  routeInputProperties.push({
1677
1557
  key: apiPropertyKey,
1678
1558
  route_key: document.key,
@@ -1703,12 +1583,14 @@ async function buildCatalogue(documents) {
1703
1583
  route: "unknown",
1704
1584
  sourceFile: match.sourceFile
1705
1585
  }).key;
1706
- const backendPropertyKey = stableKey$2(backendRouteKey, "input", match.backendField);
1586
+ const backendPropertyKey = stableKey$2(backendRouteKey, "input", match.resolvedProperty?.field || match.backendField);
1707
1587
  backendPropertiesByKey.set(backendPropertyKey, {
1708
1588
  key: backendPropertyKey,
1709
1589
  backend_route_key: backendRouteKey,
1710
1590
  backend: match.backend,
1711
- 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,
1712
1594
  direction: "input",
1713
1595
  source_file: backendFieldSource(match),
1714
1596
  provenance: "code_analysis"
@@ -1732,19 +1614,7 @@ async function buildCatalogue(documents) {
1732
1614
  const apiPropertyKey = stableKey$2(document.key, "output", property.path);
1733
1615
  const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
1734
1616
  const evidenceStatus = evidenceStatusFromCandidates(matches);
1735
- const backendMappings = dedupeByKey(matches.map((match) => {
1736
- const backendRouteCandidate = resolveBackendRouteCandidate(document, match);
1737
- return {
1738
- backend: match.backend,
1739
- method: backendRouteCandidate?.method || null,
1740
- route: backendRouteCandidate?.route || "unknown",
1741
- field: match.backendField,
1742
- source_file: backendFieldSource(match),
1743
- confidence: match.confidence,
1744
- mapper_type: match.mapperType,
1745
- reason: match.reviewReason
1746
- };
1747
- }), (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}`);
1748
1618
  routeOutputProperties.push({
1749
1619
  key: apiPropertyKey,
1750
1620
  route_key: document.key,
@@ -1775,12 +1645,14 @@ async function buildCatalogue(documents) {
1775
1645
  route: "unknown",
1776
1646
  sourceFile: match.sourceFile
1777
1647
  }).key;
1778
- const backendPropertyKey = stableKey$2(backendRouteKey, "output", match.backendField);
1648
+ const backendPropertyKey = stableKey$2(backendRouteKey, "output", match.resolvedProperty?.field || match.backendField);
1779
1649
  backendPropertiesByKey.set(backendPropertyKey, {
1780
1650
  key: backendPropertyKey,
1781
1651
  backend_route_key: backendRouteKey,
1782
1652
  backend: match.backend,
1783
- 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,
1784
1656
  direction: "output",
1785
1657
  source_file: backendFieldSource(match),
1786
1658
  provenance: "code_analysis"
@@ -1824,7 +1696,7 @@ async function buildCatalogue(documents) {
1824
1696
  };
1825
1697
  }
1826
1698
  //#endregion
1827
- //#region src/services/directusSyncService.ts
1699
+ //#region src/services/directus/directusSyncService.ts
1828
1700
  const ROUTE_STATUSES = { published: "published" };
1829
1701
  const LOCAL_SOURCE_FILE = "digital-api:tools/datasource-catalogue";
1830
1702
  const DIRECTUS_RETRY_MAX_ATTEMPTS = 6;
@@ -2356,14 +2228,14 @@ async function pushCatalogueToDirectus(catalogue, options) {
2356
2228
  }
2357
2229
  }
2358
2230
  }
2359
- if (options.outputPath) await fs$1.appendFile(options.outputPath, "", "utf8");
2231
+ if (options.outputPath) await fs.appendFile(options.outputPath, "", "utf8");
2360
2232
  const removedOrphanedLinks = await cleanupOrphanedPropertyLinks(client);
2361
2233
  taskProgressService.report("Finalizing Directus synchronization");
2362
2234
  if (warnings.size) {
2363
- console.warn("========================================");
2364
- console.warn("BIG WARNING: missing predefined values or unresolved Directus links");
2365
- for (const warning of warnings) console.warn(`- ${warning}`);
2366
- console.warn("========================================");
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("========================================");
2367
2239
  }
2368
2240
  return {
2369
2241
  pushedCollections,
@@ -2403,9 +2275,11 @@ function buildReviewProperty(property) {
2403
2275
  const backends = property.backend_mappings.map((mapping) => ({
2404
2276
  type: mapping.backend,
2405
2277
  field: mapping.field,
2278
+ document: mapping.document,
2279
+ description: mapping.description,
2406
2280
  source: mapping.source_file,
2407
- ...mapping.route !== "unknown" || mapping.method ? { operation: {
2408
- method: mapping.method,
2281
+ ...mapping.route && (mapping.route !== "unknown" || mapping.method) ? { operation: {
2282
+ method: mapping.method || null,
2409
2283
  route: mapping.route
2410
2284
  } } : {},
2411
2285
  confidence: mapping.confidence,
@@ -2552,12 +2426,12 @@ async function writeRouteReviewDocument(catalogue, outputDir, repoRoot, routeKey
2552
2426
  if (!route) throw new Error(`Unable to write review document for unknown route key ${routeKey}`);
2553
2427
  const document = relativizeRouteReviewDocument(buildRouteReviewDocument(catalogue, route.key), repoRoot);
2554
2428
  const filePath = path.join(outputDir, buildRouteReviewRelativePath(route.method, route.path));
2555
- await fs$1.mkdir(path.dirname(filePath), { recursive: true });
2556
- await fs$1.writeFile(filePath, `${stringify(document)}\n`, "utf8");
2429
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
2430
+ await fs.writeFile(filePath, `${stringify(document)}\n`, "utf8");
2557
2431
  return filePath;
2558
2432
  }
2559
2433
  async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
2560
- await fs$1.mkdir(outputDir, { recursive: true });
2434
+ await fs.mkdir(outputDir, { recursive: true });
2561
2435
  for (const [index, route] of catalogue.routes.entries()) {
2562
2436
  taskProgressService.log(`[${index + 1}/${catalogue.routes.length}] - ${route.method} ${route.path} ...`);
2563
2437
  await writeRouteReviewDocument(catalogue, outputDir, repoRoot, route.key);
@@ -2565,8 +2439,8 @@ async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
2565
2439
  return outputDir;
2566
2440
  }
2567
2441
  async function prepareCatalogueOutputDirectory(outputDir) {
2568
- await fs$1.mkdir(outputDir, { recursive: true });
2569
- await Promise.all(["catalogue.yaml", "catalogue.yml"].map((fileName) => fs$1.rm(path.join(outputDir, fileName), { force: true })));
2442
+ await fs.mkdir(outputDir, { recursive: true });
2443
+ await Promise.all(["catalogue.yaml", "catalogue.yml"].map((fileName) => fs.rm(path.join(outputDir, fileName), { force: true })));
2570
2444
  }
2571
2445
  async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.cwd()) {
2572
2446
  await prepareCatalogueOutputDirectory(outputDir);
@@ -2577,16 +2451,17 @@ async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.
2577
2451
  };
2578
2452
  }
2579
2453
  function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backendRoutesByKey, backendPropertiesByKey, mappingEvidence) {
2580
- const backendRouteKey = stableKey$1(routeKey, mapping.backend, mapping.route);
2581
- const backendRoute = backendRoutesByKey.get(backendRouteKey) || {
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) || {
2582
2457
  key: backendRouteKey,
2583
2458
  backend: mapping.backend,
2584
2459
  route_key: routeKey,
2585
- route: `${mapping.method || "CALL"} ${mapping.route}`,
2460
+ route: `${mapping.method || "CALL"} ${backendRoute}`,
2586
2461
  source_file: mapping.source_file,
2587
2462
  provenance: "code_analysis"
2588
2463
  };
2589
- backendRoutesByKey.set(backendRouteKey, backendRoute);
2464
+ backendRoutesByKey.set(backendRouteKey, backendRouteRecord);
2590
2465
  const backendPropertyKey = stableKey$1(backendRouteKey, direction, mapping.field);
2591
2466
  backendPropertiesByKey.set(backendPropertyKey, {
2592
2467
  key: backendPropertyKey,
@@ -2612,8 +2487,10 @@ function toCatalogueBackendMapping(mapping) {
2612
2487
  return {
2613
2488
  backend: mapping.type,
2614
2489
  method: mapping.operation?.method || null,
2615
- route: mapping.operation?.route || "unknown",
2490
+ ...mapping.operation?.route ? { route: mapping.operation.route } : {},
2616
2491
  field: mapping.field,
2492
+ document: mapping.document,
2493
+ description: mapping.description,
2617
2494
  source_file: mapping.source,
2618
2495
  confidence: mapping.confidence,
2619
2496
  mapper_type: mapping.mapper_type,
@@ -2744,7 +2621,7 @@ async function readCatalogueFromDirectory(inputDir) {
2744
2621
  if (!files.length) throw new Error(`No route YAML documents found in ${inputDir}`);
2745
2622
  return {
2746
2623
  catalogue: mergeRouteReviewDocuments(await Promise.all(files.sort().map(async (file) => {
2747
- const parsed = parse(await fs$1.readFile(file, "utf8"));
2624
+ const parsed = parse(await fs.readFile(file, "utf8"));
2748
2625
  if (parsed?.schema_version !== 1 || !parsed?.route?.method || !parsed?.route?.path) throw new Error(`Invalid route YAML document: ${file}`);
2749
2626
  return parsed;
2750
2627
  }))),
@@ -2752,7 +2629,7 @@ async function readCatalogueFromDirectory(inputDir) {
2752
2629
  };
2753
2630
  }
2754
2631
  //#endregion
2755
- //#region src/services/directusPushService.ts
2632
+ //#region src/services/directus/directusPushService.ts
2756
2633
  async function pushCatalogueDirectoryToDirectus(params) {
2757
2634
  const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
2758
2635
  taskProgressService.report("Loading and reconstructing route YAML documents");
@@ -2921,6 +2798,42 @@ var cleanOrphans_default = (program) => void program.command("clean-orphans").op
2921
2798
  }
2922
2799
  });
2923
2800
  //#endregion
2801
+ //#region src/commands/backendSources.ts
2802
+ async function listBackendSourceMetadata(sources, backend) {
2803
+ const selectedSources = backend ? sources.filter((source) => source.name === backend) : sources;
2804
+ if (backend && !selectedSources.length) throw new Error(`Unknown backend source: ${backend}`);
2805
+ const propertiesByBackend = await resolveBackendProperties(selectedSources);
2806
+ return selectedSources.map((source) => ({
2807
+ backend: source.name,
2808
+ properties: propertiesByBackend.get(source.name) || []
2809
+ }));
2810
+ }
2811
+ 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) => {
2812
+ intro("Atlas backend source metadata");
2813
+ try {
2814
+ const sources = getUserConfig().analysis.backends;
2815
+ const selectedBackend = options.backend || await select({
2816
+ message: "Which backend source do you want to execute?",
2817
+ options: sources.map((source) => ({
2818
+ value: source.name,
2819
+ label: source.name,
2820
+ hint: source.resolve ? "resolver configured" : "no resolver configured"
2821
+ }))
2822
+ });
2823
+ if (isCancel(selectedBackend)) {
2824
+ cancel("Backend source execution cancelled");
2825
+ process.exitCode = 1;
2826
+ return;
2827
+ }
2828
+ const metadata = await listBackendSourceMetadata(sources, selectedBackend);
2829
+ log.message(JSON.stringify(metadata, null, 2));
2830
+ outro("Backend source metadata generated");
2831
+ } catch (error) {
2832
+ cancel(error instanceof Error ? error.message : String(error));
2833
+ process.exitCode = 1;
2834
+ }
2835
+ });
2836
+ //#endregion
2924
2837
  //#region src/services/analysisProfileService.ts
2925
2838
  function createAnalysisProfile(enabled) {
2926
2839
  const durations = /* @__PURE__ */ new Map();
@@ -2938,8 +2851,8 @@ function buildBackendTopologyRelativePath(method, routePath) {
2938
2851
  }
2939
2852
  async function writeBackendTopologyArtifact(artifact, outputDir) {
2940
2853
  const filePath = path.join(outputDir, buildBackendTopologyRelativePath(artifact.route.method, artifact.route.path));
2941
- await fs$1.mkdir(path.dirname(filePath), { recursive: true });
2942
- await fs$1.writeFile(filePath, `${stringify(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
2854
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
2855
+ await fs.writeFile(filePath, `${stringify(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
2943
2856
  return filePath;
2944
2857
  }
2945
2858
  function isRecord(value) {
@@ -2958,7 +2871,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
2958
2871
  const filePath = path.join(outputDir, buildBackendTopologyRelativePath(method, routePath));
2959
2872
  let content;
2960
2873
  try {
2961
- content = await fs$1.readFile(filePath, "utf8");
2874
+ content = await fs.readFile(filePath, "utf8");
2962
2875
  } catch (error) {
2963
2876
  if (isRecord(error) && error.code === "ENOENT") throw new Error(`Backend graph artifact not found for ${method} ${routePath}: ${filePath}. Run generate:graph first.`);
2964
2877
  throw error;
@@ -2979,7 +2892,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
2979
2892
  const missingFiles = [];
2980
2893
  await Promise.all(analysisFiles.map(async (analysisFile) => {
2981
2894
  try {
2982
- await fs$1.access(analysisFile);
2895
+ await fs.access(analysisFile);
2983
2896
  } catch {
2984
2897
  missingFiles.push(path.relative(cwd, analysisFile));
2985
2898
  }
@@ -3079,7 +2992,7 @@ async function generateHandler(cwd, routeArgument, options) {
3079
2992
  process.exit(1);
3080
2993
  }
3081
2994
  }
3082
- var generate_default = (program) => void program.command("generate:catalogue").argument("[route]", "Route selector in the format \"GET /v1/products\"").option("-o, --output <directory>", "Directory where catalogue artifacts are generated", ".tmp/datasource-catalogue").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) => {
2995
+ 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", ".tmp/datasource-catalogue").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) => {
3083
2996
  const config = getUserConfig();
3084
2997
  return generateHandler(config.cwd, routeArgument, {
3085
2998
  ...options,
@@ -3087,104 +3000,6 @@ var generate_default = (program) => void program.command("generate:catalogue").a
3087
3000
  });
3088
3001
  });
3089
3002
  //#endregion
3090
- //#region src/services/routeBackendTopologyService.ts
3091
- function stringProperty(object, name) {
3092
- const property = object.getProperty(name);
3093
- if (!property || !Node.isPropertyAssignment(property)) return void 0;
3094
- const initializer = property.getInitializer();
3095
- return initializer && (Node.isStringLiteral(initializer) || Node.isNoSubstitutionTemplateLiteral(initializer)) ? initializer.getLiteralValue() : void 0;
3096
- }
3097
- function handlerProperty(object) {
3098
- const property = object.getProperty("handler");
3099
- if (!property || !Node.isPropertyAssignment(property)) return void 0;
3100
- const initializer = property.getInitializer();
3101
- return initializer && (Node.isIdentifier(initializer) || Node.isPropertyAccessExpression(initializer)) ? initializer.getText() : void 0;
3102
- }
3103
- function extractRouteDeclarations(sourceFile) {
3104
- const initializer = sourceFile.getVariableDeclaration("routes")?.getInitializer();
3105
- let expression = initializer;
3106
- if (initializer && (Node.isAsExpression(initializer) || Node.isSatisfiesExpression(initializer))) expression = initializer.getExpression();
3107
- if (!expression || !Node.isArrayLiteralExpression(expression)) return [];
3108
- return expression.getElements().flatMap((element) => {
3109
- if (!Node.isObjectLiteralExpression(element)) return [];
3110
- const method = stringProperty(element, "method");
3111
- const routePath = stringProperty(element, "path");
3112
- const handlerRef = handlerProperty(element);
3113
- return method && routePath && handlerRef ? [{
3114
- method: method.toUpperCase(),
3115
- path: routePath,
3116
- sourceFile,
3117
- handlerRef
3118
- }] : [];
3119
- });
3120
- }
3121
- async function discoverBackendTopologyRouteSelectors(cwd) {
3122
- const project = new Project({
3123
- skipAddingFilesFromTsConfig: true,
3124
- compilerOptions: {
3125
- allowJs: true,
3126
- checkJs: false
3127
- }
3128
- });
3129
- return (await globby(["app/_api/**/routes.@(js|ts)", "app/legacy/**/routes.@(js|ts)"], {
3130
- cwd,
3131
- absolute: true
3132
- })).flatMap((routeFile) => extractRouteDeclarations(project.addSourceFileAtPath(routeFile))).map((route) => ({
3133
- method: route.method,
3134
- path: route.path
3135
- }));
3136
- }
3137
- async function generateBackendTopologyArtifactsInWorkers(params) {
3138
- const selectors = params.routeSelector ? [params.routeSelector] : await discoverBackendTopologyRouteSelectors(params.cwd);
3139
- params.onRoutesDiscovered?.(selectors.length);
3140
- const workerCount = Math.min(params.workers || 2, selectors.length);
3141
- const chunks = Array.from({ length: workerCount }, () => []);
3142
- selectors.forEach((selector, index) => chunks[index % workerCount].push(selector));
3143
- const artifacts = [];
3144
- let completed = 0;
3145
- let writeQueue = Promise.resolve();
3146
- const workers = [];
3147
- try {
3148
- await Promise.all(chunks.map((routeSelectors) => new Promise((resolve, reject) => {
3149
- const worker = new Worker(new URL("./routeBackendTopologyWorker.ts", import.meta.url), {
3150
- workerData: {
3151
- cwd: params.cwd,
3152
- routeSelectors
3153
- },
3154
- execArgv: process.execArgv
3155
- });
3156
- workers.push(worker);
3157
- worker.on("message", (message) => {
3158
- if (message.type === "error") {
3159
- reject(new Error(message.message));
3160
- return;
3161
- }
3162
- writeQueue = writeQueue.then(async () => {
3163
- if (message.type === "artifact") {
3164
- await params.onArtifact(message.artifact);
3165
- artifacts.push(message.artifact);
3166
- return;
3167
- }
3168
- completed += 1;
3169
- params.onRouteProgress?.({
3170
- current: completed,
3171
- total: selectors.length,
3172
- route: message.route,
3173
- stage: "completed"
3174
- });
3175
- }).catch(reject);
3176
- });
3177
- worker.once("error", reject);
3178
- worker.once("exit", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`Topology worker exited with code ${code}`)));
3179
- })));
3180
- await writeQueue;
3181
- return artifacts;
3182
- } catch (error) {
3183
- await Promise.all(workers.map((worker) => worker.terminate().catch(() => void 0)));
3184
- throw error;
3185
- }
3186
- }
3187
- //#endregion
3188
3003
  //#region src/commands/generateGraph.ts
3189
3004
  function parseWorkerCount$1(value) {
3190
3005
  const workers = Number(value);
@@ -3333,7 +3148,7 @@ var generateTest_default = (program) => void program.command("generate:test").de
3333
3148
  }
3334
3149
  });
3335
3150
  //#endregion
3336
- //#region src/services/aiSdkClient.ts
3151
+ //#region src/services/ai/aiSdkClient.ts
3337
3152
  var AISdkClient = class {
3338
3153
  #config;
3339
3154
  constructor(config) {
@@ -3364,7 +3179,7 @@ var AISdkClient = class {
3364
3179
  }
3365
3180
  };
3366
3181
  //#endregion
3367
- //#region src/services/inferenceService.ts
3182
+ //#region src/services/ai/inferenceService.ts
3368
3183
  const MAPPING_OUTPUT_SCHEMA = z.object({ mappings: z.array(z.object({
3369
3184
  property_index: z.number().int(),
3370
3185
  candidate_index: z.number().int(),
@@ -3439,7 +3254,7 @@ async function readSourceExcerpts(document) {
3439
3254
  const excerpts = [];
3440
3255
  let totalCharacters = 0;
3441
3256
  for (const location of uniqueLocations) try {
3442
- const lines = (await fs$1.readFile(location.sourceFile, "utf8")).split("\n");
3257
+ const lines = (await fs.readFile(location.sourceFile, "utf8")).split("\n");
3443
3258
  const startLine = Math.max(1, location.line - EXCERPT_RADIUS);
3444
3259
  const endLine = Math.min(lines.length, location.line + EXCERPT_RADIUS);
3445
3260
  const code = lines.slice(startLine - 1, endLine).map((line, index) => `${String(startLine + index).padStart(5)} | ${line}`).join("\n");
@@ -4205,18 +4020,18 @@ var init_default = (program, datasourceCommand) => void program.command("init").
4205
4020
  const configDirectory = path.dirname(configPath);
4206
4021
  intro("Initialize datasource configuration");
4207
4022
  try {
4208
- await fs$1.mkdir(configDirectory, { recursive: true });
4023
+ await fs.mkdir(configDirectory, { recursive: true });
4209
4024
  if (!options.force) {
4210
4025
  let exists = false;
4211
4026
  try {
4212
- await fs$1.access(configPath);
4027
+ await fs.access(configPath);
4213
4028
  exists = true;
4214
4029
  } catch (error) {
4215
4030
  if (error.code !== "ENOENT") throw error;
4216
4031
  }
4217
4032
  if (exists) throw new Error(`${configPath} already exists. Use --force to overwrite it.`);
4218
4033
  }
4219
- await fs$1.writeFile(configPath, configTemplate, "utf8");
4034
+ await fs.writeFile(configPath, configTemplate, "utf8");
4220
4035
  outro(`Created ${configPath}`);
4221
4036
  } catch (error) {
4222
4037
  cancel(error instanceof Error ? error.message : String(error));
@@ -4349,7 +4164,7 @@ function collectImpactedRoutes(params) {
4349
4164
  return params.graphs.filter((graph) => graph.analysisFiles.some((analysisFile) => changedFiles.has(normalizeFilePath(analysisFile)))).sort((left, right) => `${left.method} ${left.path}`.localeCompare(`${right.method} ${right.path}`));
4350
4165
  }
4351
4166
  async function readChangedFiles(filePath) {
4352
- return (await fs$1.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
4167
+ return (await fs.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
4353
4168
  }
4354
4169
  async function readRouteGraphs(outputDirectory) {
4355
4170
  const graphFiles = await globby("**/*.graph.yaml", {
@@ -4357,7 +4172,7 @@ async function readRouteGraphs(outputDirectory) {
4357
4172
  absolute: true
4358
4173
  });
4359
4174
  const graphs = await Promise.all(graphFiles.map(async (graphFile) => {
4360
- const document = parse(await fs$1.readFile(graphFile, "utf8"));
4175
+ const document = parse(await fs.readFile(graphFile, "utf8"));
4361
4176
  if (!isGraphDocument(document)) throw new Error(`Invalid graph document: ${graphFile}`);
4362
4177
  return {
4363
4178
  method: document.route.method,
@@ -4475,8 +4290,8 @@ async function reportChangedHandler(cwd, options) {
4475
4290
  routes: coverages
4476
4291
  });
4477
4292
  const reportPath = path.resolve(cwd, options.report);
4478
- await fs$1.mkdir(path.dirname(reportPath), { recursive: true });
4479
- await fs$1.writeFile(reportPath, `${report}\n`, "utf8");
4293
+ await fs.mkdir(path.dirname(reportPath), { recursive: true });
4294
+ await fs.writeFile(reportPath, `${report}\n`, "utf8");
4480
4295
  note(report, "Datasource mapping report");
4481
4296
  }
4482
4297
  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", ".tmp/datasource-catalogue").option("--report <file>", "Markdown report output path", ".tmp/datasource-catalogue/changed-routes-report.md").description("Report deterministic mapping coverage for routes impacted by changed files").action(async (options) => {
@@ -4496,9 +4311,11 @@ const program = new Command();
4496
4311
  program.version("3.4.0-alpha.1");
4497
4312
  program.name("atlas").description("Manage the API-to-backend mapping catalogue").option("--project-root <path>", "Root directory of the API project to analyze").option("-c, --config <path>", "Path to atlas.config.ts", path.resolve(process.cwd(), "atlas.config.ts")).hook("preAction", async (_thisCommand, actionCommand) => {
4498
4313
  if (actionCommand.name() === "init") return;
4499
- setUserConfig(await loadAtlasConfig(program.opts().config, program.opts().projectRoot));
4314
+ const config = await loadAtlasConfig(program.opts().config, program.opts().projectRoot);
4315
+ setUserConfig(config);
4500
4316
  });
4501
4317
  init_default(program, program);
4318
+ backendSources_default(program);
4502
4319
  generate_default(program);
4503
4320
  generateGraph_default(program);
4504
4321
  generateTest_default(program);