@cmflow/atlas 3.4.0-beta.3 → 3.4.0-beta.5
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 +48 -1
- package/dist/bin/atlas.mjs +49 -48
- package/dist/defineExpressionRule-Dfvzj6n2.mjs +2 -0
- package/dist/defineExpressionRule-Dfvzj6n2.mjs.map +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/rules/cleanObjectRule.d.mts +6 -0
- package/dist/rules/cleanObjectRule.mjs +2 -0
- package/dist/rules/cleanObjectRule.mjs.map +1 -0
- package/dist/rules/cmsI18nFieldRule.d.mts +12 -0
- package/dist/rules/cmsI18nFieldRule.mjs +2 -0
- package/dist/rules/cmsI18nFieldRule.mjs.map +1 -0
- package/dist/rules/dateConversionRule.d.mts +6 -0
- package/dist/rules/dateConversionRule.mjs +2 -0
- package/dist/rules/dateConversionRule.mjs.map +1 -0
- package/dist/rules/lodashGetRule.d.mts +6 -0
- package/dist/rules/lodashGetRule.mjs +2 -0
- package/dist/rules/lodashGetRule.mjs.map +1 -0
- package/dist/rules/mappingUtilityRule.d.mts +6 -0
- package/dist/rules/mappingUtilityRule.mjs +2 -0
- package/dist/rules/mappingUtilityRule.mjs.map +1 -0
- package/dist/rules/memberGetFieldRule.d.mts +6 -0
- package/dist/rules/memberGetFieldRule.mjs +2 -0
- package/dist/rules/memberGetFieldRule.mjs.map +1 -0
- package/dist/rules/quableI18nFieldRule.d.mts +6 -0
- package/dist/rules/quableI18nFieldRule.mjs +2 -0
- package/dist/rules/quableI18nFieldRule.mjs.map +1 -0
- package/dist/types-DcvdOZ2j.d.mts +102 -0
- package/package.json +10 -3
- package/dist/defineRule-Dfvzj6n2.mjs +0 -2
- package/dist/defineRule-Dfvzj6n2.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -50,13 +50,60 @@ The configuration imports `defineConfig` from Atlas:
|
|
|
50
50
|
import { defineConfig } from "@cmflow/atlas";
|
|
51
51
|
|
|
52
52
|
export default defineConfig({
|
|
53
|
-
|
|
53
|
+
backends: ["ICC", "CMS", "CMS_B2C"],
|
|
54
54
|
openapiUrl: "https://api.example.com/openapi.json",
|
|
55
55
|
openapiTimeoutMs: 60_000,
|
|
56
56
|
directusUrl: "https://cms.api.clubmed"
|
|
57
57
|
});
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
## Custom expression rules
|
|
63
|
+
|
|
64
|
+
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`.
|
|
65
|
+
|
|
66
|
+
`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.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { defineConfig, defineExpressionRule } from "@cmflow/atlas";
|
|
70
|
+
import { Node } from "ts-morph";
|
|
71
|
+
|
|
72
|
+
const localizedFieldRule = defineExpressionRule({
|
|
73
|
+
name: "localized-field",
|
|
74
|
+
match: (expression) =>
|
|
75
|
+
Node.isCallExpression(expression) && expression.getExpression().getText() === "localizedField",
|
|
76
|
+
parse: (expression) => {
|
|
77
|
+
if (!Node.isCallExpression(expression)) return undefined;
|
|
78
|
+
|
|
79
|
+
const [source, field] = expression.getArguments();
|
|
80
|
+
if (!source || !Node.isExpression(source) || !Node.isStringLiteral(field)) return undefined;
|
|
81
|
+
|
|
82
|
+
return { backendField: `${source.getText()}.${field.getLiteralValue()}` };
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export default defineConfig({
|
|
87
|
+
// …other Atlas options
|
|
88
|
+
analysis: {
|
|
89
|
+
// …other analysis options
|
|
90
|
+
rules: [localizedFieldRule]
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For a wrapper that does not change the field path, return `transparent: true`:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const unwrapRule = defineExpressionRule({
|
|
99
|
+
name: "unwrap-api-value",
|
|
100
|
+
match: (expression) => Node.isCallExpression(expression) && expression.getExpression().getText() === "unwrap",
|
|
101
|
+
parse: () => ({ transparent: true })
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`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.
|
|
106
|
+
|
|
60
107
|
Create a `.env.local` in the target API project when pushing to Directus:
|
|
61
108
|
|
|
62
109
|
```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)}`);
|
|
@@ -375,18 +392,11 @@ function extractOpenApiOutputProperties(operation, swagger) {
|
|
|
375
392
|
}
|
|
376
393
|
//#endregion
|
|
377
394
|
//#region src/services/backendTypeService.ts
|
|
378
|
-
|
|
395
|
+
function getBackendTypes() {
|
|
396
|
+
return Object.fromEntries(getUserConfig().backends.map((backend) => [backend, backend]));
|
|
397
|
+
}
|
|
379
398
|
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);
|
|
399
|
+
return Object.hasOwn(getBackendTypes(), value);
|
|
390
400
|
}
|
|
391
401
|
//#endregion
|
|
392
402
|
//#region src/services/backendSourceService.ts
|
|
@@ -481,7 +491,7 @@ function tryResolveWithExtensions(basePath) {
|
|
|
481
491
|
].filter((candidate) => Boolean(candidate));
|
|
482
492
|
for (const candidate of candidates) try {
|
|
483
493
|
const normalized = path.normalize(candidate);
|
|
484
|
-
if (fs
|
|
494
|
+
if (fs.existsSync(normalized)) return normalized;
|
|
485
495
|
} catch {
|
|
486
496
|
continue;
|
|
487
497
|
}
|
|
@@ -2349,7 +2359,7 @@ async function pushCatalogueToDirectus(catalogue, options) {
|
|
|
2349
2359
|
}
|
|
2350
2360
|
}
|
|
2351
2361
|
}
|
|
2352
|
-
if (options.outputPath) await fs.appendFile(options.outputPath, "", "utf8");
|
|
2362
|
+
if (options.outputPath) await fs$1.appendFile(options.outputPath, "", "utf8");
|
|
2353
2363
|
const removedOrphanedLinks = await cleanupOrphanedPropertyLinks(client);
|
|
2354
2364
|
taskProgressService.report("Finalizing Directus synchronization");
|
|
2355
2365
|
if (warnings.size) {
|
|
@@ -2545,12 +2555,12 @@ async function writeRouteReviewDocument(catalogue, outputDir, repoRoot, routeKey
|
|
|
2545
2555
|
if (!route) throw new Error(`Unable to write review document for unknown route key ${routeKey}`);
|
|
2546
2556
|
const document = relativizeRouteReviewDocument(buildRouteReviewDocument(catalogue, route.key), repoRoot);
|
|
2547
2557
|
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");
|
|
2558
|
+
await fs$1.mkdir(path.dirname(filePath), { recursive: true });
|
|
2559
|
+
await fs$1.writeFile(filePath, `${stringify(document)}\n`, "utf8");
|
|
2550
2560
|
return filePath;
|
|
2551
2561
|
}
|
|
2552
2562
|
async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
|
|
2553
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
2563
|
+
await fs$1.mkdir(outputDir, { recursive: true });
|
|
2554
2564
|
for (const [index, route] of catalogue.routes.entries()) {
|
|
2555
2565
|
taskProgressService.log(`[${index + 1}/${catalogue.routes.length}] - ${route.method} ${route.path} ...`);
|
|
2556
2566
|
await writeRouteReviewDocument(catalogue, outputDir, repoRoot, route.key);
|
|
@@ -2558,8 +2568,8 @@ async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
|
|
|
2558
2568
|
return outputDir;
|
|
2559
2569
|
}
|
|
2560
2570
|
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 })));
|
|
2571
|
+
await fs$1.mkdir(outputDir, { recursive: true });
|
|
2572
|
+
await Promise.all(["catalogue.yaml", "catalogue.yml"].map((fileName) => fs$1.rm(path.join(outputDir, fileName), { force: true })));
|
|
2563
2573
|
}
|
|
2564
2574
|
async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.cwd()) {
|
|
2565
2575
|
await prepareCatalogueOutputDirectory(outputDir);
|
|
@@ -2737,7 +2747,7 @@ async function readCatalogueFromDirectory(inputDir) {
|
|
|
2737
2747
|
if (!files.length) throw new Error(`No route YAML documents found in ${inputDir}`);
|
|
2738
2748
|
return {
|
|
2739
2749
|
catalogue: mergeRouteReviewDocuments(await Promise.all(files.sort().map(async (file) => {
|
|
2740
|
-
const parsed = parse(await fs.readFile(file, "utf8"));
|
|
2750
|
+
const parsed = parse(await fs$1.readFile(file, "utf8"));
|
|
2741
2751
|
if (parsed?.schema_version !== 1 || !parsed?.route?.method || !parsed?.route?.path) throw new Error(`Invalid route YAML document: ${file}`);
|
|
2742
2752
|
return parsed;
|
|
2743
2753
|
}))),
|
|
@@ -2931,8 +2941,8 @@ function buildBackendTopologyRelativePath(method, routePath) {
|
|
|
2931
2941
|
}
|
|
2932
2942
|
async function writeBackendTopologyArtifact(artifact, outputDir) {
|
|
2933
2943
|
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");
|
|
2944
|
+
await fs$1.mkdir(path.dirname(filePath), { recursive: true });
|
|
2945
|
+
await fs$1.writeFile(filePath, `${stringify(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
|
|
2936
2946
|
return filePath;
|
|
2937
2947
|
}
|
|
2938
2948
|
function isRecord(value) {
|
|
@@ -2951,7 +2961,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
|
|
|
2951
2961
|
const filePath = path.join(outputDir, buildBackendTopologyRelativePath(method, routePath));
|
|
2952
2962
|
let content;
|
|
2953
2963
|
try {
|
|
2954
|
-
content = await fs.readFile(filePath, "utf8");
|
|
2964
|
+
content = await fs$1.readFile(filePath, "utf8");
|
|
2955
2965
|
} catch (error) {
|
|
2956
2966
|
if (isRecord(error) && error.code === "ENOENT") throw new Error(`Backend graph artifact not found for ${method} ${routePath}: ${filePath}. Run generate:graph first.`);
|
|
2957
2967
|
throw error;
|
|
@@ -2972,7 +2982,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
|
|
|
2972
2982
|
const missingFiles = [];
|
|
2973
2983
|
await Promise.all(analysisFiles.map(async (analysisFile) => {
|
|
2974
2984
|
try {
|
|
2975
|
-
await fs.access(analysisFile);
|
|
2985
|
+
await fs$1.access(analysisFile);
|
|
2976
2986
|
} catch {
|
|
2977
2987
|
missingFiles.push(path.relative(cwd, analysisFile));
|
|
2978
2988
|
}
|
|
@@ -3432,7 +3442,7 @@ async function readSourceExcerpts(document) {
|
|
|
3432
3442
|
const excerpts = [];
|
|
3433
3443
|
let totalCharacters = 0;
|
|
3434
3444
|
for (const location of uniqueLocations) try {
|
|
3435
|
-
const lines = (await fs.readFile(location.sourceFile, "utf8")).split("\n");
|
|
3445
|
+
const lines = (await fs$1.readFile(location.sourceFile, "utf8")).split("\n");
|
|
3436
3446
|
const startLine = Math.max(1, location.line - EXCERPT_RADIUS);
|
|
3437
3447
|
const endLine = Math.min(lines.length, location.line + EXCERPT_RADIUS);
|
|
3438
3448
|
const code = lines.slice(startLine - 1, endLine).map((line, index) => `${String(startLine + index).padStart(5)} | ${line}`).join("\n");
|
|
@@ -4159,13 +4169,10 @@ const configTemplate = `import { defineConfig } from "@cmflow/atlas";
|
|
|
4159
4169
|
|
|
4160
4170
|
export default defineConfig({
|
|
4161
4171
|
repoRoot: process.cwd(),
|
|
4162
|
-
|
|
4172
|
+
backends: [],
|
|
4163
4173
|
openapiUrl: "https://api.example.com/openapi.json",
|
|
4164
4174
|
openapiTimeoutMs: 60_000,
|
|
4165
4175
|
directusUrl: "https://cms.api.clubmed",
|
|
4166
|
-
resolver: {
|
|
4167
|
-
alias: {}
|
|
4168
|
-
},
|
|
4169
4176
|
inference: {
|
|
4170
4177
|
model: process.env.AI_MODEL ?? "gpt-4o-mini",
|
|
4171
4178
|
baseUrl: process.env.AI_BASE_URL ?? "https://api.openai.com/v1",
|
|
@@ -4201,18 +4208,18 @@ var init_default = (program, datasourceCommand) => void program.command("init").
|
|
|
4201
4208
|
const configDirectory = path.dirname(configPath);
|
|
4202
4209
|
intro("Initialize datasource configuration");
|
|
4203
4210
|
try {
|
|
4204
|
-
await fs.mkdir(configDirectory, { recursive: true });
|
|
4211
|
+
await fs$1.mkdir(configDirectory, { recursive: true });
|
|
4205
4212
|
if (!options.force) {
|
|
4206
4213
|
let exists = false;
|
|
4207
4214
|
try {
|
|
4208
|
-
await fs.access(configPath);
|
|
4215
|
+
await fs$1.access(configPath);
|
|
4209
4216
|
exists = true;
|
|
4210
4217
|
} catch (error) {
|
|
4211
4218
|
if (error.code !== "ENOENT") throw error;
|
|
4212
4219
|
}
|
|
4213
4220
|
if (exists) throw new Error(`${configPath} already exists. Use --force to overwrite it.`);
|
|
4214
4221
|
}
|
|
4215
|
-
await fs.writeFile(configPath, configTemplate, "utf8");
|
|
4222
|
+
await fs$1.writeFile(configPath, configTemplate, "utf8");
|
|
4216
4223
|
outro(`Created ${configPath}`);
|
|
4217
4224
|
} catch (error) {
|
|
4218
4225
|
cancel(error instanceof Error ? error.message : String(error));
|
|
@@ -4345,7 +4352,7 @@ function collectImpactedRoutes(params) {
|
|
|
4345
4352
|
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
4353
|
}
|
|
4347
4354
|
async function readChangedFiles(filePath) {
|
|
4348
|
-
return (await fs.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
|
|
4355
|
+
return (await fs$1.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
|
|
4349
4356
|
}
|
|
4350
4357
|
async function readRouteGraphs(outputDirectory) {
|
|
4351
4358
|
const graphFiles = await globby("**/*.graph.yaml", {
|
|
@@ -4353,7 +4360,7 @@ async function readRouteGraphs(outputDirectory) {
|
|
|
4353
4360
|
absolute: true
|
|
4354
4361
|
});
|
|
4355
4362
|
const graphs = await Promise.all(graphFiles.map(async (graphFile) => {
|
|
4356
|
-
const document = parse(await fs.readFile(graphFile, "utf8"));
|
|
4363
|
+
const document = parse(await fs$1.readFile(graphFile, "utf8"));
|
|
4357
4364
|
if (!isGraphDocument(document)) throw new Error(`Invalid graph document: ${graphFile}`);
|
|
4358
4365
|
return {
|
|
4359
4366
|
method: document.route.method,
|
|
@@ -4471,8 +4478,8 @@ async function reportChangedHandler(cwd, options) {
|
|
|
4471
4478
|
routes: coverages
|
|
4472
4479
|
});
|
|
4473
4480
|
const reportPath = path.resolve(cwd, options.report);
|
|
4474
|
-
await fs.mkdir(path.dirname(reportPath), { recursive: true });
|
|
4475
|
-
await fs.writeFile(reportPath, `${report}\n`, "utf8");
|
|
4481
|
+
await fs$1.mkdir(path.dirname(reportPath), { recursive: true });
|
|
4482
|
+
await fs$1.writeFile(reportPath, `${report}\n`, "utf8");
|
|
4476
4483
|
note(report, "Datasource mapping report");
|
|
4477
4484
|
}
|
|
4478
4485
|
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 +4499,7 @@ const program = new Command();
|
|
|
4492
4499
|
program.version("3.4.0-alpha.1");
|
|
4493
4500
|
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
4501
|
if (actionCommand.name() === "init") return;
|
|
4495
|
-
|
|
4496
|
-
const repoRoot = program.opts().projectRoot ?? config.repoRoot;
|
|
4497
|
-
setUserConfig({
|
|
4498
|
-
...config,
|
|
4499
|
-
repoRoot,
|
|
4500
|
-
cwd: repoRoot
|
|
4501
|
-
});
|
|
4502
|
+
setUserConfig(await loadAtlasConfig(program.opts().config, program.opts().projectRoot));
|
|
4502
4503
|
});
|
|
4503
4504
|
init_default(program, program);
|
|
4504
4505
|
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
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { r as defineExpressionRule, t as UserConfig } from "./types-DcvdOZ2j.mjs";
|
|
2
|
+
//#region src/utils/defineConfig.d.ts
|
|
3
|
+
declare function defineConfig(config: UserConfig): UserConfig;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { type UserConfig, defineConfig, defineExpressionRule };
|
|
6
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/defineConfig.ts"],"sourcesContent":["import type { UserConfig } from \"../models/types\";\n\nexport function defineConfig(config: UserConfig): UserConfig {\n return config;\n}\n"],"mappings":"wDAEA,SAAgB,EAAa,EAAgC,CAC3D,OAAO,CACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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
|
+
//# sourceMappingURL=cleanObjectRule.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
|
|
2
|
+
//#region src/rules/cmsI18nFieldRule.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* `CmsI18n#get`/`getAll` reads a localized field off the translations array passed to the
|
|
5
|
+
* constructor (e.g. `new CmsI18n(offer.translations)`). Resolving to `i18n.field` breaks
|
|
6
|
+
* domain-path matching downstream because the receiver name ("i18n") is not the domain root
|
|
7
|
+
* ("offer"); resolving through the constructor argument restores that root.
|
|
8
|
+
*/
|
|
9
|
+
declare const cmsI18nFieldRule: FieldExtractionRule;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { cmsI18nFieldRule };
|
|
12
|
+
//# sourceMappingURL=cmsI18nFieldRule.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
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
|
+
//# sourceMappingURL=cmsI18nFieldRule.mjs.map
|
|
@@ -0,0 +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/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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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
|
+
//# sourceMappingURL=dateConversionRule.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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
|
+
//# sourceMappingURL=lodashGetRule.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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
|
+
//# sourceMappingURL=mappingUtilityRule.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mappingUtilityRule.mjs","names":[],"sources":["../../src/rules/mappingUtilityRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nconst mapperTypes = new Set([\"string\", \"number\", \"boolean\", \"date\", \"datetime\", \"enum\", \"array\", \"object\"]);\n\nexport const mappingUtilityRule = defineExpressionRule({\n name: \"mapping-utility-wrapper\",\n match: (expression, config) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const callee = expression.getExpression().getText();\n return config.analysis.neutralExpressionMatchers.some((matcher) => callee.startsWith(matcher.prefix));\n },\n parse: (expression, config) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const callee = expression.getExpression().getText();\n const matcher = config.analysis.neutralExpressionMatchers.find((item) => callee.startsWith(item.prefix));\n const mapperType = callee.split(\".\").find((segment) => mapperTypes.has(segment));\n\n return {\n transparent: true,\n mapperType,\n apiMapping: matcher?.apiMapping ?? false\n };\n }\n});\n"],"mappings":"yFAGA,MAAM,EAAc,IAAI,IAAI,CAAC,SAAU,SAAU,UAAW,OAAQ,WAAY,OAAQ,QAAS,QAAQ,CAAC,EAE7F,EAAqB,EAAqB,CACrD,KAAM,0BACN,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`member-get-field`,match:e=>{if(!t.isCallExpression(e))return!1;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));return t.isPropertyAccessExpression(n)&&[`get`,`getAll`].includes(n.getName())&&!!r},parse:e=>{if(!t.isCallExpression(e))return;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!(!t.isPropertyAccessExpression(n)||!t.isStringLiteral(r)&&!t.isNoSubstitutionTemplateLiteral(r)))return{backendField:`${n.getExpression().getText()}.${r.getLiteralValue()}`}}});export{n as memberGetFieldRule};
|
|
2
|
+
//# sourceMappingURL=memberGetFieldRule.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memberGetFieldRule.mjs","names":[],"sources":["../../src/rules/memberGetFieldRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const memberGetFieldRule = defineExpressionRule({\n name: \"member-get-field\",\n match: (expression) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n return Node.isPropertyAccessExpression(target) && [\"get\", \"getAll\"].includes(target.getName()) && Boolean(field);\n },\n parse: (expression) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n if (!Node.isPropertyAccessExpression(target) || (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))) {\n return undefined;\n }\n\n return { backendField: `${target.getExpression().getText()}.${field.getLiteralValue()}` };\n }\n});\n"],"mappings":"yFAGA,MAAa,EAAqB,EAAqB,CACrD,KAAM,mBACN,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
|
|
2
|
+
//#region src/rules/quableI18nFieldRule.d.ts
|
|
3
|
+
declare const quableI18nFieldRule: FieldExtractionRule;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { quableI18nFieldRule };
|
|
6
|
+
//# sourceMappingURL=quableI18nFieldRule.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
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
|
+
//# sourceMappingURL=quableI18nFieldRule.mjs.map
|
|
@@ -0,0 +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/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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Expression } from "ts-morph";
|
|
2
|
+
//#region src/utils/defineExpressionRule.d.ts
|
|
3
|
+
type FieldExtractionRuleResult = {
|
|
4
|
+
backendField?: string;
|
|
5
|
+
transparent?: boolean;
|
|
6
|
+
mapperType?: string;
|
|
7
|
+
apiMapping?: boolean;
|
|
8
|
+
};
|
|
9
|
+
type FieldExtractionRule = {
|
|
10
|
+
name: string;
|
|
11
|
+
match: (expression: Expression, config: UserConfig) => boolean;
|
|
12
|
+
parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;
|
|
13
|
+
};
|
|
14
|
+
declare function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/models/types.d.ts
|
|
17
|
+
interface NeutralExpressionMatcher {
|
|
18
|
+
prefix: string;
|
|
19
|
+
apiMapping?: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface UserConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Absolute path to the root of the repository to analyze (e.g. the digital-api repo).
|
|
24
|
+
* Can be set via the ATLAS_CWD environment variable.
|
|
25
|
+
*/
|
|
26
|
+
repoRoot?: string;
|
|
27
|
+
/** Backend identifiers used by the API project. */
|
|
28
|
+
backends: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Default OpenAPI document URL used to extract the public contract exposed by the API.
|
|
31
|
+
* The CLI can still override this value with `--openapi-url`.
|
|
32
|
+
*/
|
|
33
|
+
openapiUrl: string;
|
|
34
|
+
/** Maximum duration allowed to connect to and download the OpenAPI document. */
|
|
35
|
+
openapiTimeoutMs: number;
|
|
36
|
+
/** Default Directus base URL used by `push` when no CLI option or environment variable overrides it. */
|
|
37
|
+
directusUrl: string;
|
|
38
|
+
/**
|
|
39
|
+
* Module aliases resolved while traversing static-analysis dependencies.
|
|
40
|
+
* Defaults are read from `compilerOptions.paths` in the repository tsconfig.
|
|
41
|
+
*/
|
|
42
|
+
resolver?: {
|
|
43
|
+
/** Explicit aliases override aliases read from tsconfig. */
|
|
44
|
+
alias?: Record<string, string>;
|
|
45
|
+
};
|
|
46
|
+
/** Defaults used by the optional AI pass that reviews unresolved static mappings. */
|
|
47
|
+
inference: {
|
|
48
|
+
/** ai/sdk model used for structured mapping suggestions. */
|
|
49
|
+
model: string;
|
|
50
|
+
/** ai/sdk HTTP API base URL. */
|
|
51
|
+
baseUrl: string;
|
|
52
|
+
/** Minimum model confidence required to replace a `needs_review` mapping. */
|
|
53
|
+
minimumConfidence: number;
|
|
54
|
+
/** Maximum duration of one route-level AI request before aborting it. */
|
|
55
|
+
timeoutMs: number;
|
|
56
|
+
/** Maximum number of route-level AI requests run concurrently. */
|
|
57
|
+
workers: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Static analysis options used to keep generated artifacts focused on business-relevant files and fields.
|
|
61
|
+
*/
|
|
62
|
+
analysis: {
|
|
63
|
+
/**
|
|
64
|
+
* Glob patterns excluded from `analysis_files`.
|
|
65
|
+
* Use this to hide technical plumbing files that add noise to route review documents.
|
|
66
|
+
*/
|
|
67
|
+
excluded: string[];
|
|
68
|
+
/**
|
|
69
|
+
* Input root fields considered transverse to the API, such as access headers or generic list filters.
|
|
70
|
+
* These fields are kept in the extracted contract but are not flagged as `needs_review`
|
|
71
|
+
* when they do not map to a backend property. A detected backend mapping always takes precedence.
|
|
72
|
+
*/
|
|
73
|
+
transversalInputs: string[];
|
|
74
|
+
/** Output field paths omitted from catalogues and coverage checks, including nested fields. */
|
|
75
|
+
ignoredOutputs: string[];
|
|
76
|
+
/** Technical property-access prefixes that must not be treated as backend fields. */
|
|
77
|
+
backendFieldAccess: {
|
|
78
|
+
excludedPrefixes: string[];
|
|
79
|
+
};
|
|
80
|
+
/** Mapper naming conventions used to infer mapping direction and domain roots. */
|
|
81
|
+
mapperNaming: {
|
|
82
|
+
inputPatterns: RegExp[];
|
|
83
|
+
outputPatterns: RegExp[];
|
|
84
|
+
domainToBackendPattern: RegExp;
|
|
85
|
+
domainNameFromToDomainPattern: RegExp;
|
|
86
|
+
constructedTypeToDomainPattern: (constructorName: string) => RegExp;
|
|
87
|
+
};
|
|
88
|
+
/** Expressions that are transparent while tracing a field through a mapping. */
|
|
89
|
+
neutralExpressionMatchers: NeutralExpressionMatcher[];
|
|
90
|
+
rules: FieldExtractionRule[];
|
|
91
|
+
};
|
|
92
|
+
/** Route-level minimum resolved-field coverage accepted by the static-analysis regression check. */
|
|
93
|
+
test: {
|
|
94
|
+
coverage: Array<{
|
|
95
|
+
route: string;
|
|
96
|
+
minimum_coverage: number | string;
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { FieldExtractionRule as n, defineExpressionRule as r, UserConfig as t };
|
|
102
|
+
//# sourceMappingURL=types-DcvdOZ2j.d.mts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmflow/atlas",
|
|
3
|
-
"version": "3.4.0-beta.
|
|
3
|
+
"version": "3.4.0-beta.5",
|
|
4
4
|
"description": "API-to-backend mapping catalogue for Club Med Flow",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "romakita",
|
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "./dist/index.mjs",
|
|
11
11
|
"module": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.mts",
|
|
12
13
|
"exports": {
|
|
13
|
-
".":
|
|
14
|
-
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"import": "./dist/index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./rules/*": {
|
|
19
|
+
"types": "./dist/rules/*.d.mts",
|
|
20
|
+
"import": "./dist/rules/*.mjs"
|
|
21
|
+
}
|
|
15
22
|
},
|
|
16
23
|
"scripts": {
|
|
17
24
|
"build": "tsdown",
|
|
@@ -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"}
|