@lightdash/cli 1.242.4 → 1.242.6
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/.tsbuildinfo +1 -1
- package/dist/handlers/contentAsCode/fileDiscovery.d.ts +12 -0
- package/dist/handlers/contentAsCode/fileDiscovery.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/fileDiscovery.js +38 -0
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +16 -15
- package/dist/handlers/lint/sarifFormatter.d.ts.map +1 -1
- package/dist/handlers/lint/sarifFormatter.js +1 -1
- package/dist/handlers/lint.d.ts.map +1 -1
- package/dist/handlers/lint.js +206 -70
- package/dist/handlers/lint.test.d.ts +2 -0
- package/dist/handlers/lint.test.d.ts.map +1 -0
- package/dist/handlers/lint.test.js +221 -0
- package/package.json +3 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ContentFileType = 'chart' | 'dashboard';
|
|
2
|
+
export type ContentFileClassification = {
|
|
3
|
+
kind: 'content';
|
|
4
|
+
contentType: ContentFileType;
|
|
5
|
+
supportedExtension: boolean;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'loose';
|
|
8
|
+
supportedExtension: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const classifyContentFilePath: (filePath: string, rootPath?: string) => ContentFileClassification | undefined;
|
|
11
|
+
export declare const isSqlChartContent: (item: object) => boolean;
|
|
12
|
+
//# sourceMappingURL=fileDiscovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileDiscovery.d.ts","sourceRoot":"","sources":["../../../src/handlers/contentAsCode/fileDiscovery.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,WAAW,CAAC;AAEpD,MAAM,MAAM,yBAAyB,GAC/B;IACI,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,eAAe,CAAC;IAC7B,kBAAkB,EAAE,OAAO,CAAC;CAC/B,GACD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,OAAO,CAAA;CAAE,CAAC;AAYrD,eAAO,MAAM,uBAAuB,aACtB,MAAM,aACL,MAAM,KAClB,yBAAyB,GAAG,SAyB9B,CAAC;AAEF,eAAO,MAAM,iBAAiB,SAAU,MAAM,KAAG,OACN,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSqlChartContent = exports.classifyContentFilePath = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path = tslib_1.__importStar(require("path"));
|
|
6
|
+
const isWithinRoot = (filePath, rootPath) => {
|
|
7
|
+
const relativePath = path.relative(rootPath, filePath);
|
|
8
|
+
return (relativePath === '' ||
|
|
9
|
+
(!relativePath.startsWith(`..${path.sep}`) &&
|
|
10
|
+
relativePath !== '..' &&
|
|
11
|
+
!path.isAbsolute(relativePath)));
|
|
12
|
+
};
|
|
13
|
+
const classifyContentFilePath = (filePath, rootPath) => {
|
|
14
|
+
if (rootPath && !isWithinRoot(filePath, rootPath))
|
|
15
|
+
return undefined;
|
|
16
|
+
const supportedExtension = filePath.endsWith('.yml');
|
|
17
|
+
if (!supportedExtension && !filePath.endsWith('.yaml'))
|
|
18
|
+
return undefined;
|
|
19
|
+
if (filePath.endsWith('.space.yml') ||
|
|
20
|
+
filePath.endsWith('.language.map.yml')) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const parentFolder = path.basename(path.dirname(filePath));
|
|
24
|
+
if (parentFolder === 'charts') {
|
|
25
|
+
return { kind: 'content', contentType: 'chart', supportedExtension };
|
|
26
|
+
}
|
|
27
|
+
if (parentFolder === 'dashboards') {
|
|
28
|
+
return {
|
|
29
|
+
kind: 'content',
|
|
30
|
+
contentType: 'dashboard',
|
|
31
|
+
supportedExtension,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return { kind: 'loose', supportedExtension };
|
|
35
|
+
};
|
|
36
|
+
exports.classifyContentFilePath = classifyContentFilePath;
|
|
37
|
+
const isSqlChartContent = (item) => 'sql' in item && !('tableName' in item);
|
|
38
|
+
exports.isSqlChartContent = isSqlChartContent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,WAAW,EA0BX,WAAW,EAGX,eAAe,EAEf,wBAAwB,EAgBxB,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAC;AAgB3B,OAAO,EAIH,KAAK,0BAA0B,EAClC,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,7 @@ const appCodeFiles_1 = require("./apps/appCodeFiles");
|
|
|
22
22
|
const appsDownload_1 = require("./apps/appsDownload");
|
|
23
23
|
const scaffolding_1 = require("./apps/scaffolding");
|
|
24
24
|
const uploadRetry_1 = require("./apps/uploadRetry");
|
|
25
|
+
const fileDiscovery_1 = require("./contentAsCode/fileDiscovery");
|
|
25
26
|
const projectResources_1 = require("./contentAsCode/projectResources");
|
|
26
27
|
const resource_1 = require("./contentAsCode/resource");
|
|
27
28
|
const contentAsCodePaths_1 = require("./contentAsCodePaths");
|
|
@@ -157,18 +158,20 @@ const hasUnsortedKeys = (obj) => {
|
|
|
157
158
|
}
|
|
158
159
|
return Object.values(obj).some(hasUnsortedKeys);
|
|
159
160
|
};
|
|
160
|
-
const isLightdashContentFile = (folder, entry) =>
|
|
161
|
-
entry.parentPath
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
!entry.
|
|
170
|
-
|
|
171
|
-
|
|
161
|
+
const isLightdashContentFile = (folder, entry) => {
|
|
162
|
+
if (!entry.isFile() || !entry.parentPath)
|
|
163
|
+
return false;
|
|
164
|
+
const classification = (0, fileDiscovery_1.classifyContentFilePath)(path.join(entry.parentPath, entry.name));
|
|
165
|
+
return (classification?.kind === 'content' &&
|
|
166
|
+
classification.supportedExtension &&
|
|
167
|
+
`${classification.contentType}s` === folder);
|
|
168
|
+
};
|
|
169
|
+
const isLooseContentFile = (entry) => {
|
|
170
|
+
if (!entry.isFile() || !entry.parentPath)
|
|
171
|
+
return false;
|
|
172
|
+
const classification = (0, fileDiscovery_1.classifyContentFilePath)(path.join(entry.parentPath, entry.name));
|
|
173
|
+
return (classification?.kind === 'loose' && classification.supportedExtension);
|
|
174
|
+
};
|
|
172
175
|
const processYamlItem = (item, fileName, stats, folder, metadata) => {
|
|
173
176
|
if (hasUnsortedKeys(item)) {
|
|
174
177
|
globalState_1.default.log(styles.warning(`Warning: ${fileName} has unsorted YAML keys. Re-download to fix, or sort keys alphabetically.`));
|
|
@@ -1482,8 +1485,6 @@ const runUploadChangesPhase = async ({ output, label, changes, action, onCount,
|
|
|
1482
1485
|
onCount?.(countChangeDelta(before, updatedChanges));
|
|
1483
1486
|
return updatedChanges;
|
|
1484
1487
|
};
|
|
1485
|
-
// SQL charts have 'sql' field instead of 'tableName'/'metricQuery'
|
|
1486
|
-
const isSqlChart = (item) => 'sql' in item && !('tableName' in item);
|
|
1487
1488
|
const upsertSingleItem = async (item, type, projectId, changes, force, config, skipSpaceCreate, publicSpaceCreate, validate, spaceNames) => {
|
|
1488
1489
|
try {
|
|
1489
1490
|
if (!force && !item.needsUpdating) {
|
|
@@ -1493,7 +1494,7 @@ const upsertSingleItem = async (item, type, projectId, changes, force, config, s
|
|
|
1493
1494
|
}
|
|
1494
1495
|
globalState_1.default.debug(`Upserting ${type} ${item.slug}`);
|
|
1495
1496
|
// SQL charts use a different endpoint
|
|
1496
|
-
const isSqlChartItem = type === 'charts' &&
|
|
1497
|
+
const isSqlChartItem = type === 'charts' && (0, fileDiscovery_1.isSqlChartContent)(item);
|
|
1497
1498
|
const endpoint = isSqlChartItem
|
|
1498
1499
|
? `/api/v1/projects/${projectId}/code/sqlCharts/${item.slug}`
|
|
1499
1500
|
: `/api/v1/projects/${projectId}/code/${type}/${item.slug}`;
|
|
@@ -1 +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,
|
|
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,CAqFR;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,CA4BA"}
|
|
@@ -59,7 +59,7 @@ function formatSarifForCli(sarifLog, searchPath) {
|
|
|
59
59
|
}
|
|
60
60
|
output.push(color(`\n${sectionLabel}\n`));
|
|
61
61
|
for (const [fileUri, fileResults] of byFile.entries()) {
|
|
62
|
-
const relativePath = path.relative(searchPath, fileUri);
|
|
62
|
+
const relativePath = path.relative(searchPath, fileUri) || path.basename(fileUri);
|
|
63
63
|
output.push(boldColor(`\n${marker} ${relativePath}`));
|
|
64
64
|
for (const result of fileResults) {
|
|
65
65
|
const message = result.message.text;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/handlers/lint.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/handlers/lint.ts"],"names":[],"mappings":"AA2BA,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;AA4aF,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6JrE"}
|
package/dist/handlers/lint.js
CHANGED
|
@@ -5,11 +5,14 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const common_1 = require("@lightdash/common");
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
7
|
const fs = tslib_1.__importStar(require("fs"));
|
|
8
|
+
const yaml = tslib_1.__importStar(require("js-yaml"));
|
|
8
9
|
const path = tslib_1.__importStar(require("path"));
|
|
9
10
|
const uuid_1 = require("uuid");
|
|
10
11
|
const YAML = tslib_1.__importStar(require("yaml"));
|
|
11
12
|
const ajv_1 = require("../ajv");
|
|
12
13
|
const analytics_1 = require("../analytics/analytics");
|
|
14
|
+
const fileDiscovery_1 = require("./contentAsCode/fileDiscovery");
|
|
15
|
+
const contentAsCodePaths_1 = require("./contentAsCodePaths");
|
|
13
16
|
const ajvToSarif_1 = require("./lint/ajvToSarif");
|
|
14
17
|
const sarifFormatter_1 = require("./lint/sarifFormatter");
|
|
15
18
|
const validateChartSchema = ajv_1.ajv.compile(common_1.chartAsCodeSchema);
|
|
@@ -80,8 +83,14 @@ function buildLocationMap(fileContent, isJson) {
|
|
|
80
83
|
const data = JSON.parse(fileContent);
|
|
81
84
|
return { data, locationMap };
|
|
82
85
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
86
|
+
// Match upload parsing, then normalize the value as it will be serialized
|
|
87
|
+
// into the upload request. Use `yaml` separately for source locations.
|
|
88
|
+
const parsedData = yaml.load(fileContent);
|
|
89
|
+
const serializedData = JSON.stringify(parsedData);
|
|
90
|
+
const data = serializedData === undefined
|
|
91
|
+
? parsedData
|
|
92
|
+
: JSON.parse(serializedData);
|
|
93
|
+
const doc = YAML.parseDocument(fileContent, { merge: true });
|
|
85
94
|
function traverse(node, jsonPath) {
|
|
86
95
|
if (!node)
|
|
87
96
|
return;
|
|
@@ -111,89 +120,210 @@ function buildLocationMap(fileContent, isJson) {
|
|
|
111
120
|
}
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
if (doc.errors.length === 0) {
|
|
124
|
+
traverse(doc.contents, '');
|
|
125
|
+
}
|
|
117
126
|
return { data, locationMap };
|
|
118
127
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
function validateAsCodeSchema({ data, fileContent, filePath, locationMap, type, }) {
|
|
129
|
+
let validate;
|
|
130
|
+
switch (type) {
|
|
131
|
+
case 'chart':
|
|
132
|
+
validate = validateChartSchema;
|
|
133
|
+
break;
|
|
134
|
+
case 'dashboard':
|
|
135
|
+
validate = validateDashboardSchema;
|
|
136
|
+
break;
|
|
137
|
+
case 'model':
|
|
138
|
+
validate = validateModelSchema;
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
return (0, common_1.assertUnreachable)(type, `Unknown Lightdash Code type: ${type}`);
|
|
142
|
+
}
|
|
143
|
+
const valid = validate(data);
|
|
144
|
+
if (!valid && validate.errors) {
|
|
145
|
+
return {
|
|
146
|
+
filePath,
|
|
147
|
+
valid: false,
|
|
148
|
+
errors: validate.errors,
|
|
149
|
+
fileContent,
|
|
150
|
+
locationMap,
|
|
151
|
+
type,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return { filePath, valid: true, type };
|
|
155
|
+
}
|
|
156
|
+
function createErrorResult({ error, fileContent, filePath, locationMap, type, }) {
|
|
157
|
+
return {
|
|
158
|
+
filePath,
|
|
159
|
+
valid: false,
|
|
160
|
+
errors: [error],
|
|
161
|
+
fileContent,
|
|
162
|
+
locationMap,
|
|
163
|
+
type,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function getUploadType(classification, data) {
|
|
167
|
+
if (classification?.kind === 'content') {
|
|
168
|
+
return classification.contentType;
|
|
169
|
+
}
|
|
170
|
+
if (classification?.kind !== 'loose')
|
|
171
|
+
return undefined;
|
|
172
|
+
if (data?.contentType === common_1.ContentAsCodeType.CHART ||
|
|
173
|
+
data?.contentType === common_1.ContentAsCodeType.SQL_CHART) {
|
|
174
|
+
return 'chart';
|
|
175
|
+
}
|
|
176
|
+
if (data?.contentType === common_1.ContentAsCodeType.DASHBOARD) {
|
|
177
|
+
return 'dashboard';
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
function getModelCodeType(data) {
|
|
182
|
+
if (data?.type === 'model' ||
|
|
183
|
+
data?.type === 'model/v1beta' ||
|
|
184
|
+
data?.type === 'model/v1') {
|
|
185
|
+
return 'model';
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
function getPartialYamlData(fileContent) {
|
|
190
|
+
try {
|
|
191
|
+
const doc = YAML.parseDocument(fileContent, { merge: true });
|
|
192
|
+
const data = doc.toJS();
|
|
193
|
+
return data && typeof data === 'object' && !Array.isArray(data)
|
|
194
|
+
? data
|
|
195
|
+
: undefined;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function getParseErrorLocation(error) {
|
|
202
|
+
if (!error || typeof error !== 'object')
|
|
203
|
+
return undefined;
|
|
204
|
+
const linePos = Reflect.get(error, 'linePos');
|
|
205
|
+
if (Array.isArray(linePos)) {
|
|
206
|
+
const firstPosition = linePos[0];
|
|
207
|
+
if (typeof firstPosition?.line === 'number' &&
|
|
208
|
+
typeof firstPosition.col === 'number') {
|
|
209
|
+
return { line: firstPosition.line, column: firstPosition.col };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const mark = Reflect.get(error, 'mark');
|
|
213
|
+
if (typeof mark?.line === 'number' && typeof mark.column === 'number') {
|
|
214
|
+
return { line: mark.line + 1, column: mark.column + 1 };
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
function createUnsupportedExtensionResult(filePath, fileContent, type) {
|
|
219
|
+
return createErrorResult({
|
|
220
|
+
filePath,
|
|
221
|
+
fileContent,
|
|
222
|
+
type,
|
|
223
|
+
error: {
|
|
224
|
+
keyword: 'extension',
|
|
225
|
+
instancePath: '',
|
|
226
|
+
schemaPath: '',
|
|
227
|
+
params: { allowedExtension: '.yml' },
|
|
228
|
+
message: "Content files must use the '.yml' extension",
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function validateFile(filePath, contentRoot) {
|
|
233
|
+
const classification = (0, fileDiscovery_1.classifyContentFilePath)(filePath, contentRoot);
|
|
234
|
+
let fileContent;
|
|
123
235
|
try {
|
|
124
|
-
|
|
125
|
-
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
236
|
+
fileContent = fs.readFileSync(filePath, 'utf8');
|
|
126
237
|
const isJson = filePath.endsWith('.json');
|
|
127
238
|
const { data, locationMap } = buildLocationMap(fileContent, isJson);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
239
|
+
const dataObj = data && typeof data === 'object' && !Array.isArray(data)
|
|
240
|
+
? data
|
|
241
|
+
: undefined;
|
|
242
|
+
const uploadType = getUploadType(classification, dataObj);
|
|
243
|
+
if (uploadType && classification?.supportedExtension === false) {
|
|
244
|
+
return createUnsupportedExtensionResult(filePath, fileContent, uploadType);
|
|
134
245
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
dataObj.type === 'model/v1beta' ||
|
|
139
|
-
dataObj.type === 'model/v1') {
|
|
140
|
-
const valid = validateModelSchema(data);
|
|
141
|
-
if (!valid && validateModelSchema.errors) {
|
|
142
|
-
return {
|
|
246
|
+
if (!dataObj) {
|
|
247
|
+
return uploadType
|
|
248
|
+
? createErrorResult({
|
|
143
249
|
filePath,
|
|
144
|
-
valid: false,
|
|
145
|
-
errors: validateModelSchema.errors,
|
|
146
250
|
fileContent,
|
|
147
251
|
locationMap,
|
|
148
|
-
type:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
252
|
+
type: uploadType,
|
|
253
|
+
error: {
|
|
254
|
+
keyword: 'type',
|
|
255
|
+
instancePath: '',
|
|
256
|
+
schemaPath: '#/type',
|
|
257
|
+
params: { type: 'object' },
|
|
258
|
+
message: 'must be object',
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
: { filePath, valid: true };
|
|
152
262
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!valid && validateChartSchema.errors) {
|
|
157
|
-
return {
|
|
158
|
-
filePath,
|
|
159
|
-
valid: false,
|
|
160
|
-
errors: validateChartSchema.errors,
|
|
161
|
-
fileContent,
|
|
162
|
-
locationMap,
|
|
163
|
-
type: 'chart',
|
|
164
|
-
};
|
|
263
|
+
if (uploadType) {
|
|
264
|
+
if (uploadType === 'chart' && (0, fileDiscovery_1.isSqlChartContent)(dataObj)) {
|
|
265
|
+
return { filePath, valid: true, type: 'chart' };
|
|
165
266
|
}
|
|
166
|
-
return {
|
|
267
|
+
return validateAsCodeSchema({
|
|
268
|
+
data,
|
|
269
|
+
fileContent,
|
|
270
|
+
filePath,
|
|
271
|
+
locationMap,
|
|
272
|
+
type: uploadType,
|
|
273
|
+
});
|
|
167
274
|
}
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
locationMap,
|
|
178
|
-
type: 'dashboard',
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
return { filePath, valid: true, type: 'dashboard' };
|
|
275
|
+
const modelType = getModelCodeType(dataObj);
|
|
276
|
+
if (modelType) {
|
|
277
|
+
return validateAsCodeSchema({
|
|
278
|
+
data,
|
|
279
|
+
fileContent,
|
|
280
|
+
filePath,
|
|
281
|
+
locationMap,
|
|
282
|
+
type: modelType,
|
|
283
|
+
});
|
|
182
284
|
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
filePath,
|
|
186
|
-
valid: true, // Don't report non-lightdash files as errors
|
|
187
|
-
};
|
|
285
|
+
return { filePath, valid: true };
|
|
188
286
|
}
|
|
189
287
|
catch (error) {
|
|
190
|
-
|
|
191
|
-
|
|
288
|
+
const partialData = fileContent
|
|
289
|
+
? getPartialYamlData(fileContent)
|
|
290
|
+
: undefined;
|
|
291
|
+
const uploadType = getUploadType(classification, partialData);
|
|
292
|
+
const type = uploadType ?? getModelCodeType(partialData);
|
|
293
|
+
if (!type)
|
|
294
|
+
return { filePath, valid: true };
|
|
295
|
+
if (uploadType && classification?.supportedExtension === false) {
|
|
296
|
+
return createUnsupportedExtensionResult(filePath, fileContent ?? '', uploadType);
|
|
297
|
+
}
|
|
298
|
+
const parseLocation = getParseErrorLocation(error);
|
|
299
|
+
const locationMap = parseLocation
|
|
300
|
+
? new Map([['/', parseLocation]])
|
|
301
|
+
: undefined;
|
|
302
|
+
return createErrorResult({
|
|
192
303
|
filePath,
|
|
193
|
-
|
|
194
|
-
|
|
304
|
+
fileContent: fileContent ?? '',
|
|
305
|
+
locationMap,
|
|
306
|
+
type,
|
|
307
|
+
error: {
|
|
308
|
+
keyword: 'parse',
|
|
309
|
+
instancePath: '',
|
|
310
|
+
schemaPath: '',
|
|
311
|
+
params: {},
|
|
312
|
+
message: (0, common_1.getErrorMessage)(error),
|
|
313
|
+
},
|
|
314
|
+
});
|
|
195
315
|
}
|
|
196
316
|
}
|
|
317
|
+
function getDefaultContentRoot(searchPath) {
|
|
318
|
+
const contentRootMarkers = [
|
|
319
|
+
'.lightdash-metadata.json',
|
|
320
|
+
'charts',
|
|
321
|
+
'dashboards',
|
|
322
|
+
];
|
|
323
|
+
return contentRootMarkers.some((marker) => fs.existsSync(path.join(searchPath, marker)))
|
|
324
|
+
? searchPath
|
|
325
|
+
: (0, contentAsCodePaths_1.getDownloadFolder)();
|
|
326
|
+
}
|
|
197
327
|
async function lintHandler(options) {
|
|
198
328
|
const executionId = (0, uuid_1.v4)();
|
|
199
329
|
const startTime = Date.now();
|
|
@@ -205,6 +335,12 @@ async function lintHandler(options) {
|
|
|
205
335
|
if (!fs.existsSync(searchPath)) {
|
|
206
336
|
throw new Error(`Path does not exist: ${searchPath}`);
|
|
207
337
|
}
|
|
338
|
+
let contentRoot = getDefaultContentRoot(searchPath);
|
|
339
|
+
if (options.path) {
|
|
340
|
+
contentRoot = fs.statSync(searchPath).isFile()
|
|
341
|
+
? path.dirname(searchPath)
|
|
342
|
+
: searchPath;
|
|
343
|
+
}
|
|
208
344
|
if (outputFormat === 'cli') {
|
|
209
345
|
console.log(chalk_1.default.dim(`Searching for Lightdash Code files in: ${searchPath}\n`));
|
|
210
346
|
}
|
|
@@ -222,7 +358,7 @@ async function lintHandler(options) {
|
|
|
222
358
|
}
|
|
223
359
|
// Validate each file
|
|
224
360
|
for (const file of codeFiles) {
|
|
225
|
-
const result = validateFile(file);
|
|
361
|
+
const result = validateFile(file, contentRoot);
|
|
226
362
|
// Only track Lightdash Code files (models, charts, dashboards)
|
|
227
363
|
if (result.type) {
|
|
228
364
|
results.push(result);
|
|
@@ -231,13 +367,13 @@ async function lintHandler(options) {
|
|
|
231
367
|
if (results.length === 0) {
|
|
232
368
|
if (outputFormat === 'cli') {
|
|
233
369
|
console.log(chalk_1.default.yellow('No Lightdash Code files found.'));
|
|
234
|
-
console.log(chalk_1.default.dim('Models must have type: model (or model/v1, model/v1beta)
|
|
370
|
+
console.log(chalk_1.default.dim('Models must have type: model (or model/v1, model/v1beta). Charts and dashboards must be in their matching folder or declare contentType.'));
|
|
235
371
|
}
|
|
236
372
|
}
|
|
237
373
|
else {
|
|
238
374
|
// Build SARIF report from all results that produced schema errors.
|
|
239
375
|
const sarifResults = results
|
|
240
|
-
.filter((r) => r.fileContent &&
|
|
376
|
+
.filter((r) => r.fileContent !== undefined &&
|
|
241
377
|
r.type &&
|
|
242
378
|
r.errors &&
|
|
243
379
|
r.errors.length > 0)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.test.d.ts","sourceRoot":"","sources":["../../src/handlers/lint.test.ts"],"names":[],"mappings":""}
|