forked from finos/architecture-as-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidate.ts
More file actions
159 lines (142 loc) · 6.25 KB
/
validate.ts
File metadata and controls
159 lines (142 loc) · 6.25 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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { getFormattedOutput, validate, exitBasedOffOfValidationOutcome, ValidationFormattingOptions, loadArchitectureAndPattern, loadTimeline, enrichWithDocumentPositions, ParsedDocumentContext } from '@finos/calm-shared';
import { initLogger } from '@finos/calm-shared';
import path from 'path';
import { mkdirp } from 'mkdirp';
import { readFileSync, writeFileSync } from 'fs';
import { Command } from 'commander';
import { ValidateOutputFormat } from '@finos/calm-shared/dist/commands/validate/validate';
import { buildSchemaDirectory, parseDocumentLoaderConfig } from '../cli';
import { buildDocumentLoader, DocumentLoader } from '@finos/calm-shared/dist/document-loader/document-loader';
import { Logger } from '@finos/calm-shared/dist/logger';
import { parseWithPointers } from '@stoplight/json';
export interface ValidateOptions {
patternPath?: string;
architecturePath?: string;
timelinePath?: string;
metaSchemaPath: string;
calmHubUrl?: string;
urlToLocalFileMapping?: string;
verbose: boolean;
strict: boolean;
outputFormat: ValidateOutputFormat;
outputPath: string;
}
export async function runValidate(options: ValidateOptions) {
const logger = initLogger(options.verbose, 'calm-validate');
try {
const { getUrlToLocalFileMap } = await import('./template');
const urlToLocalMap = getUrlToLocalFileMap(options.urlToLocalFileMapping);
const patternBasePath = options.patternPath ? path.dirname(path.resolve(options.patternPath)) : undefined;
const docLoaderOpts = await parseDocumentLoaderConfig(options, urlToLocalMap, patternBasePath);
const docLoader: DocumentLoader = buildDocumentLoader(docLoaderOpts);
const schemaDirectory = await buildSchemaDirectory(docLoader, options.verbose);
await schemaDirectory.loadSchemas();
let architecture: object | undefined = undefined;
let pattern: object | undefined = undefined;
let timeline: object | undefined = undefined;
if (options.timelinePath) {
const result = await loadTimeline(
options.timelinePath,
docLoader,
schemaDirectory,
logger
);
timeline = result.timeline;
pattern = result.pattern;
}
else {
const result = await loadArchitectureAndPattern(
options.architecturePath ?? '',
options.patternPath ?? '',
docLoader,
schemaDirectory,
logger
);
architecture = result.architecture;
pattern = result.pattern;
}
const documentContexts = buildDocumentContexts(options, logger);
if (!architecture && !pattern && !timeline) {
throw new Error('You must provide an architecture, a pattern, or a timeline');
}
const outcome = await validate(architecture, pattern, timeline, schemaDirectory, options.verbose);
enrichWithDocumentPositions(outcome, documentContexts);
const content = getFormattedOutput(outcome, options.outputFormat, toFormattingOptions(documentContexts));
writeOutputFile(options.outputPath, content);
exitBasedOffOfValidationOutcome(outcome, options.strict);
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
const stack = err instanceof Error ? err.stack : undefined;
logger.error('An error occurred while validating: ' + message);
if (stack) logger.debug(stack);
process.exit(1);
}
}
export function writeOutputFile(output: string, validationsOutput: string) {
if (output) {
const dirname = path.dirname(output);
mkdirp.sync(dirname);
writeFileSync(output, validationsOutput);
} else {
process.stdout.write(validationsOutput);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function checkValidateOptions(program: Command, options: any, patternOption: string, architectureOption: string, timelineOption: string) {
if (options.timeline && (options.pattern || options.architecture)) {
program.error(`error: the option '${timelineOption}' cannot be used with either of the options '${patternOption}' or '${architectureOption}'`);
}
if (!options.pattern && !options.architecture && !options.timeline) {
program.error(`error: one of the required options '${patternOption}', '${architectureOption}' or '${timelineOption}' was not specified`);
}
}
interface LoadedDocumentContext extends ParsedDocumentContext {
filePath: string;
lines: string[];
}
function buildDocumentContexts(options: ValidateOptions, logger: Logger): Record<string, LoadedDocumentContext> {
const contexts: Record<string, LoadedDocumentContext> = {};
if (options.architecturePath) {
const context = loadDocumentContext(options.architecturePath, 'architecture', logger);
if (context) {
contexts['architecture'] = context;
}
}
if (options.patternPath) {
const context = loadDocumentContext(options.patternPath, 'pattern', logger);
if (context) {
contexts['pattern'] = context;
}
}
return contexts;
}
function loadDocumentContext(filePath: string, id: string, logger: Logger): LoadedDocumentContext | undefined {
try {
const absolutePath = path.resolve(filePath);
const raw = readFileSync(absolutePath, 'utf-8');
const parsed = parseWithPointers(raw);
return {
id,
filePath: absolutePath,
lines: raw.split(/\r?\n/),
data: parsed.data,
parseResult: parsed
};
} catch (error) {
logger.debug(`Could not build document context for ${filePath}: ${error}`);
return undefined;
}
}
function toFormattingOptions(contexts: Record<string, LoadedDocumentContext>): ValidationFormattingOptions {
const documents: Record<string, { id: string; label: string; filePath: string; lines: string[] }> = {};
Object.entries(contexts).forEach(([id, ctx]) => {
documents[id] = {
id: ctx.id,
label: path.basename(ctx.filePath),
filePath: ctx.filePath,
lines: ctx.lines
};
});
return { documents };
}