speckeeper 0.9.0 → 0.9.1
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 +22 -0
- package/dist/cli.js +32 -5
- package/dist/cli.js.map +1 -1
- package/dist/{config-api-BDl4otlv.d.ts → config-api-CfxXt9Zt.d.ts} +22 -1
- package/dist/dsl/index.d.ts +15 -3
- package/dist/dsl/index.js +13 -4
- package/dist/dsl/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -290,6 +290,28 @@ class EntityModel extends Model<typeof EntitySchema> {
|
|
|
290
290
|
|
|
291
291
|
Without `deepValidation`, speckeeper still performs existence checks for all spec IDs across all configured sources.
|
|
292
292
|
|
|
293
|
+
#### Lookup keys (when spec ID differs from external identifier)
|
|
294
|
+
|
|
295
|
+
By default, the global scanner searches for each spec's `id` in external sources. When the external identifier differs — for example, entity ID `"user"` vs DDL table name `"users"` — define `lookupKeys` on the model to map per source type:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
class EntityModel extends Model<typeof EntitySchema> {
|
|
299
|
+
readonly id = 'entity';
|
|
300
|
+
readonly name = 'Entity';
|
|
301
|
+
readonly idPrefix = 'ENT';
|
|
302
|
+
readonly schema = EntitySchema;
|
|
303
|
+
|
|
304
|
+
protected lookupKeys: LookupKeyConfig<Entity> = {
|
|
305
|
+
ddl: (spec) => spec.tableName,
|
|
306
|
+
openapi: (spec) => spec.schemaName ?? spec.id,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
With this configuration, when scanning DDL sources the scanner searches for `spec.tableName` instead of `spec.id`. If a match is found, the result is mapped back to the original spec ID for reporting and deep validation.
|
|
312
|
+
|
|
313
|
+
`lookupKeys` is optional per source type — any source type not listed falls back to `spec.id`.
|
|
314
|
+
|
|
293
315
|
#### Custom scanners
|
|
294
316
|
|
|
295
317
|
For file formats not covered by the built-in scanners, provide a custom `SourceScanner` plugin:
|
package/dist/cli.js
CHANGED
|
@@ -727,7 +727,7 @@ function loadFileContent(filePath, sourceType) {
|
|
|
727
727
|
}
|
|
728
728
|
return content;
|
|
729
729
|
}
|
|
730
|
-
function runGlobalScan(sources, specIds, basePath) {
|
|
730
|
+
function runGlobalScan(sources, specIds, basePath, lookupKeyMap) {
|
|
731
731
|
const cwd = basePath ?? process.cwd();
|
|
732
732
|
const result = /* @__PURE__ */ new Map();
|
|
733
733
|
const warnings = [];
|
|
@@ -740,6 +740,13 @@ function runGlobalScan(sources, specIds, basePath) {
|
|
|
740
740
|
});
|
|
741
741
|
continue;
|
|
742
742
|
}
|
|
743
|
+
const keyToSpecId = /* @__PURE__ */ new Map();
|
|
744
|
+
for (const specId of specIds) {
|
|
745
|
+
const overrides = lookupKeyMap?.get(specId);
|
|
746
|
+
const key = overrides?.[source.type] ?? specId;
|
|
747
|
+
keyToSpecId.set(key, specId);
|
|
748
|
+
}
|
|
749
|
+
const keysToSearch = Array.from(keyToSpecId.keys());
|
|
743
750
|
const allFiles = /* @__PURE__ */ new Set();
|
|
744
751
|
for (const pattern of source.paths) {
|
|
745
752
|
const found = glob.sync(pattern, {
|
|
@@ -768,7 +775,7 @@ function runGlobalScan(sources, specIds, basePath) {
|
|
|
768
775
|
if (content == null) continue;
|
|
769
776
|
let matches;
|
|
770
777
|
try {
|
|
771
|
-
matches = scanner.findSpecIds(content,
|
|
778
|
+
matches = scanner.findSpecIds(content, keysToSearch, relPath);
|
|
772
779
|
} catch (e) {
|
|
773
780
|
const msg = e instanceof Error ? e.message : String(e);
|
|
774
781
|
warnings.push({
|
|
@@ -779,17 +786,19 @@ function runGlobalScan(sources, specIds, basePath) {
|
|
|
779
786
|
continue;
|
|
780
787
|
}
|
|
781
788
|
for (const match of matches) {
|
|
789
|
+
const originalSpecId = keyToSpecId.get(match.specId) ?? match.specId;
|
|
782
790
|
const globalMatch = {
|
|
783
791
|
...match,
|
|
792
|
+
specId: originalSpecId,
|
|
784
793
|
sourceType: source.type,
|
|
785
794
|
relation: source.relation,
|
|
786
795
|
filePath: relPath
|
|
787
796
|
};
|
|
788
|
-
const existing = result.get(
|
|
797
|
+
const existing = result.get(originalSpecId);
|
|
789
798
|
if (existing) {
|
|
790
799
|
existing.push(globalMatch);
|
|
791
800
|
} else {
|
|
792
|
-
result.set(
|
|
801
|
+
result.set(originalSpecId, [globalMatch]);
|
|
793
802
|
}
|
|
794
803
|
}
|
|
795
804
|
}
|
|
@@ -932,17 +941,35 @@ async function checkCommand(type, options) {
|
|
|
932
941
|
const results = [];
|
|
933
942
|
const allSpecIds = [];
|
|
934
943
|
const specIdToModel = /* @__PURE__ */ new Map();
|
|
944
|
+
const lookupKeyMap = /* @__PURE__ */ new Map();
|
|
935
945
|
for (const model of models) {
|
|
936
946
|
const modelSpecs = getSpecsFromConfig(specs, model.id);
|
|
947
|
+
const modelLookupKeys = model.getLookupKeys?.();
|
|
937
948
|
for (const spec of modelSpecs) {
|
|
938
949
|
const specId = spec.id;
|
|
939
950
|
allSpecIds.push(specId);
|
|
940
951
|
specIdToModel.set(specId, { modelId: model.id, spec });
|
|
952
|
+
if (modelLookupKeys) {
|
|
953
|
+
const overrides = {};
|
|
954
|
+
for (const [sourceType, mapper] of Object.entries(modelLookupKeys)) {
|
|
955
|
+
if (typeof mapper === "function") {
|
|
956
|
+
overrides[sourceType] = mapper(spec);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (Object.keys(overrides).length > 0) {
|
|
960
|
+
lookupKeyMap.set(specId, overrides);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
941
963
|
}
|
|
942
964
|
}
|
|
943
965
|
if (sources.length > 0 && allSpecIds.length > 0) {
|
|
944
966
|
const filteredSources = checkType === "all" ? sources : sources.filter((s) => s.type === checkType);
|
|
945
|
-
const { matches, warnings: scanWarnings } = runGlobalScan(
|
|
967
|
+
const { matches, warnings: scanWarnings } = runGlobalScan(
|
|
968
|
+
filteredSources,
|
|
969
|
+
allSpecIds,
|
|
970
|
+
cwd,
|
|
971
|
+
lookupKeyMap.size > 0 ? lookupKeyMap : void 0
|
|
972
|
+
);
|
|
946
973
|
for (const sw of scanWarnings) {
|
|
947
974
|
results.push({
|
|
948
975
|
type: sw.sourceType,
|