speckeeper 0.7.2 → 0.8.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.
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { d as LintRule, E as Exporter, c as ExternalChecker, a as CoverageChecker } from '../model-BPZMYasZ.js';
2
+ import { q as LintRule, m as Exporter, n as ExternalChecker, k as CoverageChecker, A as ArtifactConfig } from '../config-api-U2pt1aHJ.js';
3
3
 
4
4
  /**
5
5
  * Core DSL — Base spec schema
@@ -125,6 +125,37 @@ interface TestCheckerConfig<T> {
125
125
  declare function testChecker<T extends {
126
126
  id: string;
127
127
  }>(config?: TestCheckerConfig<T>): ExternalChecker<T>;
128
+ type AnnotationRelationType = 'verifiedBy' | 'implements' | 'traces';
129
+ interface AnnotationCheckEntry {
130
+ artifact: string;
131
+ relationType: AnnotationRelationType;
132
+ contentPatterns?: RegExp[];
133
+ checker?: ExternalChecker<{
134
+ id: string;
135
+ }>;
136
+ }
137
+ interface AnnotationCheckerConfig<_T extends {
138
+ id: string;
139
+ }> {
140
+ artifact?: string;
141
+ relationType?: AnnotationRelationType;
142
+ checks?: AnnotationCheckEntry[];
143
+ contentPatterns?: RegExp[];
144
+ }
145
+ declare function setArtifactsConfig(config: Record<string, ArtifactConfig>): void;
146
+ declare function getArtifactsConfig(): Record<string, ArtifactConfig> | undefined;
147
+ declare function annotationChecker<T extends {
148
+ id: string;
149
+ }>(config?: AnnotationCheckerConfig<T>): ExternalChecker<T>;
150
+ interface AnnotationCoverageConfig {
151
+ artifact: string;
152
+ relationType: AnnotationRelationType;
153
+ description: string;
154
+ contentPatterns?: RegExp[];
155
+ }
156
+ declare function annotationCoverage<T extends {
157
+ id: string;
158
+ }>(config: AnnotationCoverageConfig): CoverageChecker<T>;
128
159
  interface OpenAPICheckerConfig<T> {
129
160
  sourcePath?: (spec: T) => string;
130
161
  mapper: (spec: T) => {
@@ -250,4 +281,4 @@ declare const VerifiedByRelationSchema: z.ZodObject<{
250
281
  }>;
251
282
  type VerifiedByRelation = z.infer<typeof VerifiedByRelationSchema>;
252
283
 
253
- export { type BaseSpec, type ImplementsRelation, ImplementsRelationSchema, type MarkdownExporterConfig, type VerifiedByRelation, VerifiedByRelationSchema, arrayMinLength, baseSpecSchema, childIdFormat, externalOpenAPIChecker, externalSqlSchemaChecker, idFormat, isTypeContainedBy, markdownExporter, relationCoverage, requireField, testChecker };
284
+ export { type AnnotationCheckEntry, type AnnotationCheckerConfig, type AnnotationCoverageConfig, type BaseSpec, type ImplementsRelation, ImplementsRelationSchema, type MarkdownExporterConfig, type VerifiedByRelation, VerifiedByRelationSchema, annotationChecker, annotationCoverage, arrayMinLength, baseSpecSchema, childIdFormat, externalOpenAPIChecker, externalSqlSchemaChecker, getArtifactsConfig, idFormat, isTypeContainedBy, markdownExporter, relationCoverage, requireField, setArtifactsConfig, testChecker };
package/dist/dsl/index.js CHANGED
@@ -290,6 +290,195 @@ function testChecker(config) {
290
290
  }
291
291
  };
292
292
  }
293
+ function relationTypeToAnnotation(relationType) {
294
+ return relationType === "verifiedBy" ? "verifies" : relationType;
295
+ }
296
+ function getDefaultPatternForRelation(relationType) {
297
+ const ann = relationTypeToAnnotation(relationType);
298
+ return new RegExp(`@${ann}\\s+([\\w-]+(?:[,\\s]+[\\w-]+)*)`, "g");
299
+ }
300
+ function extractSpecIds(captured) {
301
+ return captured.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
302
+ }
303
+ var _artifactsConfig;
304
+ function setArtifactsConfig(config) {
305
+ _artifactsConfig = config;
306
+ }
307
+ function getArtifactsConfig() {
308
+ return _artifactsConfig;
309
+ }
310
+ function runAnnotationScan(specId, artifact, relationType, contentPatterns, basePath) {
311
+ const matchedFiles = [];
312
+ const warnings = [];
313
+ const artifactsConfig = getArtifactsConfig();
314
+ const artifactConfig = artifactsConfig?.[artifact];
315
+ if (!artifactConfig) {
316
+ warnings.push({
317
+ message: `Artifact config "${artifact}" not found; set artifacts in config and call setArtifactsConfig()`,
318
+ specId
319
+ });
320
+ return { matchedFiles, warnings };
321
+ }
322
+ const allFiles = /* @__PURE__ */ new Set();
323
+ for (const pattern of artifactConfig.globs) {
324
+ const found = glob.sync(pattern, {
325
+ cwd: basePath,
326
+ ignore: artifactConfig.exclude ?? []
327
+ });
328
+ for (const f of found) {
329
+ allFiles.add(f);
330
+ }
331
+ }
332
+ const patterns = contentPatterns.length > 0 ? contentPatterns : [getDefaultPatternForRelation(relationType)];
333
+ for (const filePath of allFiles) {
334
+ const fullPath = join(basePath, filePath);
335
+ let content;
336
+ try {
337
+ content = readFileSync(fullPath, "utf-8");
338
+ } catch {
339
+ continue;
340
+ }
341
+ const lines = content.split("\n");
342
+ for (let lineNum = 1; lineNum <= lines.length; lineNum++) {
343
+ const line = lines[lineNum - 1];
344
+ for (const re of patterns) {
345
+ const copy = new RegExp(re.source, re.flags);
346
+ let m;
347
+ while ((m = copy.exec(line)) !== null) {
348
+ const ids = extractSpecIds(m[1] ?? "");
349
+ if (ids.includes(specId)) {
350
+ matchedFiles.push({ specId, filePath, line: lineNum, relationType });
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+ return { matchedFiles, warnings };
357
+ }
358
+ function annotationChecker(config) {
359
+ return {
360
+ targetType: "annotation",
361
+ sourcePath: () => ".",
362
+ check: (spec, _externalData) => {
363
+ const errors = [];
364
+ const warnings = [];
365
+ const allMatchedFiles = [];
366
+ const basePath = process.cwd();
367
+ const checks = config?.artifact && config?.relationType ? [
368
+ {
369
+ artifact: config.artifact,
370
+ relationType: config.relationType,
371
+ contentPatterns: config.contentPatterns
372
+ }
373
+ ] : config?.checks ?? [];
374
+ for (const entry of checks) {
375
+ if (entry.checker) {
376
+ const result = entry.checker.check(spec, void 0);
377
+ errors.push(...result.errors);
378
+ warnings.push(...result.warnings);
379
+ if (result.matchedFiles) {
380
+ allMatchedFiles.push(...result.matchedFiles);
381
+ }
382
+ } else {
383
+ const patterns = entry.contentPatterns ?? getArtifactsConfig()?.[entry.artifact]?.contentPatterns ?? [];
384
+ const { matchedFiles, warnings: w } = runAnnotationScan(
385
+ spec.id,
386
+ entry.artifact,
387
+ entry.relationType,
388
+ Array.isArray(patterns) ? patterns : [patterns],
389
+ basePath
390
+ );
391
+ warnings.push(...w);
392
+ allMatchedFiles.push(...matchedFiles);
393
+ }
394
+ }
395
+ if (checks.length > 0 && allMatchedFiles.length === 0) {
396
+ warnings.push({
397
+ message: `No annotation matches found for spec "${spec.id}"`,
398
+ specId: spec.id
399
+ });
400
+ }
401
+ return {
402
+ success: errors.length === 0,
403
+ errors,
404
+ warnings,
405
+ matchedFiles: allMatchedFiles.length > 0 ? allMatchedFiles : void 0
406
+ };
407
+ }
408
+ };
409
+ }
410
+ function annotationCoverage(config) {
411
+ return {
412
+ targetModel: "annotation",
413
+ description: config.description,
414
+ check: (specs, _registry) => {
415
+ const basePath = process.cwd();
416
+ const allSpecIds = new Set(specs.map((s) => s.id));
417
+ const coveredIds = /* @__PURE__ */ new Set();
418
+ const artifactsConfig = getArtifactsConfig();
419
+ const artifactConfig = artifactsConfig?.[config.artifact];
420
+ if (!artifactConfig) {
421
+ return {
422
+ total: allSpecIds.size,
423
+ covered: 0,
424
+ uncovered: allSpecIds.size,
425
+ coveragePercent: 0,
426
+ coveredItems: [],
427
+ uncoveredItems: Array.from(allSpecIds).map((id) => ({ id }))
428
+ };
429
+ }
430
+ const allFiles = /* @__PURE__ */ new Set();
431
+ for (const pattern of artifactConfig.globs) {
432
+ const found = glob.sync(pattern, {
433
+ cwd: basePath,
434
+ ignore: artifactConfig.exclude ?? []
435
+ });
436
+ for (const f of found) {
437
+ allFiles.add(f);
438
+ }
439
+ }
440
+ const patterns = config.contentPatterns ?? artifactConfig.contentPatterns ?? [getDefaultPatternForRelation(config.relationType)];
441
+ const patternList = Array.isArray(patterns) ? patterns : [patterns];
442
+ for (const filePath of allFiles) {
443
+ const fullPath = join(basePath, filePath);
444
+ let content;
445
+ try {
446
+ content = readFileSync(fullPath, "utf-8");
447
+ } catch {
448
+ continue;
449
+ }
450
+ const lines = content.split("\n");
451
+ for (const line of lines) {
452
+ for (const re of patternList) {
453
+ const copy = new RegExp(re.source, re.flags);
454
+ let m;
455
+ while ((m = copy.exec(line)) !== null) {
456
+ const ids = extractSpecIds(m[1] ?? "");
457
+ for (const id of ids) {
458
+ if (allSpecIds.has(id)) {
459
+ coveredIds.add(id);
460
+ }
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+ const coveredItems = Array.from(coveredIds).map((id) => ({ id }));
467
+ const uncoveredItems = Array.from(allSpecIds).filter((id) => !coveredIds.has(id)).map((id) => ({ id }));
468
+ const total = allSpecIds.size;
469
+ const covered = coveredItems.length;
470
+ const coveragePercent = total > 0 ? Math.round(covered / total * 100) : 100;
471
+ return {
472
+ total,
473
+ covered,
474
+ uncovered: total - covered,
475
+ coveragePercent,
476
+ coveredItems,
477
+ uncoveredItems
478
+ };
479
+ }
480
+ };
481
+ }
293
482
  function resolveRef(doc, ref) {
294
483
  if (!ref.startsWith("#/")) return void 0;
295
484
  const parts = ref.substring(2).split("/");
@@ -451,6 +640,10 @@ function externalOpenAPIChecker(config) {
451
640
  }
452
641
  };
453
642
  }
643
+ function stripSchemaPrefix(name) {
644
+ const dotIndex = name.lastIndexOf(".");
645
+ return dotIndex >= 0 ? name.substring(dotIndex + 1) : name;
646
+ }
454
647
  function parseDDLWithParser(content) {
455
648
  const parser = new nodeSqlParser.Parser();
456
649
  const tables = [];
@@ -459,7 +652,8 @@ function parseDDLWithParser(content) {
459
652
  const statements = Array.isArray(ast) ? ast : [ast];
460
653
  for (const stmt of statements) {
461
654
  if (stmt.type !== "create" || stmt.keyword !== "table") continue;
462
- const tableName = typeof stmt.table === "string" ? stmt.table : stmt.table?.[0]?.table ?? "";
655
+ const rawName = typeof stmt.table === "string" ? stmt.table : stmt.table?.[0]?.table ?? "";
656
+ const tableName = stripSchemaPrefix(rawName);
463
657
  const columns = (stmt.create_definitions || []).filter((def) => def.resource === "column").map((def) => {
464
658
  const colName = def.column?.column ?? "";
465
659
  let colType = def.definition?.dataType ?? "";
@@ -482,7 +676,7 @@ function parseDDLWithParser(content) {
482
676
  }
483
677
  function parseDDLWithRegex(content) {
484
678
  const tables = [];
485
- const tableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`|"|')?(\w+)(?:`|"|')?\s*\(([\s\S]*?)\);/gi;
679
+ const tableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(?:[`"']?\w+[`"']?\s*\.\s*)?)[`"']?(\w+)[`"']?\s*\(([\s\S]*?)\);/gi;
486
680
  let match;
487
681
  while ((match = tableRegex.exec(content)) !== null) {
488
682
  const tableName = match[1];
@@ -628,6 +822,6 @@ var VerifiedByRelationSchema = z.object({
628
822
  description: z.string().optional()
629
823
  });
630
824
 
631
- export { ImplementsRelationSchema, VerifiedByRelationSchema, arrayMinLength, baseSpecSchema, childIdFormat, externalOpenAPIChecker, externalSqlSchemaChecker, idFormat, isTypeContainedBy, markdownExporter, relationCoverage, requireField, testChecker };
825
+ export { ImplementsRelationSchema, VerifiedByRelationSchema, annotationChecker, annotationCoverage, arrayMinLength, baseSpecSchema, childIdFormat, externalOpenAPIChecker, externalSqlSchemaChecker, getArtifactsConfig, idFormat, isTypeContainedBy, markdownExporter, relationCoverage, requireField, setArtifactsConfig, testChecker };
632
826
  //# sourceMappingURL=index.js.map
633
827
  //# sourceMappingURL=index.js.map