@lightdash/cli 2.204.0 → 2.205.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.
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type ContentSlugRenameRequest } from '@lightdash/common';
|
|
1
|
+
import { ContentType, type ContentSlugRenameRequest } from '@lightdash/common';
|
|
2
2
|
type SlugUpdateOptions = {
|
|
3
3
|
verbose: boolean;
|
|
4
4
|
project?: string;
|
|
5
5
|
path?: string;
|
|
6
6
|
dryRun: boolean;
|
|
7
|
+
type: ContentType.CHART | ContentType.DASHBOARD;
|
|
7
8
|
from: string;
|
|
8
9
|
to: string;
|
|
9
10
|
};
|
|
@@ -18,7 +19,8 @@ type LocalSlugUpdatePlan = {
|
|
|
18
19
|
}>;
|
|
19
20
|
metadata: {
|
|
20
21
|
root: string;
|
|
21
|
-
|
|
22
|
+
resourceType: ContentType.CHART | ContentType.DASHBOARD;
|
|
23
|
+
slugs: Record<string, string>;
|
|
22
24
|
} | undefined;
|
|
23
25
|
referencesUpdated: number;
|
|
24
26
|
};
|
|
@@ -26,11 +28,11 @@ export type LocalSlugUpdateFileChange = {
|
|
|
26
28
|
source: string;
|
|
27
29
|
target?: string;
|
|
28
30
|
};
|
|
29
|
-
export declare const
|
|
31
|
+
export declare const planLocalSlugUpdate: (customPath: string | undefined, from: string, to: string, resourceType?: ContentType.CHART | ContentType.DASHBOARD) => Promise<LocalSlugUpdatePlan>;
|
|
30
32
|
export declare const getLocalSlugUpdateFileChanges: (plan: LocalSlugUpdatePlan, customPath: string | undefined) => LocalSlugUpdateFileChange[];
|
|
31
|
-
export declare const
|
|
33
|
+
export declare const applyLocalSlugUpdate: (plan: LocalSlugUpdatePlan) => Promise<() => Promise<void>>;
|
|
32
34
|
export declare const requestSlugUpdate: (projectUuid: string, request: ContentSlugRenameRequest) => Promise<void>;
|
|
33
|
-
export declare const
|
|
35
|
+
export declare const executeSlugUpdate: (projectUuid: string, customPath: string | undefined, from: string, to: string, resourceType?: ContentType.CHART | ContentType.DASHBOARD) => Promise<LocalSlugUpdatePlan>;
|
|
34
36
|
export declare const slugUpdateHandler: (options: SlugUpdateOptions) => Promise<void>;
|
|
35
37
|
export {};
|
|
36
38
|
//# sourceMappingURL=slugUpdate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slugUpdate.d.ts","sourceRoot":"","sources":["../../src/handlers/slugUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"slugUpdate.d.ts","sourceRoot":"","sources":["../../src/handlers/slugUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,WAAW,EAIX,KAAK,wBAAwB,EAChC,MAAM,mBAAmB,CAAC;AAY3B,KAAK,iBAAiB,GAAG;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACvB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,SAAS,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,QAAQ,EACF;QACI,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC;QACxD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,GACD,SAAS,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAIF,MAAM,MAAM,yBAAyB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAwDF,eAAO,MAAM,mBAAmB,eAChB,MAAM,GAAG,SAAS,QACxB,MAAM,MACR,MAAM,iBACI,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,SAAS,KACxD,OAAO,CAAC,mBAAmB,CAyM7B,CAAC;AAEF,eAAO,MAAM,6BAA6B,SAChC,mBAAmB,cACb,MAAM,GAAG,SAAS,KAC/B,yBAAyB,EAuB3B,CAAC;AAuDF,eAAO,MAAM,oBAAoB,SACvB,mBAAmB,KAC1B,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAuD7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,gBACb,MAAM,WACV,wBAAwB,KAClC,OAAO,CAAC,IAAI,CAmBd,CAAC;AAEF,eAAO,MAAM,iBAAiB,gBACb,MAAM,cACP,MAAM,GAAG,SAAS,QACxB,MAAM,MACR,MAAM,iBACI,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,SAAS,KACxD,OAAO,CAAC,mBAAmB,CAuB7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,YACjB,iBAAiB,KAC3B,OAAO,CAAC,IAAI,CAwDd,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.slugUpdateHandler = exports.
|
|
3
|
+
exports.slugUpdateHandler = exports.executeSlugUpdate = exports.requestSlugUpdate = exports.applyLocalSlugUpdate = exports.getLocalSlugUpdateFileChanges = exports.planLocalSlugUpdate = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@lightdash/common");
|
|
6
6
|
const fs_1 = require("fs");
|
|
@@ -56,11 +56,12 @@ const assertMovesAreSafe = async (moves) => {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
|
-
const
|
|
59
|
+
const planLocalSlugUpdate = async (customPath, from, to, resourceType = common_1.ContentType.CHART) => {
|
|
60
60
|
const root = (0, contentAsCodePaths_1.getDownloadFolder)(customPath);
|
|
61
61
|
const fileUpdates = [];
|
|
62
62
|
const fileMoves = [];
|
|
63
63
|
let referencesUpdated = 0;
|
|
64
|
+
const languageMapKey = resourceType === common_1.ContentType.CHART ? 'chart' : 'dashboard';
|
|
64
65
|
if (!from || from.length > 255) {
|
|
65
66
|
throw new common_1.ParameterError('The source slug must contain between 1 and 255 characters');
|
|
66
67
|
}
|
|
@@ -88,7 +89,10 @@ const planLocalChartSlugUpdate = async (customPath, from, to) => {
|
|
|
88
89
|
const parsed = document.toJS();
|
|
89
90
|
const isChart = parsed.contentType === common_1.ContentType.CHART ||
|
|
90
91
|
parsed.metricQuery !== undefined;
|
|
91
|
-
|
|
92
|
+
const isDashboard = parsed.contentType === common_1.ContentType.DASHBOARD ||
|
|
93
|
+
(parsed.contentType === undefined && Array.isArray(parsed.tiles));
|
|
94
|
+
const isTargetContent = resourceType === common_1.ContentType.CHART ? isChart : isDashboard;
|
|
95
|
+
if (isTargetContent && parsed.slug === from) {
|
|
92
96
|
document.set('slug', to);
|
|
93
97
|
referencesUpdated += 1;
|
|
94
98
|
changed = true;
|
|
@@ -112,16 +116,21 @@ const planLocalChartSlugUpdate = async (customPath, from, to) => {
|
|
|
112
116
|
.join('; ')}`);
|
|
113
117
|
}
|
|
114
118
|
const languageMapEntry = languageMapDocument.getIn([
|
|
115
|
-
|
|
119
|
+
languageMapKey,
|
|
116
120
|
from,
|
|
117
121
|
]);
|
|
118
122
|
if (languageMapEntry !== undefined) {
|
|
119
|
-
if (languageMapDocument.getIn([
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
if (languageMapDocument.getIn([
|
|
124
|
+
languageMapKey,
|
|
125
|
+
to,
|
|
126
|
+
]) !== undefined) {
|
|
127
|
+
throw new common_1.ParameterError(`Cannot update language map because ${resourceType} slug "${to}" already exists in "${sourceLanguageMap}"`);
|
|
122
128
|
}
|
|
123
|
-
languageMapDocument.setIn([
|
|
124
|
-
languageMapDocument.deleteIn([
|
|
129
|
+
languageMapDocument.setIn([languageMapKey, to], languageMapEntry);
|
|
130
|
+
languageMapDocument.deleteIn([
|
|
131
|
+
languageMapKey,
|
|
132
|
+
from,
|
|
133
|
+
]);
|
|
125
134
|
fileUpdates.push({
|
|
126
135
|
filePath: sourceLanguageMap,
|
|
127
136
|
content: languageMapDocument.toString(),
|
|
@@ -141,7 +150,7 @@ const planLocalChartSlugUpdate = async (customPath, from, to) => {
|
|
|
141
150
|
}
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
|
-
if (Array.isArray(parsed.tiles)) {
|
|
153
|
+
if (resourceType === common_1.ContentType.CHART && Array.isArray(parsed.tiles)) {
|
|
145
154
|
for (const [index, tile] of parsed.tiles.entries()) {
|
|
146
155
|
if (tile.type === 'saved_chart' &&
|
|
147
156
|
tile.properties?.chartSlug === from) {
|
|
@@ -151,7 +160,7 @@ const planLocalChartSlugUpdate = async (customPath, from, to) => {
|
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
}
|
|
154
|
-
if (parsed.resource?.type ===
|
|
163
|
+
if (parsed.resource?.type === resourceType &&
|
|
155
164
|
parsed.resource.slug === from) {
|
|
156
165
|
document.setIn(['resource', 'slug'], to);
|
|
157
166
|
referencesUpdated += 1;
|
|
@@ -163,14 +172,17 @@ const planLocalChartSlugUpdate = async (customPath, from, to) => {
|
|
|
163
172
|
}
|
|
164
173
|
const metadata = await (0, metadataFile_1.readMetadataFile)(root);
|
|
165
174
|
let updatedMetadata;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
const metadataSlugs = resourceType === common_1.ContentType.CHART
|
|
176
|
+
? metadata.charts
|
|
177
|
+
: metadata.dashboards;
|
|
178
|
+
if (from in metadataSlugs) {
|
|
179
|
+
if (to !== from && to in metadataSlugs) {
|
|
180
|
+
throw new common_1.ParameterError(`Cannot update local metadata because ${resourceType} slug "${to}" already exists`);
|
|
169
181
|
}
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
delete
|
|
173
|
-
updatedMetadata = { root,
|
|
182
|
+
const slugs = { ...metadataSlugs };
|
|
183
|
+
slugs[to] = slugs[from];
|
|
184
|
+
delete slugs[from];
|
|
185
|
+
updatedMetadata = { root, resourceType, slugs };
|
|
174
186
|
}
|
|
175
187
|
await assertMovesAreSafe(fileMoves);
|
|
176
188
|
return {
|
|
@@ -180,7 +192,7 @@ const planLocalChartSlugUpdate = async (customPath, from, to) => {
|
|
|
180
192
|
referencesUpdated,
|
|
181
193
|
};
|
|
182
194
|
};
|
|
183
|
-
exports.
|
|
195
|
+
exports.planLocalSlugUpdate = planLocalSlugUpdate;
|
|
184
196
|
const getLocalSlugUpdateFileChanges = (plan, customPath) => {
|
|
185
197
|
const root = (0, contentAsCodePaths_1.getDownloadFolder)(customPath);
|
|
186
198
|
const movedSources = new Set(plan.fileMoves.map(({ source }) => source));
|
|
@@ -235,7 +247,7 @@ const restoreFiles = async (snapshots) => {
|
|
|
235
247
|
? fs_1.promises.rm(filePath, { force: true })
|
|
236
248
|
: fs_1.promises.writeFile(filePath, content)));
|
|
237
249
|
};
|
|
238
|
-
const
|
|
250
|
+
const applyLocalSlugUpdate = async (plan) => {
|
|
239
251
|
const metadataPath = plan.metadata
|
|
240
252
|
? path.join(plan.metadata.root, metadataFile_1.METADATA_FILENAME)
|
|
241
253
|
: undefined;
|
|
@@ -260,7 +272,12 @@ const applyLocalChartSlugUpdate = async (plan) => {
|
|
|
260
272
|
}
|
|
261
273
|
if (plan.metadata && metadataPath) {
|
|
262
274
|
const existing = await (0, metadataFile_1.readMetadataFile)(plan.metadata.root);
|
|
263
|
-
await fs_1.promises.writeFile(metadataPath, JSON.stringify({
|
|
275
|
+
await fs_1.promises.writeFile(metadataPath, JSON.stringify({
|
|
276
|
+
...existing,
|
|
277
|
+
[plan.metadata.resourceType === common_1.ContentType.CHART
|
|
278
|
+
? 'charts'
|
|
279
|
+
: 'dashboards']: plan.metadata.slugs,
|
|
280
|
+
}, null, 2));
|
|
264
281
|
}
|
|
265
282
|
}
|
|
266
283
|
catch (error) {
|
|
@@ -270,7 +287,7 @@ const applyLocalChartSlugUpdate = async (plan) => {
|
|
|
270
287
|
}
|
|
271
288
|
return () => restoreFiles(snapshots);
|
|
272
289
|
};
|
|
273
|
-
exports.
|
|
290
|
+
exports.applyLocalSlugUpdate = applyLocalSlugUpdate;
|
|
274
291
|
const requestSlugUpdate = async (projectUuid, request) => {
|
|
275
292
|
try {
|
|
276
293
|
await (0, apiClient_1.lightdashApi)({
|
|
@@ -289,22 +306,22 @@ const requestSlugUpdate = async (projectUuid, request) => {
|
|
|
289
306
|
}
|
|
290
307
|
};
|
|
291
308
|
exports.requestSlugUpdate = requestSlugUpdate;
|
|
292
|
-
const
|
|
293
|
-
const localPlan = await (0, exports.
|
|
309
|
+
const executeSlugUpdate = async (projectUuid, customPath, from, to, resourceType = common_1.ContentType.CHART) => {
|
|
310
|
+
const localPlan = await (0, exports.planLocalSlugUpdate)(customPath, from, to, resourceType);
|
|
294
311
|
await (0, exports.requestSlugUpdate)(projectUuid, {
|
|
295
|
-
resourceType
|
|
312
|
+
resourceType,
|
|
296
313
|
from,
|
|
297
314
|
to,
|
|
298
315
|
});
|
|
299
316
|
try {
|
|
300
|
-
await (0, exports.
|
|
317
|
+
await (0, exports.applyLocalSlugUpdate)(localPlan);
|
|
301
318
|
}
|
|
302
319
|
catch (error) {
|
|
303
|
-
throw new Error(
|
|
320
|
+
throw new Error(`The ${resourceType} slug was renamed in Lightdash, but the local files could not be updated. Fix the local file error and rerun the same command; the rename is idempotent.`, { cause: error });
|
|
304
321
|
}
|
|
305
322
|
return localPlan;
|
|
306
323
|
};
|
|
307
|
-
exports.
|
|
324
|
+
exports.executeSlugUpdate = executeSlugUpdate;
|
|
308
325
|
const slugUpdateHandler = async (options) => {
|
|
309
326
|
globalState_1.default.setVerbose(options.verbose);
|
|
310
327
|
await (0, apiClient_1.checkLightdashVersion)();
|
|
@@ -317,17 +334,17 @@ const slugUpdateHandler = async (options) => {
|
|
|
317
334
|
throw new common_1.ParameterError('No project selected. Run lightdash config set-project');
|
|
318
335
|
}
|
|
319
336
|
(0, selectProject_1.logSelectedProject)(selection, config, options.dryRun
|
|
320
|
-
?
|
|
321
|
-
:
|
|
337
|
+
? `Previewing ${options.type} slug update in`
|
|
338
|
+
: `Updating ${options.type} slug in`);
|
|
322
339
|
if (options.dryRun) {
|
|
323
|
-
const localPlan = await (0, exports.
|
|
324
|
-
console.info(styles.success(`Would update
|
|
340
|
+
const localPlan = await (0, exports.planLocalSlugUpdate)(options.path, options.from, options.to, options.type);
|
|
341
|
+
console.info(styles.success(`Would update ${options.type} slug "${options.from}" -> "${options.to}".`));
|
|
325
342
|
logLocalSlugUpdateFileChanges(localPlan, options.path, true);
|
|
326
343
|
console.info(styles.secondary('No changes were made.'));
|
|
327
344
|
return;
|
|
328
345
|
}
|
|
329
|
-
const localPlan = await (0, exports.
|
|
330
|
-
console.info(styles.success(`Updated
|
|
346
|
+
const localPlan = await (0, exports.executeSlugUpdate)(selection.projectUuid, options.path, options.from, options.to, options.type);
|
|
347
|
+
console.info(styles.success(`Updated ${options.type} slug "${options.from}" -> "${options.to}".`));
|
|
331
348
|
logLocalSlugUpdateFileChanges(localPlan, options.path, false);
|
|
332
349
|
};
|
|
333
350
|
exports.slugUpdateHandler = slugUpdateHandler;
|
|
@@ -22,6 +22,63 @@ const createTemporaryContent = async () => {
|
|
|
22
22
|
.splice(0)
|
|
23
23
|
.map((directory) => fs_1.promises.rm(directory, { recursive: true, force: true })));
|
|
24
24
|
});
|
|
25
|
+
(0, vitest_1.describe)('local dashboard slug updates', () => {
|
|
26
|
+
(0, vitest_1.test)('updates only dashboard identity, language maps, deliveries, and dashboard metadata', async () => {
|
|
27
|
+
const root = await createTemporaryContent();
|
|
28
|
+
const dashboardFile = path.join(root, 'dashboards', 'old-overview.yml');
|
|
29
|
+
const mapFile = path.join(root, 'dashboards', 'old-overview.language.map.yml');
|
|
30
|
+
const chartFile = path.join(root, 'charts', 'old-overview.yml');
|
|
31
|
+
const chartSource = 'contentType: chart\nslug: old-overview\nmetricQuery: {}\n';
|
|
32
|
+
const alertFile = path.join(root, 'alerts', 'delivery.yml');
|
|
33
|
+
await Promise.all([
|
|
34
|
+
fs_1.promises.writeFile(dashboardFile, 'contentType: dashboard\nslug: old-overview\ntiles:\n - type: saved_chart\n properties:\n chartSlug: old-overview\n'),
|
|
35
|
+
fs_1.promises.writeFile(mapFile, 'dashboard:\n old-overview:\n name: Overview\n'),
|
|
36
|
+
fs_1.promises.writeFile(chartFile, chartSource),
|
|
37
|
+
fs_1.promises.writeFile(alertFile, 'resource:\n type: dashboard\n slug: old-overview\n'),
|
|
38
|
+
fs_1.promises.writeFile(path.join(root, '.lightdash-metadata.json'), JSON.stringify({
|
|
39
|
+
version: 1,
|
|
40
|
+
charts: { 'old-overview': 'chart-timestamp' },
|
|
41
|
+
dashboards: { 'old-overview': 'dashboard-timestamp' },
|
|
42
|
+
})),
|
|
43
|
+
]);
|
|
44
|
+
const plan = await (0, slugUpdate_1.planLocalSlugUpdate)(root, 'old-overview', 'overview', common_1.ContentType.DASHBOARD);
|
|
45
|
+
(0, vitest_1.expect)(await fs_1.promises.readFile(dashboardFile, 'utf8')).toContain('slug: old-overview');
|
|
46
|
+
await (0, slugUpdate_1.applyLocalSlugUpdate)(plan);
|
|
47
|
+
const dashboard = (0, yaml_1.parse)(await fs_1.promises.readFile(path.join(root, 'dashboards', 'overview.yml'), 'utf8'));
|
|
48
|
+
(0, vitest_1.expect)(dashboard.slug).toBe('overview');
|
|
49
|
+
(0, vitest_1.expect)(dashboard.tiles[0].properties.chartSlug).toBe('old-overview');
|
|
50
|
+
(0, vitest_1.expect)((0, yaml_1.parse)(await fs_1.promises.readFile(path.join(root, 'dashboards', 'overview.language.map.yml'), 'utf8'))).toEqual({
|
|
51
|
+
dashboard: { overview: { name: 'Overview' } },
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.expect)((0, yaml_1.parse)(await fs_1.promises.readFile(alertFile, 'utf8')).resource.slug).toBe('overview');
|
|
54
|
+
(0, vitest_1.expect)(await fs_1.promises.readFile(chartFile, 'utf8')).toBe(chartSource);
|
|
55
|
+
(0, vitest_1.expect)(JSON.parse(await fs_1.promises.readFile(path.join(root, '.lightdash-metadata.json'), 'utf8'))).toEqual({
|
|
56
|
+
version: 1,
|
|
57
|
+
charts: { 'old-overview': 'chart-timestamp' },
|
|
58
|
+
dashboards: { overview: 'dashboard-timestamp' },
|
|
59
|
+
});
|
|
60
|
+
const replay = await (0, slugUpdate_1.planLocalSlugUpdate)(root, 'old-overview', 'overview', common_1.ContentType.DASHBOARD);
|
|
61
|
+
(0, vitest_1.expect)((0, slugUpdate_1.getLocalSlugUpdateFileChanges)(replay, root)).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.test)('sends the dashboard type before changing local files', async () => {
|
|
64
|
+
const root = await createTemporaryContent();
|
|
65
|
+
const dashboardFile = path.join(root, 'dashboards', 'old-overview.yml');
|
|
66
|
+
const source = 'slug: old-overview\ntiles: []\n';
|
|
67
|
+
await fs_1.promises.writeFile(dashboardFile, source);
|
|
68
|
+
const api = vitest_1.vi
|
|
69
|
+
.spyOn(apiClient, 'lightdashApi')
|
|
70
|
+
.mockImplementation(async () => {
|
|
71
|
+
(0, vitest_1.expect)(await fs_1.promises.readFile(dashboardFile, 'utf8')).toBe(source);
|
|
72
|
+
return undefined;
|
|
73
|
+
});
|
|
74
|
+
await (0, slugUpdate_1.executeSlugUpdate)('project-uuid', root, 'old-overview', 'overview', common_1.ContentType.DASHBOARD);
|
|
75
|
+
(0, vitest_1.expect)(JSON.parse(api.mock.calls[0][0].body)).toEqual({
|
|
76
|
+
resourceType: common_1.ContentType.DASHBOARD,
|
|
77
|
+
from: 'old-overview',
|
|
78
|
+
to: 'overview',
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
25
82
|
(0, vitest_1.describe)('local chart slug updates', () => {
|
|
26
83
|
(0, vitest_1.test)('updates chart files, language maps, references, filenames, and metadata', async () => {
|
|
27
84
|
const root = await createTemporaryContent();
|
|
@@ -55,7 +112,7 @@ const createTemporaryContent = async () => {
|
|
|
55
112
|
dashboards: { overview: 'dashboard-timestamp' },
|
|
56
113
|
})),
|
|
57
114
|
]);
|
|
58
|
-
const plan = await (0, slugUpdate_1.
|
|
115
|
+
const plan = await (0, slugUpdate_1.planLocalSlugUpdate)(root, 'copy-of-orders', 'orders');
|
|
59
116
|
(0, vitest_1.expect)(plan.referencesUpdated).toBe(4);
|
|
60
117
|
(0, vitest_1.expect)(plan.fileMoves).toHaveLength(2);
|
|
61
118
|
(0, vitest_1.expect)((0, slugUpdate_1.getLocalSlugUpdateFileChanges)(plan, root)).toEqual([
|
|
@@ -71,7 +128,7 @@ const createTemporaryContent = async () => {
|
|
|
71
128
|
},
|
|
72
129
|
{ source: path.join('dashboards', 'overview.yml') },
|
|
73
130
|
]);
|
|
74
|
-
await (0, slugUpdate_1.
|
|
131
|
+
await (0, slugUpdate_1.applyLocalSlugUpdate)(plan);
|
|
75
132
|
await (0, vitest_1.expect)(fs_1.promises.access(chartFile)).rejects.toThrow();
|
|
76
133
|
await (0, vitest_1.expect)(fs_1.promises.access(languageMapFile)).rejects.toThrow();
|
|
77
134
|
const renamedChart = (0, yaml_1.parse)(await fs_1.promises.readFile(path.join(root, 'charts', 'orders.yml'), 'utf8'));
|
|
@@ -98,8 +155,8 @@ const createTemporaryContent = async () => {
|
|
|
98
155
|
(0, vitest_1.test)('is idempotent after local files have already been updated', async () => {
|
|
99
156
|
const root = await createTemporaryContent();
|
|
100
157
|
await fs_1.promises.writeFile(path.join(root, 'charts', 'copy-of-orders.yml'), 'contentType: chart\nslug: copy-of-orders\nname: Orders\nmetricQuery: {}\n');
|
|
101
|
-
await (0, slugUpdate_1.
|
|
102
|
-
const secondPlan = await (0, slugUpdate_1.
|
|
158
|
+
await (0, slugUpdate_1.applyLocalSlugUpdate)(await (0, slugUpdate_1.planLocalSlugUpdate)(root, 'copy-of-orders', 'orders'));
|
|
159
|
+
const secondPlan = await (0, slugUpdate_1.planLocalSlugUpdate)(root, 'copy-of-orders', 'orders');
|
|
103
160
|
(0, vitest_1.expect)(secondPlan).toEqual({
|
|
104
161
|
fileUpdates: [],
|
|
105
162
|
fileMoves: [],
|
|
@@ -113,7 +170,7 @@ const createTemporaryContent = async () => {
|
|
|
113
170
|
const source = 'contentType: chart\nslug: copy-of-orders\nname: Orders\nmetricQuery: {}\n';
|
|
114
171
|
await fs_1.promises.writeFile(chartFile, source);
|
|
115
172
|
const api = vitest_1.vi.spyOn(apiClient, 'lightdashApi');
|
|
116
|
-
const plan = await (0, slugUpdate_1.
|
|
173
|
+
const plan = await (0, slugUpdate_1.planLocalSlugUpdate)(root, 'copy-of-orders', 'orders');
|
|
117
174
|
(0, vitest_1.expect)((0, slugUpdate_1.getLocalSlugUpdateFileChanges)(plan, root)).toEqual([
|
|
118
175
|
{
|
|
119
176
|
source: path.join('charts', 'copy-of-orders.yml'),
|
|
@@ -131,7 +188,7 @@ const createTemporaryContent = async () => {
|
|
|
131
188
|
fs_1.promises.writeFile(sourceFile, 'contentType: chart\nslug: copy-of-orders\nname: Orders\nmetricQuery: {}\n'),
|
|
132
189
|
fs_1.promises.writeFile(path.join(root, 'charts', 'orders.yml'), 'contentType: chart\nslug: another-chart\nname: Another\nmetricQuery: {}\n'),
|
|
133
190
|
]);
|
|
134
|
-
await (0, vitest_1.expect)((0, slugUpdate_1.
|
|
191
|
+
await (0, vitest_1.expect)((0, slugUpdate_1.planLocalSlugUpdate)(root, 'copy-of-orders', 'orders')).rejects.toThrow('already exists');
|
|
135
192
|
(0, vitest_1.expect)(await fs_1.promises.readFile(sourceFile, 'utf8')).toContain('slug: copy-of-orders');
|
|
136
193
|
});
|
|
137
194
|
(0, vitest_1.test)('rejects malformed target slugs before constructing file paths', async () => {
|
|
@@ -139,7 +196,7 @@ const createTemporaryContent = async () => {
|
|
|
139
196
|
const chartFile = path.join(root, 'charts', 'copy-of-orders.yml');
|
|
140
197
|
const source = 'contentType: chart\nslug: copy-of-orders\nname: Orders\nmetricQuery: {}\n';
|
|
141
198
|
await fs_1.promises.writeFile(chartFile, source);
|
|
142
|
-
await (0, vitest_1.expect)((0, slugUpdate_1.
|
|
199
|
+
await (0, vitest_1.expect)((0, slugUpdate_1.planLocalSlugUpdate)(root, 'copy-of-orders', '../../orders')).rejects.toThrow('target slug must contain');
|
|
143
200
|
(0, vitest_1.expect)(await fs_1.promises.readFile(chartFile, 'utf8')).toBe(source);
|
|
144
201
|
});
|
|
145
202
|
(0, vitest_1.test)('does not change local files when the server rejects the rename', async () => {
|
|
@@ -153,7 +210,7 @@ const createTemporaryContent = async () => {
|
|
|
153
210
|
statusCode: 409,
|
|
154
211
|
data: {},
|
|
155
212
|
}));
|
|
156
|
-
await (0, vitest_1.expect)((0, slugUpdate_1.
|
|
213
|
+
await (0, vitest_1.expect)((0, slugUpdate_1.executeSlugUpdate)('project-uuid', root, 'copy-of-orders', 'orders')).rejects.toThrow('Slug already in use');
|
|
157
214
|
(0, vitest_1.expect)(await fs_1.promises.readFile(chartFile, 'utf8')).toBe(source);
|
|
158
215
|
await (0, vitest_1.expect)(fs_1.promises.access(path.join(root, 'charts', 'orders.yml'))).rejects.toThrow();
|
|
159
216
|
(0, vitest_1.expect)((await fs_1.promises.readdir(path.join(root, 'charts'))).some((file) => file.includes('.slug-update-'))).toBe(false);
|
|
@@ -167,7 +224,7 @@ const createTemporaryContent = async () => {
|
|
|
167
224
|
await (0, vitest_1.expect)(fs_1.promises.access(path.join(root, 'charts', 'orders.yml'))).rejects.toThrow();
|
|
168
225
|
return undefined;
|
|
169
226
|
});
|
|
170
|
-
await (0, slugUpdate_1.
|
|
227
|
+
await (0, slugUpdate_1.executeSlugUpdate)('project-uuid', root, 'copy-of-orders', 'orders');
|
|
171
228
|
await (0, vitest_1.expect)(fs_1.promises.access(chartFile)).rejects.toThrow();
|
|
172
229
|
(0, vitest_1.expect)(await fs_1.promises.readFile(path.join(root, 'charts', 'orders.yml'), 'utf8')).toContain('slug: orders');
|
|
173
230
|
});
|
package/dist/index.js
CHANGED
|
@@ -608,11 +608,14 @@ commander_1.program
|
|
|
608
608
|
.action(renameHandler_1.renameHandler);
|
|
609
609
|
commander_1.program
|
|
610
610
|
.command('slug-update')
|
|
611
|
-
.description('Rename a chart slug and update its local references')
|
|
611
|
+
.description('Rename a chart or dashboard slug and update its local references')
|
|
612
|
+
.addOption(new commander_1.Option('--type <resource-type>', 'content type to rename')
|
|
613
|
+
.choices(['chart', 'dashboard'])
|
|
614
|
+
.default('chart'))
|
|
612
615
|
.option('--verbose', 'show verbose output', false)
|
|
613
616
|
.option('--project <project uuid or slug>', 'specify a project by UUID or slug', parseProjectArgument, undefined)
|
|
614
617
|
.option('-p, --path <path>', 'specify the local content-as-code path to update', undefined)
|
|
615
|
-
.option('--dry-run', 'preview the
|
|
618
|
+
.option('--dry-run', 'preview the rename and local file changes without making changes', false)
|
|
616
619
|
.requiredOption('--from <slug>', 'current content slug')
|
|
617
620
|
.requiredOption('--to <slug>', 'new content slug')
|
|
618
621
|
.action(slugUpdate_1.slugUpdateHandler);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.205.0",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"@actions/core": "1.11.1",
|
|
23
23
|
"@aws-sdk/credential-providers": "3.995.0",
|
|
24
24
|
"@casl/ability": "6.8.0",
|
|
25
|
-
"@lightdash/common": "2.
|
|
26
|
-
"@lightdash/warehouses": "2.
|
|
25
|
+
"@lightdash/common": "2.205.0",
|
|
26
|
+
"@lightdash/warehouses": "2.205.0",
|
|
27
27
|
"@sidvind/better-ajv-errors": "7.0.0",
|
|
28
28
|
"@types/columnify": "1.5.1",
|
|
29
29
|
"ajv": "8.18.0",
|