@lightdash/cli 0.2191.2 → 0.2193.0

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/dist/ajv.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ajv.d.ts","sourceRoot":"","sources":["../src/ajv.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,GAAG,KAAiC,CAAC"}
1
+ {"version":3,"file":"ajv.d.ts","sourceRoot":"","sources":["../src/ajv.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAGtB,eAAO,MAAM,GAAG,KAGd,CAAC"}
package/dist/ajv.js CHANGED
@@ -3,4 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ajv = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const ajv_1 = tslib_1.__importDefault(require("ajv"));
6
- exports.ajv = new ajv_1.default({ coerceTypes: true });
6
+ const ajv_formats_1 = tslib_1.__importDefault(require("ajv-formats"));
7
+ exports.ajv = new ajv_1.default({
8
+ coerceTypes: true,
9
+ allErrors: true, // Report all errors, not just the first one
10
+ });
11
+ (0, ajv_formats_1.default)(exports.ajv);
@@ -0,0 +1,66 @@
1
+ import type { ErrorObject } from 'ajv';
2
+ type LocationMap = Map<string, {
3
+ line: number;
4
+ column: number;
5
+ }>;
6
+ type FileValidationResult = {
7
+ filePath: string;
8
+ errors: ErrorObject[];
9
+ fileContent: string;
10
+ locationMap?: LocationMap;
11
+ schemaType: 'chart' | 'dashboard';
12
+ };
13
+ export type SarifLog = {
14
+ version: '2.1.0';
15
+ $schema: string;
16
+ runs: SarifRun[];
17
+ };
18
+ type SarifRun = {
19
+ tool: {
20
+ driver: {
21
+ name: string;
22
+ version: string;
23
+ informationUri?: string;
24
+ rules?: SarifRule[];
25
+ };
26
+ };
27
+ results: SarifResult[];
28
+ };
29
+ type SarifRule = {
30
+ id: string;
31
+ shortDescription: {
32
+ text: string;
33
+ };
34
+ fullDescription?: {
35
+ text: string;
36
+ };
37
+ help?: {
38
+ text: string;
39
+ };
40
+ };
41
+ export type SarifResult = {
42
+ ruleId: string;
43
+ level: 'error' | 'warning' | 'note';
44
+ message: {
45
+ text: string;
46
+ };
47
+ locations: SarifLocation[];
48
+ properties?: Record<string, unknown>;
49
+ };
50
+ type SarifLocation = {
51
+ physicalLocation: {
52
+ artifactLocation: {
53
+ uri: string;
54
+ };
55
+ region: {
56
+ startLine: number;
57
+ startColumn: number;
58
+ };
59
+ };
60
+ };
61
+ /**
62
+ * Convert validation results to SARIF format
63
+ */
64
+ export declare function createSarifReport(results: FileValidationResult[]): SarifLog;
65
+ export {};
66
+ //# sourceMappingURL=ajvToSarif.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ajvToSarif.d.ts","sourceRoot":"","sources":["../../../src/handlers/lint/ajvToSarif.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,KAAK,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjE,KAAK,oBAAoB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,EAAE,OAAO,GAAG,WAAW,CAAC;CACrC,CAAC;AAGF,MAAM,MAAM,QAAQ,GAAG;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;CACpB,CAAC;AAEF,KAAK,QAAQ,GAAG;IACZ,IAAI,EAAE;QACF,MAAM,EAAE;YACJ,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,CAAC;YAChB,cAAc,CAAC,EAAE,MAAM,CAAC;YACxB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;SACvB,CAAC;KACL,CAAC;IACF,OAAO,EAAE,WAAW,EAAE,CAAC;CAC1B,CAAC;AAEF,KAAK,SAAS,GAAG;IACb,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,eAAe,CAAC,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,IAAI,CAAC,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACpC,OAAO,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC,CAAC;AAEF,KAAK,aAAa,GAAG;IACjB,gBAAgB,EAAE;QACd,gBAAgB,EAAE;YACd,GAAG,EAAE,MAAM,CAAC;SACf,CAAC;QACF,MAAM,EAAE;YACJ,SAAS,EAAE,MAAM,CAAC;YAClB,WAAW,EAAE,MAAM,CAAC;SACvB,CAAC;KACL,CAAC;CACL,CAAC;AAmJF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,QAAQ,CAwG3E"}
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSarifReport = createSarifReport;
4
+ /**
5
+ * Find the line and column number in YAML content for a given data path using regex-based search.
6
+ *
7
+ * This is a FALLBACK strategy used when the locationMap doesn't have an entry for the error path.
8
+ * Primary use case: root-level missing required properties (dataPath='/' which isn't in locationMap).
9
+ *
10
+ * The function uses regex patterns to search through the YAML content and locate:
11
+ * - Array items by counting '-' markers
12
+ * - Nested properties within array items
13
+ * - Simple top-level keys
14
+ *
15
+ * Returns line 1, column 1 if no match is found (used for root-level errors).
16
+ */
17
+ function findLocationForPath(yamlContent, dataPath) {
18
+ const lines = yamlContent.split('\n');
19
+ // Remove leading slash and split path
20
+ const pathParts = dataPath
21
+ .replace(/^\//, '')
22
+ .split('/')
23
+ .filter((p) => p !== '');
24
+ if (pathParts.length === 0) {
25
+ // Error at root level
26
+ return { line: 1, column: 1 };
27
+ }
28
+ // Check if we have an array index in the path (e.g., /dimensions/1/type)
29
+ let arrayParentKey = null;
30
+ let arrayIndex = null;
31
+ let propertyInArray = null;
32
+ // Look for pattern: parent/index/property
33
+ if (pathParts.length >= 3) {
34
+ const secondToLast = pathParts[pathParts.length - 2];
35
+ if (/^\d+$/.test(secondToLast)) {
36
+ // We have an array index
37
+ arrayParentKey = pathParts[pathParts.length - 3];
38
+ arrayIndex = parseInt(secondToLast, 10);
39
+ propertyInArray = pathParts[pathParts.length - 1];
40
+ }
41
+ }
42
+ else if (pathParts.length === 2) {
43
+ const [firstPart, lastPart] = pathParts;
44
+ if (/^\d+$/.test(lastPart)) {
45
+ // Path is like /dimensions/1
46
+ arrayParentKey = firstPart;
47
+ arrayIndex = parseInt(lastPart, 10);
48
+ }
49
+ }
50
+ // For array items with nested property errors (e.g., /dimensions/1/type)
51
+ if (arrayParentKey && arrayIndex !== null && propertyInArray) {
52
+ const parentPattern = new RegExp(`^\\s*${arrayParentKey}\\s*:`);
53
+ let foundParent = false;
54
+ let arrayItemCount = 0;
55
+ let arrayItemStartLine = -1;
56
+ for (let i = 0; i < lines.length; i += 1) {
57
+ if (!foundParent && parentPattern.test(lines[i])) {
58
+ foundParent = true;
59
+ }
60
+ else if (foundParent && /^\s*-\s/.test(lines[i])) {
61
+ if (arrayItemCount === arrayIndex) {
62
+ arrayItemStartLine = i;
63
+ break;
64
+ }
65
+ arrayItemCount += 1;
66
+ }
67
+ }
68
+ // Now find the nested property within this array item
69
+ if (arrayItemStartLine >= 0) {
70
+ // Check if property is on the same line as the array marker (e.g., "- id: value")
71
+ const sameLinePattern = new RegExp(`^\\s*-\\s+${propertyInArray}\\s*:`);
72
+ if (sameLinePattern.test(lines[arrayItemStartLine])) {
73
+ const match = lines[arrayItemStartLine].match(/^(\s*-\s+)/);
74
+ const column = match ? match[1].length + 1 : 1;
75
+ return { line: arrayItemStartLine + 1, column };
76
+ }
77
+ // Otherwise, look for the property on subsequent lines
78
+ const propPattern = new RegExp(`^\\s*${propertyInArray}\\s*:`);
79
+ for (let i = arrayItemStartLine + 1; i < Math.min(lines.length, arrayItemStartLine + 20); i += 1) {
80
+ if (propPattern.test(lines[i])) {
81
+ const match = lines[i].match(/^(\s*)/);
82
+ const column = match ? match[1].length + 1 : 1;
83
+ return { line: i + 1, column };
84
+ }
85
+ if (/^\s*-\s/.test(lines[i])) {
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ // Simple search for the last key in the path
92
+ const lastPart = pathParts[pathParts.length - 1];
93
+ if (!/^\d+$/.test(lastPart)) {
94
+ const keyPattern = new RegExp(`^\\s*${lastPart}\\s*:`);
95
+ for (let i = 0; i < lines.length; i += 1) {
96
+ if (keyPattern.test(lines[i])) {
97
+ const match = lines[i].match(/^(\s*)/);
98
+ const column = match ? match[1].length + 1 : 1;
99
+ return { line: i + 1, column };
100
+ }
101
+ }
102
+ }
103
+ return { line: 1, column: 1 };
104
+ }
105
+ /**
106
+ * Get a friendly error message for an AJV error
107
+ */
108
+ function getFriendlyMessage(error) {
109
+ if (error.keyword === 'required' && error.params.missingProperty) {
110
+ return `Missing required property '${error.params.missingProperty}'`;
111
+ }
112
+ if (error.keyword === 'additionalProperties' &&
113
+ error.params.additionalProperty) {
114
+ return `Property '${error.params.additionalProperty}' is not allowed`;
115
+ }
116
+ if (error.keyword === 'type') {
117
+ return `Expected type '${error.params.type}'`;
118
+ }
119
+ if (error.keyword === 'enum' && error.params.allowedValues) {
120
+ return `Value must be one of: ${error.params.allowedValues.join(', ')}`;
121
+ }
122
+ if (error.keyword === 'const') {
123
+ return `Value must be '${error.params.allowedValue}'`;
124
+ }
125
+ return error.message || 'Validation error';
126
+ }
127
+ /**
128
+ * Convert validation results to SARIF format
129
+ */
130
+ function createSarifReport(results) {
131
+ const sarifResults = [];
132
+ const rules = new Map();
133
+ for (const result of results) {
134
+ for (const error of result.errors) {
135
+ // For additionalProperties errors, append the property name to the path
136
+ // Handle root-level errors carefully to avoid double slashes (//propertyName)
137
+ let dataPath = error.instancePath || '/';
138
+ if (error.keyword === 'additionalProperties' &&
139
+ error.params.additionalProperty) {
140
+ dataPath =
141
+ dataPath === '/'
142
+ ? `/${error.params.additionalProperty}`
143
+ : `${dataPath}/${error.params.additionalProperty}`;
144
+ }
145
+ // Determine error location using a two-strategy approach:
146
+ // 1. PRIMARY: Use locationMap (built from YAML AST during parsing)
147
+ // - Fast O(1) lookup for ~95% of cases
148
+ // - Works for: additional properties, type errors, enum errors, nested errors, array items
149
+ // - Works for: nested missing required fields (e.g., missing 'exploreName' in 'metricQuery')
150
+ //
151
+ // 2. FALLBACK: Use regex-based search when locationMap lookup fails
152
+ // - Needed for: root-level missing required properties (e.g., missing 'name', 'version')
153
+ // These have dataPath='/' which doesn't exist in locationMap since it stores actual YAML keys
154
+ // - Also provides defensive error handling if AST traversal misses any edge cases
155
+ let location = null;
156
+ if (result.locationMap) {
157
+ location = result.locationMap.get(dataPath) || null;
158
+ }
159
+ if (!location) {
160
+ // Fallback to regex search - primarily for root-level missing required properties
161
+ location = findLocationForPath(result.fileContent, dataPath);
162
+ }
163
+ const message = getFriendlyMessage(error);
164
+ const ruleId = `${result.schemaType}/${error.keyword}`;
165
+ // Add rule if we haven't seen it before
166
+ if (!rules.has(ruleId)) {
167
+ rules.set(ruleId, {
168
+ id: ruleId,
169
+ shortDescription: {
170
+ text: `${error.keyword} validation error`,
171
+ },
172
+ });
173
+ }
174
+ const sarifResult = {
175
+ ruleId,
176
+ level: 'error',
177
+ message: {
178
+ text: message,
179
+ },
180
+ locations: [
181
+ {
182
+ physicalLocation: {
183
+ artifactLocation: {
184
+ uri: result.filePath,
185
+ },
186
+ region: {
187
+ startLine: location?.line || 1,
188
+ startColumn: location?.column || 1,
189
+ },
190
+ },
191
+ },
192
+ ],
193
+ };
194
+ // Add additional context
195
+ if (error.params) {
196
+ const properties = {};
197
+ Object.entries(error.params).forEach(([key, value]) => {
198
+ properties[key] = value;
199
+ });
200
+ sarifResult.properties = { errorParams: properties };
201
+ }
202
+ sarifResults.push(sarifResult);
203
+ }
204
+ }
205
+ return {
206
+ version: '2.1.0',
207
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
208
+ runs: [
209
+ {
210
+ tool: {
211
+ driver: {
212
+ name: 'lightdash-lint',
213
+ version: '1.0.0',
214
+ informationUri: 'https://github.com/lightdash/lightdash',
215
+ rules: Array.from(rules.values()),
216
+ },
217
+ },
218
+ results: sarifResults,
219
+ },
220
+ ],
221
+ };
222
+ }
@@ -0,0 +1,14 @@
1
+ import type { SarifLog } from './ajvToSarif';
2
+ /**
3
+ * Format SARIF results for CLI output
4
+ */
5
+ export declare function formatSarifForCli(sarifLog: SarifLog, searchPath: string): string;
6
+ /**
7
+ * Get summary statistics from SARIF log
8
+ */
9
+ export declare function getSarifSummary(sarifLog: SarifLog): {
10
+ totalFiles: number;
11
+ totalErrors: number;
12
+ hasErrors: boolean;
13
+ };
14
+ //# sourceMappingURL=sarifFormatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarifFormatter.d.ts","sourceRoot":"","sources":["../../../src/handlers/lint/sarifFormatter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,cAAc,CAAC;AAgC1D;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACnB,MAAM,CA2ER;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACtB,CAsBA"}
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatSarifForCli = formatSarifForCli;
4
+ exports.getSarifSummary = getSarifSummary;
5
+ const tslib_1 = require("tslib");
6
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const fs = tslib_1.__importStar(require("fs"));
8
+ const path = tslib_1.__importStar(require("path"));
9
+ /**
10
+ * Get a snippet of the file around the error location
11
+ */
12
+ function getCodeSnippet(filePath, line, contextLines = 2) {
13
+ try {
14
+ const content = fs.readFileSync(filePath, 'utf8');
15
+ const lines = content.split('\n');
16
+ const startLine = Math.max(0, line - 1 - contextLines);
17
+ const endLine = Math.min(lines.length, line + contextLines);
18
+ return lines
19
+ .slice(startLine, endLine)
20
+ .map((lineText, idx) => {
21
+ const lineNum = startLine + idx + 1;
22
+ const prefix = lineNum === line ? '→ ' : ' ';
23
+ return `${prefix}${lineNum
24
+ .toString()
25
+ .padStart(3, ' ')} | ${lineText}`;
26
+ })
27
+ .join('\n');
28
+ }
29
+ catch {
30
+ return '';
31
+ }
32
+ }
33
+ /**
34
+ * Format SARIF results for CLI output
35
+ */
36
+ function formatSarifForCli(sarifLog, searchPath) {
37
+ const output = [];
38
+ if (!sarifLog.runs || sarifLog.runs.length === 0) {
39
+ return chalk_1.default.green('\n✓ All Lightdash Code files are valid!\n');
40
+ }
41
+ const run = sarifLog.runs[0];
42
+ const results = run.results || [];
43
+ if (results.length === 0) {
44
+ return chalk_1.default.green('\n✓ All Lightdash Code files are valid!\n');
45
+ }
46
+ // Group results by file
47
+ const resultsByFile = new Map();
48
+ for (const result of results) {
49
+ const location = result.locations?.[0];
50
+ const uri = location?.physicalLocation?.artifactLocation?.uri;
51
+ if (uri) {
52
+ if (!resultsByFile.has(uri)) {
53
+ resultsByFile.set(uri, []);
54
+ }
55
+ resultsByFile.get(uri).push(result);
56
+ }
57
+ }
58
+ // Count stats
59
+ const totalFiles = resultsByFile.size;
60
+ output.push(chalk_1.default.red('\nValidation Errors:\n'));
61
+ // Format each file's errors
62
+ for (const [fileUri, fileResults] of resultsByFile.entries()) {
63
+ const relativePath = path.relative(searchPath, fileUri);
64
+ output.push(chalk_1.default.red.bold(`\n✗ ${relativePath}`));
65
+ for (const result of fileResults) {
66
+ const message = result.message.text;
67
+ const location = result.locations?.[0]?.physicalLocation;
68
+ const line = location?.region?.startLine;
69
+ const column = location?.region?.startColumn;
70
+ output.push(chalk_1.default.red(`\n ${message}${line ? ` (line ${line})` : ''}`));
71
+ // Add clickable file link for IDEs
72
+ if (line) {
73
+ // Encode the URI properly for file:// protocol to handle special characters (#, ?, &, spaces, etc.)
74
+ const encodedPath = encodeURI(fileUri);
75
+ const fileLink = `file://${encodedPath}:${line}${column ? `:${column}` : ''}`;
76
+ output.push(chalk_1.default.dim(` ${fileLink}`));
77
+ }
78
+ if (line && fs.existsSync(fileUri)) {
79
+ const snippet = getCodeSnippet(fileUri, line);
80
+ if (snippet) {
81
+ output.push(chalk_1.default.dim(`\n${snippet}\n`));
82
+ }
83
+ }
84
+ }
85
+ }
86
+ output.push(chalk_1.default.red(`\nValidation failed for ${totalFiles} file${totalFiles > 1 ? 's' : ''}`));
87
+ return output.join('\n');
88
+ }
89
+ /**
90
+ * Get summary statistics from SARIF log
91
+ */
92
+ function getSarifSummary(sarifLog) {
93
+ if (!sarifLog.runs || sarifLog.runs.length === 0) {
94
+ return { totalFiles: 0, totalErrors: 0, hasErrors: false };
95
+ }
96
+ const run = sarifLog.runs[0];
97
+ const results = run.results || [];
98
+ const uniqueFiles = new Set();
99
+ for (const result of results) {
100
+ const location = result.locations?.[0];
101
+ const uri = location?.physicalLocation?.artifactLocation?.uri;
102
+ if (uri) {
103
+ uniqueFiles.add(uri);
104
+ }
105
+ }
106
+ return {
107
+ totalFiles: uniqueFiles.size,
108
+ totalErrors: results.length,
109
+ hasErrors: results.length > 0,
110
+ };
111
+ }
@@ -0,0 +1,8 @@
1
+ type LintOptions = {
2
+ path?: string;
3
+ verbose?: boolean;
4
+ format?: 'cli' | 'json';
5
+ };
6
+ export declare function lintHandler(options: LintOptions): Promise<void>;
7
+ export {};
8
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/handlers/lint.ts"],"names":[],"mappings":"AAUA,KAAK,WAAW,GAAG;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CAC3B,CAAC;AAgNF,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmGrE"}