@lightdash/cli 0.2658.1 → 0.2661.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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"AA4CA,MAAM,MAAM,sBAAsB,GAAG;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AA2FF,KAAK,aAAa,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;CACxB,CAAC;AAoVF,KAAK,mBAAmB,GAAG,QAAQ,GAAG,YAAY,GAAG,WAAW,CAAC;AAkDjE,eAAO,MAAM,eAAe,GACxB,KAAK,MAAM,EAAE,EACb,MAAM,mBAAmB,EACzB,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,aAAa,MAAM,EACnB,cAAa,OAAe,EAC5B,SAAQ,OAAe,KACxB,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,CAAC,CA+G7C,CAAC;AAEF,eAAO,MAAM,eAAe,GACxB,SAAS,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAsMd,CAAC;AAgbF,eAAO,MAAM,aAAa,GACtB,SAAS,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAiKd,CAAC"}
|
|
@@ -119,15 +119,15 @@ const isLightdashContentFile = (folder, entry) => entry.isFile() &&
|
|
|
119
119
|
entry.parentPath.endsWith(path.sep + folder) &&
|
|
120
120
|
entry.name.endsWith('.yml') &&
|
|
121
121
|
!entry.name.endsWith('.language.map.yml');
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
const isLooseContentFile = (entry) => entry.isFile() &&
|
|
123
|
+
entry.parentPath &&
|
|
124
|
+
!entry.parentPath.endsWith(`${path.sep}charts`) &&
|
|
125
|
+
!entry.parentPath.endsWith(`${path.sep}dashboards`) &&
|
|
126
|
+
entry.name.endsWith('.yml') &&
|
|
127
|
+
!entry.name.endsWith('.language.map.yml');
|
|
128
|
+
const processYamlItem = (item, fileName, stats, folder, metadata) => {
|
|
129
129
|
if (hasUnsortedKeys(item)) {
|
|
130
|
-
globalState_1.default.log(styles.warning(`Warning: ${
|
|
130
|
+
globalState_1.default.log(styles.warning(`Warning: ${fileName} has unsorted YAML keys. Re-download to fix, or sort keys alphabetically.`));
|
|
131
131
|
}
|
|
132
132
|
const metadataSection = folder === 'dashboards' ? metadata.dashboards : metadata.charts;
|
|
133
133
|
const downloadedAtRaw = metadataSection[item.slug] ?? item.downloadedAt;
|
|
@@ -144,6 +144,15 @@ const loadYamlFile = async (file, folder, metadata) => {
|
|
|
144
144
|
needsUpdating: needsUpdating ?? true,
|
|
145
145
|
};
|
|
146
146
|
};
|
|
147
|
+
const loadYamlFile = async (file, folder, metadata) => {
|
|
148
|
+
const filePath = path.join(file.parentPath, file.name);
|
|
149
|
+
const [fileContent, stats] = await Promise.all([
|
|
150
|
+
fs_1.promises.readFile(filePath, 'utf-8'),
|
|
151
|
+
fs_1.promises.stat(filePath),
|
|
152
|
+
]);
|
|
153
|
+
const item = yaml.load(fileContent);
|
|
154
|
+
return processYamlItem(item, file.name, stats, folder, metadata);
|
|
155
|
+
};
|
|
147
156
|
const readCodeFiles = async (folder, customPath) => {
|
|
148
157
|
const baseDir = getDownloadFolder(customPath);
|
|
149
158
|
globalState_1.default.log(`Reading ${folder} from ${baseDir}`);
|
|
@@ -176,6 +185,55 @@ const readCodeFiles = async (folder, customPath) => {
|
|
|
176
185
|
throw error;
|
|
177
186
|
}
|
|
178
187
|
};
|
|
188
|
+
/**
|
|
189
|
+
* Reads YAML files outside the standard charts/ and dashboards/ directories
|
|
190
|
+
* and classifies them by their contentType field.
|
|
191
|
+
*/
|
|
192
|
+
const readLooseCodeFiles = async (customPath) => {
|
|
193
|
+
const baseDir = getDownloadFolder(customPath);
|
|
194
|
+
const charts = [];
|
|
195
|
+
const dashboards = [];
|
|
196
|
+
try {
|
|
197
|
+
const metadata = await (0, metadataFile_1.readMetadataFile)(baseDir);
|
|
198
|
+
const allEntries = await fs_1.promises.readdir(baseDir, {
|
|
199
|
+
recursive: true,
|
|
200
|
+
withFileTypes: true,
|
|
201
|
+
});
|
|
202
|
+
const looseFiles = allEntries.filter(isLooseContentFile);
|
|
203
|
+
await Promise.all(looseFiles.map(async (file) => {
|
|
204
|
+
try {
|
|
205
|
+
const filePath = path.join(file.parentPath, file.name);
|
|
206
|
+
const [fileContent, stats] = await Promise.all([
|
|
207
|
+
fs_1.promises.readFile(filePath, 'utf-8'),
|
|
208
|
+
fs_1.promises.stat(filePath),
|
|
209
|
+
]);
|
|
210
|
+
const parsed = yaml.load(fileContent);
|
|
211
|
+
const contentType = parsed?.contentType;
|
|
212
|
+
if (contentType === common_1.ContentAsCodeType.CHART ||
|
|
213
|
+
contentType === common_1.ContentAsCodeType.SQL_CHART) {
|
|
214
|
+
charts.push(processYamlItem(parsed, file.name, stats, 'charts', metadata));
|
|
215
|
+
}
|
|
216
|
+
else if (contentType === common_1.ContentAsCodeType.DASHBOARD) {
|
|
217
|
+
dashboards.push(processYamlItem(parsed, file.name, stats, 'dashboards', metadata));
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
globalState_1.default.debug(`Skipping ${file.name}: no recognized contentType`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
globalState_1.default.debug(`Skipping ${file.name}: failed to parse (${(0, common_1.getErrorMessage)(e)})`);
|
|
225
|
+
}
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (error.code === 'ENOENT') {
|
|
230
|
+
// Base directory doesn't exist — nothing to discover
|
|
231
|
+
return { charts, dashboards };
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
return { charts, dashboards };
|
|
236
|
+
};
|
|
179
237
|
const groupBySpace = (items) => {
|
|
180
238
|
const itemsWithIndex = items.map((item, index) => ({ item, index }));
|
|
181
239
|
return (0, groupBy_1.default)(itemsWithIndex, (entry) => entry.item.spaceSlug);
|
|
@@ -521,6 +579,16 @@ const upsertSingleItem = async (item, type, projectId, changes, force, config, s
|
|
|
521
579
|
Object.keys(updatedChanges).forEach((key) => {
|
|
522
580
|
changes[key] = updatedChanges[key];
|
|
523
581
|
});
|
|
582
|
+
// Warn if contentType contradicts the folder this item came from
|
|
583
|
+
const itemContentType = item.contentType;
|
|
584
|
+
if (itemContentType) {
|
|
585
|
+
const expectedType = itemContentType === common_1.ContentAsCodeType.DASHBOARD
|
|
586
|
+
? 'dashboards'
|
|
587
|
+
: 'charts';
|
|
588
|
+
if (expectedType !== type) {
|
|
589
|
+
globalState_1.default.log(styles.warning(`Warning: "${item.name}" has contentType "${itemContentType}" but is in the ${type}/ directory. It will be uploaded as a ${type.slice(0, -1)}.`));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
524
592
|
// Run validation if requested
|
|
525
593
|
if (validate && !isSqlChartItem) {
|
|
526
594
|
const contentUuid = type === 'charts'
|
|
@@ -581,9 +649,10 @@ const upsertSingleItem = async (item, type, projectId, changes, force, config, s
|
|
|
581
649
|
*
|
|
582
650
|
* @param slugs if slugs are provided, we only force upsert the charts/dashboards that match the slugs, if slugs are empty, we upload files that were locally updated
|
|
583
651
|
*/
|
|
584
|
-
const upsertResources = async (type, projectId, changes, force, slugs, customPath, skipSpaceCreate, publicSpaceCreate, validate, concurrency = 1) => {
|
|
652
|
+
const upsertResources = async (type, projectId, changes, force, slugs, customPath, skipSpaceCreate, publicSpaceCreate, validate, concurrency = 1, extraItems = []) => {
|
|
585
653
|
const config = await (0, config_1.getConfig)();
|
|
586
|
-
const
|
|
654
|
+
const folderItems = await readCodeFiles(type, customPath);
|
|
655
|
+
const items = [...folderItems, ...extraItems];
|
|
587
656
|
globalState_1.default.log(`Found ${items.length} ${type} files`);
|
|
588
657
|
const hasFilter = slugs.length > 0;
|
|
589
658
|
const filteredItems = hasFilter
|
|
@@ -724,11 +793,19 @@ const uploadHandler = async (options) => {
|
|
|
724
793
|
if (parseInt(String(options.concurrency), 10) > 1000) {
|
|
725
794
|
globalState_1.default.log(styles.warning(`Concurrency limit exceeded. Using maximum of 1000 instead of ${options.concurrency}`));
|
|
726
795
|
}
|
|
796
|
+
// Discover loose YAML files (outside charts/ and dashboards/) classified by contentType
|
|
797
|
+
const looseFiles = await readLooseCodeFiles(options.path);
|
|
798
|
+
if (looseFiles.charts.length > 0) {
|
|
799
|
+
globalState_1.default.log(`Found ${looseFiles.charts.length} chart(s) outside charts/ directory (classified by contentType)`);
|
|
800
|
+
}
|
|
801
|
+
if (looseFiles.dashboards.length > 0) {
|
|
802
|
+
globalState_1.default.log(`Found ${looseFiles.dashboards.length} dashboard(s) outside dashboards/ directory (classified by contentType)`);
|
|
803
|
+
}
|
|
727
804
|
if (hasFilters && chartSlugs.length === 0) {
|
|
728
805
|
globalState_1.default.log(styles.warning(`No charts filters provided, skipping`));
|
|
729
806
|
}
|
|
730
807
|
else {
|
|
731
|
-
const { changes: chartChanges, total } = await upsertResources('charts', projectId, changes, options.force, chartSlugs, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency);
|
|
808
|
+
const { changes: chartChanges, total } = await upsertResources('charts', projectId, changes, options.force, chartSlugs, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency, looseFiles.charts);
|
|
732
809
|
changes = chartChanges;
|
|
733
810
|
chartTotal = total;
|
|
734
811
|
}
|
|
@@ -736,7 +813,7 @@ const uploadHandler = async (options) => {
|
|
|
736
813
|
globalState_1.default.log(styles.warning(`No dashboard filters provided, skipping`));
|
|
737
814
|
}
|
|
738
815
|
else {
|
|
739
|
-
const { changes: dashboardChanges, total } = await upsertResources('dashboards', projectId, changes, options.force, options.dashboards, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency);
|
|
816
|
+
const { changes: dashboardChanges, total } = await upsertResources('dashboards', projectId, changes, options.force, options.dashboards, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency, looseFiles.dashboards);
|
|
740
817
|
changes = dashboardChanges;
|
|
741
818
|
dashboardTotal = total;
|
|
742
819
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2661.1",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"unique-names-generator": "^4.7.1",
|
|
41
41
|
"uuid": "^11.0.3",
|
|
42
42
|
"yaml": "^2.7.0",
|
|
43
|
-
"@lightdash/common": "0.
|
|
44
|
-
"@lightdash/warehouses": "0.
|
|
43
|
+
"@lightdash/common": "0.2661.1",
|
|
44
|
+
"@lightdash/warehouses": "0.2661.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/inquirer": "^8.2.1",
|