@cmflow/atlas 3.4.0-beta.4 → 3.4.0-beta.6
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 +51 -2
- package/dist/bin/atlas.mjs +47 -49
- package/dist/defineExpressionRule-Dfvzj6n2.mjs +2 -0
- package/dist/defineExpressionRule-Dfvzj6n2.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/rules/cleanObjectRule.d.mts +1 -1
- package/dist/rules/cleanObjectRule.mjs +1 -1
- package/dist/rules/cleanObjectRule.mjs.map +1 -1
- package/dist/rules/cmsI18nFieldRule.d.mts +1 -1
- package/dist/rules/cmsI18nFieldRule.mjs +1 -1
- package/dist/rules/cmsI18nFieldRule.mjs.map +1 -1
- package/dist/rules/dateConversionRule.d.mts +1 -1
- package/dist/rules/dateConversionRule.mjs +1 -1
- package/dist/rules/dateConversionRule.mjs.map +1 -1
- package/dist/rules/lodashGetRule.d.mts +1 -1
- package/dist/rules/lodashGetRule.mjs +1 -1
- package/dist/rules/lodashGetRule.mjs.map +1 -1
- package/dist/rules/mappingUtilityRule.d.mts +1 -1
- package/dist/rules/mappingUtilityRule.mjs +1 -1
- package/dist/rules/mappingUtilityRule.mjs.map +1 -1
- package/dist/rules/memberGetFieldRule.d.mts +1 -1
- package/dist/rules/memberGetFieldRule.mjs +1 -1
- package/dist/rules/memberGetFieldRule.mjs.map +1 -1
- package/dist/rules/quableI18nFieldRule.d.mts +1 -1
- package/dist/rules/quableI18nFieldRule.mjs +1 -1
- package/dist/rules/quableI18nFieldRule.mjs.map +1 -1
- package/dist/{types-Dl8rY7PV.d.mts → types-3y34Gf8R.d.mts} +13 -8
- package/package.json +1 -1
- package/dist/defineRule-Dfvzj6n2.mjs +0 -2
- package/dist/defineRule-Dfvzj6n2.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -50,13 +50,62 @@ The configuration imports `defineConfig` from Atlas:
|
|
|
50
50
|
import { defineConfig } from "@cmflow/atlas";
|
|
51
51
|
|
|
52
52
|
export default defineConfig({
|
|
53
|
-
backendTypesFile: "app/_infra/back/BackendTypes.ts",
|
|
54
53
|
openapiUrl: "https://api.example.com/openapi.json",
|
|
55
54
|
openapiTimeoutMs: 60_000,
|
|
56
|
-
directusUrl: "https://cms.api.clubmed"
|
|
55
|
+
directusUrl: "https://cms.api.clubmed",
|
|
56
|
+
analysis: {
|
|
57
|
+
backends: ["ICC", "CMS", "CMS_B2C"]
|
|
58
|
+
}
|
|
57
59
|
});
|
|
58
60
|
```
|
|
59
61
|
|
|
62
|
+
Atlas reads `compilerOptions.paths` from the project's `tsconfig.json` and uses them as module aliases. Add `resolver.alias` only to override or complement those aliases.
|
|
63
|
+
|
|
64
|
+
## Custom expression rules
|
|
65
|
+
|
|
66
|
+
Use `defineExpressionRule` when a project-specific helper hides a backend field or wraps an expression that Atlas should follow. Add the rule to `analysis.rules` in `atlas.config.ts`.
|
|
67
|
+
|
|
68
|
+
`match` is a cheap predicate that selects the `ts-morph` expression. `parse` returns the information Atlas should use: a backend field, a transparent wrapper, or both.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { defineConfig, defineExpressionRule } from "@cmflow/atlas";
|
|
72
|
+
import { Node } from "ts-morph";
|
|
73
|
+
|
|
74
|
+
const localizedFieldRule = defineExpressionRule({
|
|
75
|
+
name: "localized-field",
|
|
76
|
+
match: (expression) =>
|
|
77
|
+
Node.isCallExpression(expression) && expression.getExpression().getText() === "localizedField",
|
|
78
|
+
parse: (expression) => {
|
|
79
|
+
if (!Node.isCallExpression(expression)) return undefined;
|
|
80
|
+
|
|
81
|
+
const [source, field] = expression.getArguments();
|
|
82
|
+
if (!source || !Node.isExpression(source) || !Node.isStringLiteral(field)) return undefined;
|
|
83
|
+
|
|
84
|
+
return { backendField: `${source.getText()}.${field.getLiteralValue()}` };
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export default defineConfig({
|
|
89
|
+
// …other Atlas options
|
|
90
|
+
analysis: {
|
|
91
|
+
// …other analysis options
|
|
92
|
+
rules: [localizedFieldRule]
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For a wrapper that does not change the field path, return `transparent: true`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const unwrapRule = defineExpressionRule({
|
|
101
|
+
name: "unwrap-api-value",
|
|
102
|
+
match: (expression) => Node.isCallExpression(expression) && expression.getExpression().getText() === "unwrap",
|
|
103
|
+
parse: () => ({ transparent: true })
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`parse` may also set `mapperType` to annotate the generated mapping, or `apiMapping: true` when the helper maps an API value rather than a backend value.
|
|
108
|
+
|
|
60
109
|
Create a `.env.local` in the target API project when pushing to Directus:
|
|
61
110
|
|
|
62
111
|
```dotenv
|
package/dist/bin/atlas.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, InvalidArgumentError, Option } from "commander";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
5
7
|
import { cancel, intro, log, note, outro, progress, spinner } from "@clack/prompts";
|
|
6
|
-
import fs from "node:fs/promises";
|
|
8
|
+
import fs$1 from "node:fs/promises";
|
|
7
9
|
import { createDirectus, createItem, deleteItem, readItems, rest, staticToken, updateItem } from "@directus/sdk";
|
|
8
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
11
|
import { globby } from "globby";
|
|
10
|
-
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
11
12
|
import { minimatch } from "minimatch";
|
|
12
|
-
import fs$1 from "node:fs";
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import { parse, stringify } from "yaml";
|
|
15
15
|
import { Worker } from "node:worker_threads";
|
|
@@ -25,7 +25,20 @@ function getUserConfig() {
|
|
|
25
25
|
if (!_config) throw new Error("Atlas config not loaded. Run Atlas from a directory containing atlas.config.ts or pass --config.");
|
|
26
26
|
return _config;
|
|
27
27
|
}
|
|
28
|
-
|
|
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) {
|
|
29
42
|
const filePath = path.resolve(configPath);
|
|
30
43
|
try {
|
|
31
44
|
let mod;
|
|
@@ -34,11 +47,15 @@ async function loadAtlasConfig(configPath) {
|
|
|
34
47
|
mod = await tsImport(filePath, import.meta.url);
|
|
35
48
|
} else mod = await import(pathToFileURL(filePath).href);
|
|
36
49
|
const config = mod.default ?? mod;
|
|
37
|
-
const repoRoot = config.repoRoot || process.cwd();
|
|
50
|
+
const repoRoot = projectRoot || config.repoRoot || process.cwd();
|
|
38
51
|
return {
|
|
39
52
|
...config,
|
|
40
53
|
repoRoot,
|
|
41
|
-
cwd: repoRoot
|
|
54
|
+
cwd: repoRoot,
|
|
55
|
+
resolver: { alias: {
|
|
56
|
+
...getTsconfigAliases(repoRoot),
|
|
57
|
+
...config.resolver?.alias
|
|
58
|
+
} }
|
|
42
59
|
};
|
|
43
60
|
} catch (error) {
|
|
44
61
|
throw new Error(`Unable to load Atlas configuration at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -374,19 +391,9 @@ function extractOpenApiOutputProperties(operation, swagger) {
|
|
|
374
391
|
return [...map.values()];
|
|
375
392
|
}
|
|
376
393
|
//#endregion
|
|
377
|
-
//#region src/
|
|
378
|
-
let knownBackendTypesCache = null;
|
|
394
|
+
//#region src/utils/isKnowBackendType.ts
|
|
379
395
|
function isKnownBackendType(value) {
|
|
380
|
-
|
|
381
|
-
const filePath = path.resolve(config.repoRoot, config.backendTypesFile);
|
|
382
|
-
if (!knownBackendTypesCache || knownBackendTypesCache.filePath !== filePath) {
|
|
383
|
-
const fileContent = fs$1.readFileSync(filePath, "utf8");
|
|
384
|
-
knownBackendTypesCache = {
|
|
385
|
-
filePath,
|
|
386
|
-
values: new Set([...fileContent.matchAll(/([A-Z0-9_]+)\s*=\s*"([A-Z0-9_]+)"/g)].map(([, key, backend]) => key === backend ? backend : null).filter((backend) => Boolean(backend)))
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
return knownBackendTypesCache.values.has(value);
|
|
396
|
+
return getUserConfig().analysis.backends.includes(value);
|
|
390
397
|
}
|
|
391
398
|
//#endregion
|
|
392
399
|
//#region src/services/backendSourceService.ts
|
|
@@ -481,7 +488,7 @@ function tryResolveWithExtensions(basePath) {
|
|
|
481
488
|
].filter((candidate) => Boolean(candidate));
|
|
482
489
|
for (const candidate of candidates) try {
|
|
483
490
|
const normalized = path.normalize(candidate);
|
|
484
|
-
if (fs
|
|
491
|
+
if (fs.existsSync(normalized)) return normalized;
|
|
485
492
|
} catch {
|
|
486
493
|
continue;
|
|
487
494
|
}
|
|
@@ -2349,7 +2356,7 @@ async function pushCatalogueToDirectus(catalogue, options) {
|
|
|
2349
2356
|
}
|
|
2350
2357
|
}
|
|
2351
2358
|
}
|
|
2352
|
-
if (options.outputPath) await fs.appendFile(options.outputPath, "", "utf8");
|
|
2359
|
+
if (options.outputPath) await fs$1.appendFile(options.outputPath, "", "utf8");
|
|
2353
2360
|
const removedOrphanedLinks = await cleanupOrphanedPropertyLinks(client);
|
|
2354
2361
|
taskProgressService.report("Finalizing Directus synchronization");
|
|
2355
2362
|
if (warnings.size) {
|
|
@@ -2545,12 +2552,12 @@ async function writeRouteReviewDocument(catalogue, outputDir, repoRoot, routeKey
|
|
|
2545
2552
|
if (!route) throw new Error(`Unable to write review document for unknown route key ${routeKey}`);
|
|
2546
2553
|
const document = relativizeRouteReviewDocument(buildRouteReviewDocument(catalogue, route.key), repoRoot);
|
|
2547
2554
|
const filePath = path.join(outputDir, buildRouteReviewRelativePath(route.method, route.path));
|
|
2548
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
2549
|
-
await fs.writeFile(filePath, `${stringify(document)}\n`, "utf8");
|
|
2555
|
+
await fs$1.mkdir(path.dirname(filePath), { recursive: true });
|
|
2556
|
+
await fs$1.writeFile(filePath, `${stringify(document)}\n`, "utf8");
|
|
2550
2557
|
return filePath;
|
|
2551
2558
|
}
|
|
2552
2559
|
async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
|
|
2553
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
2560
|
+
await fs$1.mkdir(outputDir, { recursive: true });
|
|
2554
2561
|
for (const [index, route] of catalogue.routes.entries()) {
|
|
2555
2562
|
taskProgressService.log(`[${index + 1}/${catalogue.routes.length}] - ${route.method} ${route.path} ...`);
|
|
2556
2563
|
await writeRouteReviewDocument(catalogue, outputDir, repoRoot, route.key);
|
|
@@ -2558,8 +2565,8 @@ async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
|
|
|
2558
2565
|
return outputDir;
|
|
2559
2566
|
}
|
|
2560
2567
|
async function prepareCatalogueOutputDirectory(outputDir) {
|
|
2561
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
2562
|
-
await Promise.all(["catalogue.yaml", "catalogue.yml"].map((fileName) => fs.rm(path.join(outputDir, fileName), { force: true })));
|
|
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 })));
|
|
2563
2570
|
}
|
|
2564
2571
|
async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.cwd()) {
|
|
2565
2572
|
await prepareCatalogueOutputDirectory(outputDir);
|
|
@@ -2737,7 +2744,7 @@ async function readCatalogueFromDirectory(inputDir) {
|
|
|
2737
2744
|
if (!files.length) throw new Error(`No route YAML documents found in ${inputDir}`);
|
|
2738
2745
|
return {
|
|
2739
2746
|
catalogue: mergeRouteReviewDocuments(await Promise.all(files.sort().map(async (file) => {
|
|
2740
|
-
const parsed = parse(await fs.readFile(file, "utf8"));
|
|
2747
|
+
const parsed = parse(await fs$1.readFile(file, "utf8"));
|
|
2741
2748
|
if (parsed?.schema_version !== 1 || !parsed?.route?.method || !parsed?.route?.path) throw new Error(`Invalid route YAML document: ${file}`);
|
|
2742
2749
|
return parsed;
|
|
2743
2750
|
}))),
|
|
@@ -2931,8 +2938,8 @@ function buildBackendTopologyRelativePath(method, routePath) {
|
|
|
2931
2938
|
}
|
|
2932
2939
|
async function writeBackendTopologyArtifact(artifact, outputDir) {
|
|
2933
2940
|
const filePath = path.join(outputDir, buildBackendTopologyRelativePath(artifact.route.method, artifact.route.path));
|
|
2934
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
2935
|
-
await fs.writeFile(filePath, `${stringify(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
|
|
2941
|
+
await fs$1.mkdir(path.dirname(filePath), { recursive: true });
|
|
2942
|
+
await fs$1.writeFile(filePath, `${stringify(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
|
|
2936
2943
|
return filePath;
|
|
2937
2944
|
}
|
|
2938
2945
|
function isRecord(value) {
|
|
@@ -2951,7 +2958,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
|
|
|
2951
2958
|
const filePath = path.join(outputDir, buildBackendTopologyRelativePath(method, routePath));
|
|
2952
2959
|
let content;
|
|
2953
2960
|
try {
|
|
2954
|
-
content = await fs.readFile(filePath, "utf8");
|
|
2961
|
+
content = await fs$1.readFile(filePath, "utf8");
|
|
2955
2962
|
} catch (error) {
|
|
2956
2963
|
if (isRecord(error) && error.code === "ENOENT") throw new Error(`Backend graph artifact not found for ${method} ${routePath}: ${filePath}. Run generate:graph first.`);
|
|
2957
2964
|
throw error;
|
|
@@ -2972,7 +2979,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
|
|
|
2972
2979
|
const missingFiles = [];
|
|
2973
2980
|
await Promise.all(analysisFiles.map(async (analysisFile) => {
|
|
2974
2981
|
try {
|
|
2975
|
-
await fs.access(analysisFile);
|
|
2982
|
+
await fs$1.access(analysisFile);
|
|
2976
2983
|
} catch {
|
|
2977
2984
|
missingFiles.push(path.relative(cwd, analysisFile));
|
|
2978
2985
|
}
|
|
@@ -3432,7 +3439,7 @@ async function readSourceExcerpts(document) {
|
|
|
3432
3439
|
const excerpts = [];
|
|
3433
3440
|
let totalCharacters = 0;
|
|
3434
3441
|
for (const location of uniqueLocations) try {
|
|
3435
|
-
const lines = (await fs.readFile(location.sourceFile, "utf8")).split("\n");
|
|
3442
|
+
const lines = (await fs$1.readFile(location.sourceFile, "utf8")).split("\n");
|
|
3436
3443
|
const startLine = Math.max(1, location.line - EXCERPT_RADIUS);
|
|
3437
3444
|
const endLine = Math.min(lines.length, location.line + EXCERPT_RADIUS);
|
|
3438
3445
|
const code = lines.slice(startLine - 1, endLine).map((line, index) => `${String(startLine + index).padStart(5)} | ${line}`).join("\n");
|
|
@@ -4159,13 +4166,9 @@ const configTemplate = `import { defineConfig } from "@cmflow/atlas";
|
|
|
4159
4166
|
|
|
4160
4167
|
export default defineConfig({
|
|
4161
4168
|
repoRoot: process.cwd(),
|
|
4162
|
-
backendTypesFile: "app/_infra/back/BackendTypes.ts",
|
|
4163
4169
|
openapiUrl: "https://api.example.com/openapi.json",
|
|
4164
4170
|
openapiTimeoutMs: 60_000,
|
|
4165
4171
|
directusUrl: "https://cms.api.clubmed",
|
|
4166
|
-
resolver: {
|
|
4167
|
-
alias: {}
|
|
4168
|
-
},
|
|
4169
4172
|
inference: {
|
|
4170
4173
|
model: process.env.AI_MODEL ?? "gpt-4o-mini",
|
|
4171
4174
|
baseUrl: process.env.AI_BASE_URL ?? "https://api.openai.com/v1",
|
|
@@ -4174,6 +4177,7 @@ export default defineConfig({
|
|
|
4174
4177
|
workers: 2
|
|
4175
4178
|
},
|
|
4176
4179
|
analysis: {
|
|
4180
|
+
backends: [],
|
|
4177
4181
|
excluded: [],
|
|
4178
4182
|
transversalInputs: [],
|
|
4179
4183
|
ignoredOutputs: [],
|
|
@@ -4201,18 +4205,18 @@ var init_default = (program, datasourceCommand) => void program.command("init").
|
|
|
4201
4205
|
const configDirectory = path.dirname(configPath);
|
|
4202
4206
|
intro("Initialize datasource configuration");
|
|
4203
4207
|
try {
|
|
4204
|
-
await fs.mkdir(configDirectory, { recursive: true });
|
|
4208
|
+
await fs$1.mkdir(configDirectory, { recursive: true });
|
|
4205
4209
|
if (!options.force) {
|
|
4206
4210
|
let exists = false;
|
|
4207
4211
|
try {
|
|
4208
|
-
await fs.access(configPath);
|
|
4212
|
+
await fs$1.access(configPath);
|
|
4209
4213
|
exists = true;
|
|
4210
4214
|
} catch (error) {
|
|
4211
4215
|
if (error.code !== "ENOENT") throw error;
|
|
4212
4216
|
}
|
|
4213
4217
|
if (exists) throw new Error(`${configPath} already exists. Use --force to overwrite it.`);
|
|
4214
4218
|
}
|
|
4215
|
-
await fs.writeFile(configPath, configTemplate, "utf8");
|
|
4219
|
+
await fs$1.writeFile(configPath, configTemplate, "utf8");
|
|
4216
4220
|
outro(`Created ${configPath}`);
|
|
4217
4221
|
} catch (error) {
|
|
4218
4222
|
cancel(error instanceof Error ? error.message : String(error));
|
|
@@ -4345,7 +4349,7 @@ function collectImpactedRoutes(params) {
|
|
|
4345
4349
|
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}`));
|
|
4346
4350
|
}
|
|
4347
4351
|
async function readChangedFiles(filePath) {
|
|
4348
|
-
return (await fs.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
|
|
4352
|
+
return (await fs$1.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
|
|
4349
4353
|
}
|
|
4350
4354
|
async function readRouteGraphs(outputDirectory) {
|
|
4351
4355
|
const graphFiles = await globby("**/*.graph.yaml", {
|
|
@@ -4353,7 +4357,7 @@ async function readRouteGraphs(outputDirectory) {
|
|
|
4353
4357
|
absolute: true
|
|
4354
4358
|
});
|
|
4355
4359
|
const graphs = await Promise.all(graphFiles.map(async (graphFile) => {
|
|
4356
|
-
const document = parse(await fs.readFile(graphFile, "utf8"));
|
|
4360
|
+
const document = parse(await fs$1.readFile(graphFile, "utf8"));
|
|
4357
4361
|
if (!isGraphDocument(document)) throw new Error(`Invalid graph document: ${graphFile}`);
|
|
4358
4362
|
return {
|
|
4359
4363
|
method: document.route.method,
|
|
@@ -4471,8 +4475,8 @@ async function reportChangedHandler(cwd, options) {
|
|
|
4471
4475
|
routes: coverages
|
|
4472
4476
|
});
|
|
4473
4477
|
const reportPath = path.resolve(cwd, options.report);
|
|
4474
|
-
await fs.mkdir(path.dirname(reportPath), { recursive: true });
|
|
4475
|
-
await fs.writeFile(reportPath, `${report}\n`, "utf8");
|
|
4478
|
+
await fs$1.mkdir(path.dirname(reportPath), { recursive: true });
|
|
4479
|
+
await fs$1.writeFile(reportPath, `${report}\n`, "utf8");
|
|
4476
4480
|
note(report, "Datasource mapping report");
|
|
4477
4481
|
}
|
|
4478
4482
|
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) => {
|
|
@@ -4492,13 +4496,7 @@ const program = new Command();
|
|
|
4492
4496
|
program.version("3.4.0-alpha.1");
|
|
4493
4497
|
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) => {
|
|
4494
4498
|
if (actionCommand.name() === "init") return;
|
|
4495
|
-
|
|
4496
|
-
const repoRoot = program.opts().projectRoot ?? config.repoRoot;
|
|
4497
|
-
setUserConfig({
|
|
4498
|
-
...config,
|
|
4499
|
-
repoRoot,
|
|
4500
|
-
cwd: repoRoot
|
|
4501
|
-
});
|
|
4499
|
+
setUserConfig(await loadAtlasConfig(program.opts().config, program.opts().projectRoot));
|
|
4502
4500
|
});
|
|
4503
4501
|
init_default(program, program);
|
|
4504
4502
|
generate_default(program);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineExpressionRule-Dfvzj6n2.mjs","names":[],"sources":["../src/utils/defineExpressionRule.ts"],"sourcesContent":["import type { Expression } from \"ts-morph\";\nimport type { UserConfig } from \"../models/types\";\n\nexport type FieldExtractionRuleResult = {\n backendField?: string;\n transparent?: boolean;\n mapperType?: string;\n apiMapping?: boolean;\n};\n\nexport type FieldExtractionRule = {\n name: string;\n match: (expression: Expression, config: UserConfig) => boolean;\n parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;\n};\n\nexport function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule {\n return rule;\n}\n"],"mappings":"AAgBA,SAAgB,EAAqB,EAAgD,CACnF,OAAO,CACT"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as UserConfig } from "./types-
|
|
1
|
+
import { r as defineExpressionRule, t as UserConfig } from "./types-3y34Gf8R.mjs";
|
|
2
2
|
//#region src/utils/defineConfig.d.ts
|
|
3
3
|
declare function defineConfig(config: UserConfig): UserConfig;
|
|
4
4
|
//#endregion
|
|
5
|
-
export { type UserConfig, defineConfig };
|
|
5
|
+
export { type UserConfig, defineConfig, defineExpressionRule };
|
|
6
6
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function
|
|
1
|
+
import{t as e}from"./defineExpressionRule-Dfvzj6n2.mjs";function t(e){return e}export{t as defineConfig,e as defineExpressionRule};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/defineConfig.ts"],"sourcesContent":["import type { UserConfig } from \"../models/types\";\n\nexport function defineConfig(config: UserConfig): UserConfig {\n return config;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/defineConfig.ts"],"sourcesContent":["import type { UserConfig } from \"../models/types\";\n\nexport function defineConfig(config: UserConfig): UserConfig {\n return config;\n}\n"],"mappings":"wDAEA,SAAgB,EAAa,EAAgC,CAC3D,OAAO,CACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`clean-object-wrapper`,match:e=>t.isCallExpression(e)&&/^(?:(?:MappingUtils\.)?cleanObject|clean)$/.test(e.getExpression().getText()),parse:()=>({transparent:!0})});export{n as cleanObjectRule};
|
|
2
2
|
//# sourceMappingURL=cleanObjectRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanObjectRule.mjs","names":[],"sources":["../../src/rules/cleanObjectRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/
|
|
1
|
+
{"version":3,"file":"cleanObjectRule.mjs","names":[],"sources":["../../src/rules/cleanObjectRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const cleanObjectRule = defineExpressionRule({\n name: \"clean-object-wrapper\",\n match: (expression) =>\n Node.isCallExpression(expression) && /^(?:(?:MappingUtils\\.)?cleanObject|clean)$/.test(expression.getExpression().getText()),\n parse: () => ({ transparent: true })\n});\n"],"mappings":"yFAGA,MAAa,EAAkB,EAAqB,CAClD,KAAM,uBACN,MAAQ,GACN,EAAK,iBAAiB,CAAU,GAAK,6CAA6C,KAAK,EAAW,cAAc,CAAC,CAAC,QAAQ,CAAC,EAC7H,WAAc,CAAE,YAAa,EAAK,EACpC,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as FieldExtractionRule } from "../types-
|
|
1
|
+
import { n as FieldExtractionRule } from "../types-3y34Gf8R.mjs";
|
|
2
2
|
//#region src/rules/cmsI18nFieldRule.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* `CmsI18n#get`/`getAll` reads a localized field off the translations array passed to the
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";function n(e){if(!t.isIdentifier(e))return;let n=(e.getSymbol()?.getDeclarations().find(t.isVariableDeclaration))?.getInitializer();if(!n||!t.isNewExpression(n)||n.getExpression().getText()!==`CmsI18n`)return;let r=n.getArguments()[0];return r&&t.isExpression(r)?r.getText():void 0}function r(e){if(!t.isCallExpression(e))return;let r=e.getExpression();if(!t.isPropertyAccessExpression(r)||![`get`,`getAll`].includes(r.getName()))return;let i=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!i||!t.isStringLiteral(i)&&!t.isNoSubstitutionTemplateLiteral(i))return;let a=n(r.getExpression());return a?`${a}[locale].${i.getLiteralValue()}`:void 0}const i=e({name:`cms-i18n-field`,match:e=>!!r(e),parse:e=>{let t=r(e);return t?{backendField:t}:void 0}});export{i as cmsI18nFieldRule};
|
|
2
2
|
//# sourceMappingURL=cmsI18nFieldRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cmsI18nFieldRule.mjs","names":[],"sources":["../../src/rules/cmsI18nFieldRule.ts"],"sourcesContent":["import { type Expression, Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/
|
|
1
|
+
{"version":3,"file":"cmsI18nFieldRule.mjs","names":[],"sources":["../../src/rules/cmsI18nFieldRule.ts"],"sourcesContent":["import { type Expression, Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nconst CMS_I18N_CLASS_NAME = \"CmsI18n\";\n\nfunction resolveCmsI18nSource(receiver: Expression): string | undefined {\n if (!Node.isIdentifier(receiver)) {\n return undefined;\n }\n\n const declaration = receiver.getSymbol()?.getDeclarations().find(Node.isVariableDeclaration);\n const initializer = declaration?.getInitializer();\n if (!initializer || !Node.isNewExpression(initializer)) {\n return undefined;\n }\n\n if (initializer.getExpression().getText() !== CMS_I18N_CLASS_NAME) {\n return undefined;\n }\n\n const constructorArgument = initializer.getArguments()[0];\n return constructorArgument && Node.isExpression(constructorArgument) ? constructorArgument.getText() : undefined;\n}\n\nfunction parseCmsI18nField(expression: Expression): string | undefined {\n if (!Node.isCallExpression(expression)) {\n return undefined;\n }\n\n const target = expression.getExpression();\n if (!Node.isPropertyAccessExpression(target) || ![\"get\", \"getAll\"].includes(target.getName())) {\n return undefined;\n }\n\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n if (!field || (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))) {\n return undefined;\n }\n\n const source = resolveCmsI18nSource(target.getExpression());\n return source ? `${source}[locale].${field.getLiteralValue()}` : undefined;\n}\n\n/**\n * `CmsI18n#get`/`getAll` reads a localized field off the translations array passed to the\n * constructor (e.g. `new CmsI18n(offer.translations)`). Resolving to `i18n.field` breaks\n * domain-path matching downstream because the receiver name (\"i18n\") is not the domain root\n * (\"offer\"); resolving through the constructor argument restores that root.\n */\nexport const cmsI18nFieldRule = defineExpressionRule({\n name: \"cms-i18n-field\",\n match: (expression) => Boolean(parseCmsI18nField(expression)),\n parse: (expression) => {\n const backendField = parseCmsI18nField(expression);\n return backendField ? { backendField } : undefined;\n }\n});\n"],"mappings":"yFAKA,SAAS,EAAqB,EAA0C,CACtE,GAAI,CAAC,EAAK,aAAa,CAAQ,EAC7B,OAIF,IAAM,GADc,EAAS,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC,KAAK,EAAK,qBAAqB,EAAA,EAC1D,eAAe,EAKhD,GAJI,CAAC,GAAe,CAAC,EAAK,gBAAgB,CAAW,GAIjD,EAAY,cAAc,CAAC,CAAC,QAAQ,IAAM,UAC5C,OAGF,IAAM,EAAsB,EAAY,aAAa,CAAC,CAAC,GACvD,OAAO,GAAuB,EAAK,aAAa,CAAmB,EAAI,EAAoB,QAAQ,EAAI,IAAA,EACzG,CAEA,SAAS,EAAkB,EAA4C,CACrE,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,IAAM,EAAS,EAAW,cAAc,EACxC,GAAI,CAAC,EAAK,2BAA2B,CAAM,GAAK,CAAC,CAAC,MAAO,QAAQ,CAAC,CAAC,SAAS,EAAO,QAAQ,CAAC,EAC1F,OAGF,IAAM,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EACtG,GAAI,CAAC,GAAU,CAAC,EAAK,gBAAgB,CAAK,GAAK,CAAC,EAAK,gCAAgC,CAAK,EACxF,OAGF,IAAM,EAAS,EAAqB,EAAO,cAAc,CAAC,EAC1D,OAAO,EAAS,GAAG,EAAO,WAAW,EAAM,gBAAgB,IAAM,IAAA,EACnE,CAQA,MAAa,EAAmB,EAAqB,CACnD,KAAM,iBACN,MAAQ,GAAe,EAAQ,EAAkB,CAAU,EAC3D,MAAQ,GAAe,CACrB,IAAM,EAAe,EAAkB,CAAU,EACjD,OAAO,EAAe,CAAE,cAAa,EAAI,IAAA,EAC3C,CACF,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`date-utils-wrapper`,match:e=>t.isCallExpression(e)&&e.getExpression().getText().startsWith(`DateUtils.`),parse:()=>({transparent:!0})});export{n as dateConversionRule};
|
|
2
2
|
//# sourceMappingURL=dateConversionRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dateConversionRule.mjs","names":[],"sources":["../../src/rules/dateConversionRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/
|
|
1
|
+
{"version":3,"file":"dateConversionRule.mjs","names":[],"sources":["../../src/rules/dateConversionRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const dateConversionRule = defineExpressionRule({\n name: \"date-utils-wrapper\",\n match: (expression) => Node.isCallExpression(expression) && expression.getExpression().getText().startsWith(\"DateUtils.\"),\n parse: () => ({ transparent: true })\n});\n"],"mappings":"yFAGA,MAAa,EAAqB,EAAqB,CACrD,KAAM,qBACN,MAAQ,GAAe,EAAK,iBAAiB,CAAU,GAAK,EAAW,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,YAAY,EACxH,WAAc,CAAE,YAAa,EAAK,EACpC,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`lodash-get`,match:e=>{if(!t.isCallExpression(e)||e.getExpression().getText()!==`get`)return!1;let n=e.getArguments()[1];return t.isStringLiteral(n)||t.isNoSubstitutionTemplateLiteral(n)},parse:e=>{if(!t.isCallExpression(e))return;let[n,r]=e.getArguments();if(!(!n||!t.isExpression(n)||!t.isStringLiteral(r)&&!t.isNoSubstitutionTemplateLiteral(r)))return{backendField:`${n.getText()}.${r.getLiteralValue()}`}}});export{n as lodashGetRule};
|
|
2
2
|
//# sourceMappingURL=lodashGetRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lodashGetRule.mjs","names":[],"sources":["../../src/rules/lodashGetRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/
|
|
1
|
+
{"version":3,"file":"lodashGetRule.mjs","names":[],"sources":["../../src/rules/lodashGetRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const lodashGetRule = defineExpressionRule({\n name: \"lodash-get\",\n match: (expression) => {\n if (!Node.isCallExpression(expression) || expression.getExpression().getText() !== \"get\") {\n return false;\n }\n\n const path = expression.getArguments()[1];\n return Node.isStringLiteral(path) || Node.isNoSubstitutionTemplateLiteral(path);\n },\n parse: (expression) => {\n if (!Node.isCallExpression(expression)) {\n return undefined;\n }\n\n const [object, path] = expression.getArguments();\n if (!object || !Node.isExpression(object) || (!Node.isStringLiteral(path) && !Node.isNoSubstitutionTemplateLiteral(path))) {\n return undefined;\n }\n\n return { backendField: `${object.getText()}.${path.getLiteralValue()}` };\n }\n});\n"],"mappings":"yFAGA,MAAa,EAAgB,EAAqB,CAChD,KAAM,aACN,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,GAAK,EAAW,cAAc,CAAC,CAAC,QAAQ,IAAM,MACjF,MAAO,GAGT,IAAM,EAAO,EAAW,aAAa,CAAC,CAAC,GACvC,OAAO,EAAK,gBAAgB,CAAI,GAAK,EAAK,gCAAgC,CAAI,CAChF,EACA,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,GAAM,CAAC,EAAQ,GAAQ,EAAW,aAAa,EAC3C,MAAC,GAAU,CAAC,EAAK,aAAa,CAAM,GAAM,CAAC,EAAK,gBAAgB,CAAI,GAAK,CAAC,EAAK,gCAAgC,CAAI,GAIvH,MAAO,CAAE,aAAc,GAAG,EAAO,QAAQ,EAAE,GAAG,EAAK,gBAAgB,GAAI,CACzE,CACF,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=new Set([`string`,`number`,`boolean`,`date`,`datetime`,`enum`,`array`,`object`]),r=e({name:`mapping-utility-wrapper`,match:(e,n)=>{if(!t.isCallExpression(e))return!1;let r=e.getExpression().getText();return n.analysis.neutralExpressionMatchers.some(e=>r.startsWith(e.prefix))},parse:(e,r)=>{if(!t.isCallExpression(e))return;let i=e.getExpression().getText(),a=r.analysis.neutralExpressionMatchers.find(e=>i.startsWith(e.prefix));return{transparent:!0,mapperType:i.split(`.`).find(e=>n.has(e)),apiMapping:a?.apiMapping??!1}}});export{r as mappingUtilityRule};
|
|
2
2
|
//# sourceMappingURL=mappingUtilityRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mappingUtilityRule.mjs","names":[],"sources":["../../src/rules/mappingUtilityRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/
|
|
1
|
+
{"version":3,"file":"mappingUtilityRule.mjs","names":[],"sources":["../../src/rules/mappingUtilityRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nconst mapperTypes = new Set([\"string\", \"number\", \"boolean\", \"date\", \"datetime\", \"enum\", \"array\", \"object\"]);\n\nexport const mappingUtilityRule = defineExpressionRule({\n name: \"mapping-utility-wrapper\",\n match: (expression, config) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const callee = expression.getExpression().getText();\n return config.analysis.neutralExpressionMatchers.some((matcher) => callee.startsWith(matcher.prefix));\n },\n parse: (expression, config) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const callee = expression.getExpression().getText();\n const matcher = config.analysis.neutralExpressionMatchers.find((item) => callee.startsWith(item.prefix));\n const mapperType = callee.split(\".\").find((segment) => mapperTypes.has(segment));\n\n return {\n transparent: true,\n mapperType,\n apiMapping: matcher?.apiMapping ?? false\n };\n }\n});\n"],"mappings":"yFAGA,MAAM,EAAc,IAAI,IAAI,CAAC,SAAU,SAAU,UAAW,OAAQ,WAAY,OAAQ,QAAS,QAAQ,CAAC,EAE7F,EAAqB,EAAqB,CACrD,KAAM,0BACN,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,MAAO,GAE/C,IAAM,EAAS,EAAW,cAAc,CAAC,CAAC,QAAQ,EAClD,OAAO,EAAO,SAAS,0BAA0B,KAAM,GAAY,EAAO,WAAW,EAAQ,MAAM,CAAC,CACtG,EACA,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,OAExC,IAAM,EAAS,EAAW,cAAc,CAAC,CAAC,QAAQ,EAC5C,EAAU,EAAO,SAAS,0BAA0B,KAAM,GAAS,EAAO,WAAW,EAAK,MAAM,CAAC,EAGvG,MAAO,CACL,YAAa,GACb,WAJiB,EAAO,MAAM,GAAG,CAAC,CAAC,KAAM,GAAY,EAAY,IAAI,CAAO,CAInE,EACT,WAAY,GAAS,YAAc,EACrC,CACF,CACF,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`member-get-field`,match:e=>{if(!t.isCallExpression(e))return!1;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));return t.isPropertyAccessExpression(n)&&[`get`,`getAll`].includes(n.getName())&&!!r},parse:e=>{if(!t.isCallExpression(e))return;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!(!t.isPropertyAccessExpression(n)||!t.isStringLiteral(r)&&!t.isNoSubstitutionTemplateLiteral(r)))return{backendField:`${n.getExpression().getText()}.${r.getLiteralValue()}`}}});export{n as memberGetFieldRule};
|
|
2
2
|
//# sourceMappingURL=memberGetFieldRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memberGetFieldRule.mjs","names":[],"sources":["../../src/rules/memberGetFieldRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/
|
|
1
|
+
{"version":3,"file":"memberGetFieldRule.mjs","names":[],"sources":["../../src/rules/memberGetFieldRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const memberGetFieldRule = defineExpressionRule({\n name: \"member-get-field\",\n match: (expression) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n return Node.isPropertyAccessExpression(target) && [\"get\", \"getAll\"].includes(target.getName()) && Boolean(field);\n },\n parse: (expression) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n if (!Node.isPropertyAccessExpression(target) || (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))) {\n return undefined;\n }\n\n return { backendField: `${target.getExpression().getText()}.${field.getLiteralValue()}` };\n }\n});\n"],"mappings":"yFAGA,MAAa,EAAqB,EAAqB,CACrD,KAAM,mBACN,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,MAAO,GAE/C,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EACtG,OAAO,EAAK,2BAA2B,CAAM,GAAK,CAAC,MAAO,QAAQ,CAAC,CAAC,SAAS,EAAO,QAAQ,CAAC,GAAK,EAAQ,CAC5G,EACA,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,OAExC,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EAClG,MAAC,EAAK,2BAA2B,CAAM,GAAM,CAAC,EAAK,gBAAgB,CAAK,GAAK,CAAC,EAAK,gCAAgC,CAAK,GAI5H,MAAO,CAAE,aAAc,GAAG,EAAO,cAAc,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAM,gBAAgB,GAAI,CAC1F,CACF,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"../
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";function n(e){if(!t.isPropertyAccessExpression(e))return;let n=e.getExpression();if(!t.isElementAccessExpression(n))return;let r=n.getExpression();if(!t.isCallExpression(r))return;let i=r.getExpression(),a=r.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!(!t.isPropertyAccessExpression(i)||i.getName()!==`getAll`||!a||!t.isStringLiteral(a)&&!t.isNoSubstitutionTemplateLiteral(a)))return`${a.getLiteralValue()}[locale].${e.getName()}`}const r=e({name:`quable-i18n-field`,match:e=>!!n(e),parse:e=>{let t=n(e);return t?{backendField:t}:void 0}});export{r as quableI18nFieldRule};
|
|
2
2
|
//# sourceMappingURL=quableI18nFieldRule.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quableI18nFieldRule.mjs","names":[],"sources":["../../src/rules/quableI18nFieldRule.ts"],"sourcesContent":["import { type Expression, Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/
|
|
1
|
+
{"version":3,"file":"quableI18nFieldRule.mjs","names":[],"sources":["../../src/rules/quableI18nFieldRule.ts"],"sourcesContent":["import { type Expression, Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nfunction parseLocalizedI18nField(expression: Expression): string | undefined {\n if (!Node.isPropertyAccessExpression(expression)) {\n return undefined;\n }\n\n const elementAccess = expression.getExpression();\n\n if (!Node.isElementAccessExpression(elementAccess)) {\n return undefined;\n }\n\n const getAllCall = elementAccess.getExpression();\n\n if (!Node.isCallExpression(getAllCall)) {\n return undefined;\n }\n\n const target = getAllCall.getExpression();\n const field = getAllCall\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n\n if (\n !Node.isPropertyAccessExpression(target) ||\n target.getName() !== \"getAll\" ||\n !field ||\n (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))\n ) {\n return undefined;\n }\n\n return `${field.getLiteralValue()}[locale].${expression.getName()}`;\n}\n\nexport const quableI18nFieldRule = defineExpressionRule({\n name: \"quable-i18n-field\",\n match: (expression) => Boolean(parseLocalizedI18nField(expression)),\n parse: (expression) => {\n const backendField = parseLocalizedI18nField(expression);\n return backendField ? { backendField } : undefined;\n }\n});\n"],"mappings":"yFAGA,SAAS,EAAwB,EAA4C,CAC3E,GAAI,CAAC,EAAK,2BAA2B,CAAU,EAC7C,OAGF,IAAM,EAAgB,EAAW,cAAc,EAE/C,GAAI,CAAC,EAAK,0BAA0B,CAAa,EAC/C,OAGF,IAAM,EAAa,EAAc,cAAc,EAE/C,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EAGpG,MAAC,EAAK,2BAA2B,CAAM,GACvC,EAAO,QAAQ,IAAM,UACrB,CAAC,GACA,CAAC,EAAK,gBAAgB,CAAK,GAAK,CAAC,EAAK,gCAAgC,CAAK,GAK9E,MAAO,GAAG,EAAM,gBAAgB,EAAE,WAAW,EAAW,QAAQ,GAClE,CAEA,MAAa,EAAsB,EAAqB,CACtD,KAAM,oBACN,MAAQ,GAAe,EAAQ,EAAwB,CAAU,EACjE,MAAQ,GAAe,CACrB,IAAM,EAAe,EAAwB,CAAU,EACvD,OAAO,EAAe,CAAE,cAAa,EAAI,IAAA,EAC3C,CACF,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Expression } from "ts-morph";
|
|
2
|
-
//#region src/utils/
|
|
2
|
+
//#region src/utils/defineExpressionRule.d.ts
|
|
3
3
|
type FieldExtractionRuleResult = {
|
|
4
4
|
backendField?: string;
|
|
5
5
|
transparent?: boolean;
|
|
@@ -11,6 +11,7 @@ type FieldExtractionRule = {
|
|
|
11
11
|
match: (expression: Expression, config: UserConfig) => boolean;
|
|
12
12
|
parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;
|
|
13
13
|
};
|
|
14
|
+
declare function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule;
|
|
14
15
|
//#endregion
|
|
15
16
|
//#region src/models/types.d.ts
|
|
16
17
|
interface NeutralExpressionMatcher {
|
|
@@ -23,8 +24,6 @@ interface UserConfig {
|
|
|
23
24
|
* Can be set via the ATLAS_CWD environment variable.
|
|
24
25
|
*/
|
|
25
26
|
repoRoot?: string;
|
|
26
|
-
/** Path, relative to `cwd`, to the file declaring backend identifiers. */
|
|
27
|
-
backendTypesFile: string;
|
|
28
27
|
/**
|
|
29
28
|
* Default OpenAPI document URL used to extract the public contract exposed by the API.
|
|
30
29
|
* The CLI can still override this value with `--openapi-url`.
|
|
@@ -34,9 +33,13 @@ interface UserConfig {
|
|
|
34
33
|
openapiTimeoutMs: number;
|
|
35
34
|
/** Default Directus base URL used by `push` when no CLI option or environment variable overrides it. */
|
|
36
35
|
directusUrl: string;
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Module aliases resolved while traversing static-analysis dependencies.
|
|
38
|
+
* Defaults are read from `compilerOptions.paths` in the repository tsconfig.
|
|
39
|
+
*/
|
|
40
|
+
resolver?: {
|
|
41
|
+
/** Explicit aliases override aliases read from tsconfig. */
|
|
42
|
+
alias?: Record<string, string>;
|
|
40
43
|
};
|
|
41
44
|
/** Defaults used by the optional AI pass that reviews unresolved static mappings. */
|
|
42
45
|
inference: {
|
|
@@ -55,6 +58,8 @@ interface UserConfig {
|
|
|
55
58
|
* Static analysis options used to keep generated artifacts focused on business-relevant files and fields.
|
|
56
59
|
*/
|
|
57
60
|
analysis: {
|
|
61
|
+
/** Backend identifiers used by the API project. */
|
|
62
|
+
backends: string[];
|
|
58
63
|
/**
|
|
59
64
|
* Glob patterns excluded from `analysis_files`.
|
|
60
65
|
* Use this to hide technical plumbing files that add noise to route review documents.
|
|
@@ -93,5 +98,5 @@ interface UserConfig {
|
|
|
93
98
|
};
|
|
94
99
|
}
|
|
95
100
|
//#endregion
|
|
96
|
-
export { FieldExtractionRule as n, UserConfig as t };
|
|
97
|
-
//# sourceMappingURL=types-
|
|
101
|
+
export { FieldExtractionRule as n, defineExpressionRule as r, UserConfig as t };
|
|
102
|
+
//# sourceMappingURL=types-3y34Gf8R.d.mts.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"defineRule-Dfvzj6n2.mjs","names":[],"sources":["../src/utils/defineRule.ts"],"sourcesContent":["import type { Expression } from \"ts-morph\";\nimport type { UserConfig } from \"../models/types\";\n\nexport type FieldExtractionRuleResult = {\n backendField?: string;\n transparent?: boolean;\n mapperType?: string;\n apiMapping?: boolean;\n};\n\nexport type FieldExtractionRule = {\n name: string;\n match: (expression: Expression, config: UserConfig) => boolean;\n parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;\n};\n\nexport function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule {\n return rule;\n}\n"],"mappings":"AAgBA,SAAgB,EAAqB,EAAgD,CACnF,OAAO,CACT"}
|