@cmflow/atlas 3.4.0-beta.23 → 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 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 `--project-root` only to override the API project directory to analyze.
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. `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.
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 --project-root /path/to/api generate:graph
298
- atlas --project-root /path/to/api generate:catalog --output .tmp/datasource-catalogue
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 --project-root /path/to/api generate:test
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 --project-root /path/to/api needs-review --sort percentage --limit 25
313
- atlas --project-root /path/to/api infer .tmp/datasource-catalogue
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 --project-root /path/to/api push .tmp/datasource-catalogue --write
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/models/types.d.ts
20
- interface NeutralExpressionMatcher {
21
- prefix: string;
22
- apiMapping?: boolean;
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
- type BackendProperty = {
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
- * Absolute path to the root of the repository to analyze (e.g. the digital-api repo).
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, UserConfig as r, BackendProperty as t };
124
- //# sourceMappingURL=types-D5fngFrb.d.mts.map
139
+ export { defineExpressionRule as a, FieldExtractionRule as i, BackendSource as n, BackendProperty as r, UserConfig as t };
140
+ //# sourceMappingURL=UserConfig-aqAuodcW.d.mts.map
@@ -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-CAC_RIoa.mjs";
6
- import { a as inferBackendNameFromFile, c as shouldKeepAnalysisFile, i as resolveModulePath, l as normalizeFilePath, n as generateBackendTopologyArtifactsInWorkers, o as isKnownBackendType, r as matchesRouteSelector, s as filterAnalysisFiles, u as globby } from "../routeBackendTopologyService-CQyRyoSN.mjs";
7
- import { i as loadOpenApiDocument, n as extractOpenApiInputProperties, r as extractOpenApiOutputProperties } from "../propertyExtractionService-BLat-Hsf.mjs";
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.repoRoot)) {
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.repoRoot)) {
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(["app/_api/**/routes.@(js|ts)", "app/legacy/**/routes.@(js|ts)"], {
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, userConfig.repoRoot);
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\"", DEFAULT_CATALOGUE_DIRECTORY).option("--route <route>", "Push one route only, for example \"POST /v0/accommodations_arrangement/check\"").option("--write", "Persist changes to Directus", false).description("Push generated per-route YAML documents to Directus").action(async (catalogueDirectoryOrRoute, options) => {
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 ? DEFAULT_CATALOGUE_DIRECTORY : catalogueDirectoryOrRoute;
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", ".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) => {
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", ".tmp/datasource-catalogue").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) => {
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, params.cwd);
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
- type: "BODY"
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
- type: "RESPONSE_BODY"
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, params.cwd, method, routePath);
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, params.cwd, routeKey);
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, params.cwd, routeKey);
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, params.cwd, routeKey);
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
- repoRoot: process.cwd(),
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", ".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) => {
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("--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) => {
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().projectRoot);
30598
+ const config = await userConfig$1(program.opts().config, program.opts().analysisDir);
30549
30599
  setUserConfig(config);
30550
30600
  });
30551
30601
  init_default(program, program);