-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidate-catalog.ts
More file actions
87 lines (77 loc) · 2.69 KB
/
Copy pathvalidate-catalog.ts
File metadata and controls
87 lines (77 loc) · 2.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
* Validate catalog YAML with @backstage/catalog-model.
* By default skips *.local.yaml (generated bulk). Pass --include-generated to validate those too.
*
* Expands `${MARVEL_*}` placeholders (see catalog-env-substitute.ts) after loading optional repo `.env`.
* Backstage itself does not expand these at ingest time.
*
* @backstage/catalog-model is loaded via createRequire so named exports resolve reliably under
* this package's NodeNext + tsx runtime (avoids CJS/ESM interop issues with the package's dual build).
*/
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { createRequire } from 'node:module';
import { join, relative } from 'node:path';
import type { Entity } from '@backstage/catalog-model';
import yaml from 'js-yaml';
import { applyCatalogEnvSubstitutions } from './catalog-env-substitute.js';
import { loadEnvFileIfPresent, repoRoot } from './repo-env.js';
const require = createRequire(import.meta.url);
const { entitySchemaValidator } = require('@backstage/catalog-model') as {
entitySchemaValidator: () => (data: unknown) => Entity;
};
loadEnvFileIfPresent(join(repoRoot, '.env'));
const catalogRoot = join(repoRoot, 'catalog-entities');
const includeGenerated = process.argv.includes('--include-generated');
const validate = entitySchemaValidator();
function walkYamlFiles(dir: string, out: string[] = []): string[] {
for (const name of readdirSync(dir)) {
const full = join(dir, name);
if (!includeGenerated && name.endsWith('.local.yaml')) {
continue;
}
const st = statSync(full);
if (st.isDirectory()) {
walkYamlFiles(full, out);
} else if (name.endsWith('.yaml') || name.endsWith('.yml')) {
out.push(full);
}
}
return out;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
let errors = 0;
const files = walkYamlFiles(catalogRoot);
for (const file of files) {
const rel = relative(repoRoot, file);
let content: string;
try {
content = readFileSync(file, 'utf8');
} catch (e) {
console.error(rel, e);
errors++;
continue;
}
content = applyCatalogEnvSubstitutions(content);
const docs = yaml.loadAll(content) as unknown[];
let i = 0;
for (const doc of docs) {
i++;
if (!isRecord(doc)) {
continue;
}
try {
validate(doc);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error(`${rel} (document #${i}):`, msg);
errors++;
}
}
}
if (errors > 0) {
console.error(`\nCatalog validation failed: ${errors} error(s).`);
process.exit(1);
}
console.log(`Validated ${files.length} YAML file(s) under catalog-entities/.`);