@lightdash/cli 1.34.0 → 1.35.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/.tsbuildinfo +1 -1
- package/dist/handlers/apps/appCodeFiles.d.ts +4 -8
- package/dist/handlers/apps/appCodeFiles.d.ts.map +1 -1
- package/dist/handlers/apps/appCodeFiles.js +20 -8
- package/dist/handlers/apps/appCodeFiles.test.js +37 -17
- package/dist/handlers/apps/appsDownload.d.ts +36 -26
- package/dist/handlers/apps/appsDownload.d.ts.map +1 -1
- package/dist/handlers/apps/appsDownload.js +53 -33
- package/dist/handlers/apps/appsDownload.test.js +126 -36
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +50 -83
- package/dist/handlers/download.test.js +30 -0
- package/dist/index.js +3 -3
- package/dist/terminal/contentAsCodeOutput.d.ts +1 -0
- package/dist/terminal/contentAsCodeOutput.d.ts.map +1 -1
- package/dist/terminal/contentAsCodeOutput.js +24 -0
- package/dist/terminal/contentAsCodeOutput.test.js +43 -0
- package/package.json +3 -3
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const common_1 = require("@lightdash/common");
|
|
4
4
|
const appsDownload_1 = require("./appsDownload");
|
|
5
|
-
describe('
|
|
5
|
+
describe('getDataAppReference', () => {
|
|
6
6
|
const appUuid = 'd3afc44c-6f0f-4d9f-a267-fb739efa31dd';
|
|
7
7
|
const projectUuid = 'd4c8dfd2-98c0-4eb2-8395-d924aee62611';
|
|
8
8
|
it('keeps a bare app UUID unchanged', () => {
|
|
9
|
-
expect((0, appsDownload_1.
|
|
9
|
+
expect((0, appsDownload_1.getDataAppReference)(appUuid)).toBe(appUuid);
|
|
10
|
+
});
|
|
11
|
+
it('keeps a bare slug unchanged', () => {
|
|
12
|
+
expect((0, appsDownload_1.getDataAppReference)('my-app-slug')).toBe('my-app-slug');
|
|
10
13
|
});
|
|
11
14
|
it.each([
|
|
12
15
|
`https://app.lightdash.cloud/projects/${projectUuid}/apps/${appUuid}`,
|
|
@@ -14,7 +17,7 @@ describe('getDataAppUuidFromReference', () => {
|
|
|
14
17
|
`https://app.lightdash.cloud/projects/${projectUuid}/apps/${appUuid}/versions/2/view?state=filters#preview`,
|
|
15
18
|
`https://app.lightdash.cloud/embed/${projectUuid}/app/${appUuid}`,
|
|
16
19
|
])('extracts the app UUID from %s', (url) => {
|
|
17
|
-
expect((0, appsDownload_1.
|
|
20
|
+
expect((0, appsDownload_1.getDataAppReference)(url)).toBe(appUuid);
|
|
18
21
|
});
|
|
19
22
|
it.each([
|
|
20
23
|
'my-app',
|
|
@@ -22,7 +25,49 @@ describe('getDataAppUuidFromReference', () => {
|
|
|
22
25
|
`https://app.lightdash.cloud/projects/${projectUuid}/apps/generate`,
|
|
23
26
|
`ftp://app.lightdash.cloud/projects/${projectUuid}/apps/${appUuid}`,
|
|
24
27
|
])('leaves unsupported references unchanged: %s', (reference) => {
|
|
25
|
-
expect((0, appsDownload_1.
|
|
28
|
+
expect((0, appsDownload_1.getDataAppReference)(reference)).toBe(reference);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('uploadFilterMatches', () => {
|
|
32
|
+
const makeManifest = (overrides = {}) => ({
|
|
33
|
+
codeVersion: 1,
|
|
34
|
+
appUuid: 'app-uuid-1',
|
|
35
|
+
projectUuid: 'proj-uuid-1',
|
|
36
|
+
version: 3,
|
|
37
|
+
name: 'My App',
|
|
38
|
+
description: '',
|
|
39
|
+
template: null,
|
|
40
|
+
downloadedAt: '2026-06-25T00:00:00.000Z',
|
|
41
|
+
...overrides,
|
|
42
|
+
});
|
|
43
|
+
it('matches everything when the filter is null', () => {
|
|
44
|
+
expect((0, appsDownload_1.uploadFilterMatches)(null, makeManifest())).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('matches on manifest.appUuid', () => {
|
|
47
|
+
const filter = new Set(['app-uuid-1']);
|
|
48
|
+
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest())).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('matches on manifest.slug', () => {
|
|
51
|
+
const filter = new Set(['sales-app']);
|
|
52
|
+
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest({ slug: 'sales-app' }))).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
it('does not match when neither uuid nor slug is in the filter', () => {
|
|
55
|
+
const filter = new Set(['other-uuid']);
|
|
56
|
+
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest({ slug: 'sales-app' }))).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
it('matches only by uuid when the manifest has no slug', () => {
|
|
59
|
+
const filter = new Set(['app-uuid-1']);
|
|
60
|
+
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest({ slug: undefined }))).toBe(true);
|
|
61
|
+
expect((0, appsDownload_1.uploadFilterMatches)(new Set(['sales-app']), makeManifest({ slug: undefined }))).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it('matches only by slug for uuid-free manifests (slug-aware servers)', () => {
|
|
64
|
+
const manifest = makeManifest({
|
|
65
|
+
appUuid: undefined,
|
|
66
|
+
slug: 'sales-app',
|
|
67
|
+
});
|
|
68
|
+
expect((0, appsDownload_1.uploadFilterMatches)(new Set(['sales-app']), manifest)).toBe(true);
|
|
69
|
+
expect((0, appsDownload_1.uploadFilterMatches)(new Set(['app-uuid-1']), manifest)).toBe(false);
|
|
70
|
+
expect((0, appsDownload_1.uploadFilterMatches)(null, manifest)).toBe(true);
|
|
26
71
|
});
|
|
27
72
|
});
|
|
28
73
|
describe('getDataAppUploadFilter', () => {
|
|
@@ -62,20 +107,20 @@ const makeDownload = () => ({
|
|
|
62
107
|
},
|
|
63
108
|
});
|
|
64
109
|
describe('selectAppsToDownload', () => {
|
|
65
|
-
it('returns explicit
|
|
110
|
+
it('returns explicit refs without listing when refs are passed', () => {
|
|
66
111
|
expect((0, appsDownload_1.selectAppsToDownload)({ apps: ['uuid-a', 'uuid-b'] })).toEqual({
|
|
67
112
|
mode: 'explicit',
|
|
68
|
-
|
|
113
|
+
appRefs: ['uuid-a', 'uuid-b'],
|
|
69
114
|
});
|
|
70
115
|
});
|
|
71
116
|
it('lists all apps when --include-apps is passed', () => {
|
|
72
117
|
expect((0, appsDownload_1.selectAppsToDownload)({ includeApps: true })).toEqual({
|
|
73
118
|
mode: 'list-all',
|
|
74
|
-
|
|
119
|
+
extraAppRefs: [],
|
|
75
120
|
});
|
|
76
121
|
});
|
|
77
|
-
it('combines --include-apps with explicitly passed
|
|
78
|
-
expect((0, appsDownload_1.selectAppsToDownload)({ apps: ['uuid-a'], includeApps: true })).toEqual({ mode: 'list-all',
|
|
122
|
+
it('combines --include-apps with explicitly passed refs', () => {
|
|
123
|
+
expect((0, appsDownload_1.selectAppsToDownload)({ apps: ['uuid-a'], includeApps: true })).toEqual({ mode: 'list-all', extraAppRefs: ['uuid-a'] });
|
|
79
124
|
});
|
|
80
125
|
it('normalizes explicit app URLs without listing apps', () => {
|
|
81
126
|
expect((0, appsDownload_1.selectAppsToDownload)({
|
|
@@ -84,7 +129,7 @@ describe('selectAppsToDownload', () => {
|
|
|
84
129
|
],
|
|
85
130
|
})).toEqual({
|
|
86
131
|
mode: 'explicit',
|
|
87
|
-
|
|
132
|
+
appRefs: ['d3afc44c-6f0f-4d9f-a267-fb739efa31dd'],
|
|
88
133
|
});
|
|
89
134
|
});
|
|
90
135
|
it('skips apps when neither flag is passed', () => {
|
|
@@ -170,6 +215,75 @@ describe('ensureDownloadedAppContext', () => {
|
|
|
170
215
|
expect(() => (0, appsDownload_1.ensureDownloadedAppContext)('app-uuid-1', withoutContext)).toThrow(/does not support app context downloads/i);
|
|
171
216
|
});
|
|
172
217
|
});
|
|
218
|
+
describe('preSlugUploadHint', () => {
|
|
219
|
+
it('suggests adding the slug to lightdash-app.yml when the server returned one', () => {
|
|
220
|
+
const hint = (0, appsDownload_1.preSlugUploadHint)({
|
|
221
|
+
folder: 'lightdash/apps/my-app',
|
|
222
|
+
slug: 'my-app',
|
|
223
|
+
});
|
|
224
|
+
expect(hint).toContain('lightdash/apps/my-app/lightdash-app.yml');
|
|
225
|
+
expect(hint).toContain('predates slug identity');
|
|
226
|
+
expect(hint).toContain('add `slug: my-app` to lightdash-app.yml');
|
|
227
|
+
expect(hint).toContain('Uploads keep working via uuid matching');
|
|
228
|
+
});
|
|
229
|
+
it('omits the add-slug suggestion when the server did not return a slug', () => {
|
|
230
|
+
const hint = (0, appsDownload_1.preSlugUploadHint)({
|
|
231
|
+
folder: 'lightdash/apps/my-app',
|
|
232
|
+
slug: undefined,
|
|
233
|
+
});
|
|
234
|
+
expect(hint).toContain('lightdash/apps/my-app/lightdash-app.yml');
|
|
235
|
+
expect(hint).toContain('predates slug identity');
|
|
236
|
+
expect(hint).not.toContain('add `slug:');
|
|
237
|
+
expect(hint).toContain('Uploads keep working via uuid matching');
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe('matchedUploadRefs', () => {
|
|
241
|
+
const manifest = {
|
|
242
|
+
codeVersion: 1,
|
|
243
|
+
appUuid: 'app-uuid-1',
|
|
244
|
+
projectUuid: 'proj-uuid-1',
|
|
245
|
+
slug: 'sales-app',
|
|
246
|
+
version: 3,
|
|
247
|
+
name: 'My App',
|
|
248
|
+
description: '',
|
|
249
|
+
template: null,
|
|
250
|
+
downloadedAt: '2026-07-29T00:00:00.000Z',
|
|
251
|
+
};
|
|
252
|
+
it('returns the filter entries the manifest satisfies', () => {
|
|
253
|
+
expect((0, appsDownload_1.matchedUploadRefs)(new Set(['app-uuid-1', 'sales-app', 'other']), manifest)).toEqual(['app-uuid-1', 'sales-app']);
|
|
254
|
+
});
|
|
255
|
+
it('returns only the slug for uuid-free manifests', () => {
|
|
256
|
+
expect((0, appsDownload_1.matchedUploadRefs)(new Set(['app-uuid-1', 'sales-app']), {
|
|
257
|
+
...manifest,
|
|
258
|
+
appUuid: undefined,
|
|
259
|
+
})).toEqual(['sales-app']);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
describe('unmatchedUploadRefsWarning', () => {
|
|
263
|
+
it('returns null when everything matched', () => {
|
|
264
|
+
expect((0, appsDownload_1.unmatchedUploadRefsWarning)([])).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
it('lists unmatched refs', () => {
|
|
267
|
+
const warning = (0, appsDownload_1.unmatchedUploadRefsWarning)(['sales-ap']);
|
|
268
|
+
expect(warning).toContain('No local app folder matched: sales-ap.');
|
|
269
|
+
expect(warning).not.toContain('slug identity');
|
|
270
|
+
});
|
|
271
|
+
it('adds the slug-identity explanation for uuid-shaped refs', () => {
|
|
272
|
+
const warning = (0, appsDownload_1.unmatchedUploadRefsWarning)([
|
|
273
|
+
'd3afc44c-6f0f-4d9f-a267-fb739efa31dd',
|
|
274
|
+
]);
|
|
275
|
+
expect(warning).toContain('carry no uuid');
|
|
276
|
+
expect(warning).toContain('select them by slug');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
describe('preSlugServerHint', () => {
|
|
280
|
+
it('warns that the server matched by uuid only and to check for duplicates', () => {
|
|
281
|
+
const hint = (0, appsDownload_1.preSlugServerHint)('my-app');
|
|
282
|
+
expect(hint).toContain('"my-app"');
|
|
283
|
+
expect(hint).toContain('predates slug-based app identity');
|
|
284
|
+
expect(hint).toContain('verify no duplicate was created');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
173
287
|
describe('computeUpsertedTotal', () => {
|
|
174
288
|
it('sums only keys that are neither skipped nor failed', () => {
|
|
175
289
|
const changes = {
|
|
@@ -211,30 +325,6 @@ describe('shouldWarnAllSkipped', () => {
|
|
|
211
325
|
expect((0, appsDownload_1.shouldWarnAllSkipped)({})).toBe(false);
|
|
212
326
|
});
|
|
213
327
|
});
|
|
214
|
-
describe('classifyAppUpload', () => {
|
|
215
|
-
it('proceeds when createNew is true regardless of project mismatch', () => {
|
|
216
|
-
expect((0, appsDownload_1.classifyAppUpload)('proj-a', 'proj-b', true)).toBe('proceed');
|
|
217
|
-
});
|
|
218
|
-
it('proceeds when manifest project matches target project', () => {
|
|
219
|
-
expect((0, appsDownload_1.classifyAppUpload)('proj-a', 'proj-a', false)).toBe('proceed');
|
|
220
|
-
});
|
|
221
|
-
it('needs-confirmation when projects differ and createNew is false', () => {
|
|
222
|
-
expect((0, appsDownload_1.classifyAppUpload)('proj-a', 'proj-b', false)).toBe('needs-confirmation');
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
describe('manifestRetargetHint', () => {
|
|
226
|
-
it('names both uuids, the consequence, and the manual fix', () => {
|
|
227
|
-
const hint = (0, appsDownload_1.manifestRetargetHint)({
|
|
228
|
-
folder: 'my-app',
|
|
229
|
-
appUuid: 'new-app-uuid',
|
|
230
|
-
projectUuid: 'target-proj-uuid',
|
|
231
|
-
});
|
|
232
|
-
expect(hint).toContain('my-app/lightdash-app.yml');
|
|
233
|
-
expect(hint).toContain('appUuid: new-app-uuid');
|
|
234
|
-
expect(hint).toContain('projectUuid: target-proj-uuid');
|
|
235
|
-
expect(hint).toMatch(/ask to create again/i);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
328
|
describe('classifyAppDownloadError', () => {
|
|
239
329
|
const make404 = (message) => new common_1.LightdashError({
|
|
240
330
|
message,
|
|
@@ -287,7 +377,7 @@ describe('appsDownloadSummary', () => {
|
|
|
287
377
|
expect(summary.failureLines).toEqual([]);
|
|
288
378
|
});
|
|
289
379
|
it('reports a warning with per-app reasons when any app failed', () => {
|
|
290
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(0, 1, [{
|
|
380
|
+
const summary = (0, appsDownload_1.appsDownloadSummary)(0, 1, [{ appRef: 'uuid-a', message: 'server exploded' }], '/tmp/x/apps', 0);
|
|
291
381
|
expect(summary.ok).toBe(false);
|
|
292
382
|
expect(summary.message).toContain('Downloaded 0 of 1 data app(s)');
|
|
293
383
|
expect(summary.message).toContain('1 failed');
|
|
@@ -303,7 +393,7 @@ describe('appsDownloadSummary', () => {
|
|
|
303
393
|
expect(summary.failureLines).toEqual([]);
|
|
304
394
|
});
|
|
305
395
|
it('reports skips and failures together', () => {
|
|
306
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(1, 3, [{
|
|
396
|
+
const summary = (0, appsDownload_1.appsDownloadSummary)(1, 3, [{ appRef: 'uuid-a', message: 'server exploded' }], '/tmp/x/apps', 1);
|
|
307
397
|
expect(summary.ok).toBe(false);
|
|
308
398
|
expect(summary.message).toContain('Downloaded 1 of 2 data app(s)');
|
|
309
399
|
expect(summary.message).toContain('1 skipped: no built version');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,WAAW,EAyBX,WAAW,EAGX,eAAe,EACf,wBAAwB,EAexB,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAC;AAY3B,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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"}
|
|
@@ -1082,10 +1082,10 @@ const downloadHandler = async (options) => {
|
|
|
1082
1082
|
});
|
|
1083
1083
|
if (appsSelection.mode !== 'none') {
|
|
1084
1084
|
output.startItem('Data apps');
|
|
1085
|
-
let
|
|
1085
|
+
let appRefsToDownload;
|
|
1086
1086
|
let appListingError = null;
|
|
1087
1087
|
if (appsSelection.mode === 'explicit') {
|
|
1088
|
-
|
|
1088
|
+
appRefsToDownload = appsSelection.appRefs;
|
|
1089
1089
|
}
|
|
1090
1090
|
else {
|
|
1091
1091
|
// List every app in the project (includes apps not in any space)
|
|
@@ -1117,14 +1117,14 @@ const downloadHandler = async (options) => {
|
|
|
1117
1117
|
if (truncatedCount > 0) {
|
|
1118
1118
|
globalState_1.default.log(styles.warning(`Found ${listedAppUuids.length} data apps, downloading the first ${appsLimit}. Pass --apps-limit <n> to raise the cap.`));
|
|
1119
1119
|
}
|
|
1120
|
-
|
|
1120
|
+
appRefsToDownload = [
|
|
1121
1121
|
...new Set([
|
|
1122
1122
|
...cappedAppUuids,
|
|
1123
|
-
...appsSelection.
|
|
1123
|
+
...appsSelection.extraAppRefs,
|
|
1124
1124
|
]),
|
|
1125
1125
|
];
|
|
1126
1126
|
}
|
|
1127
|
-
if (
|
|
1127
|
+
if (appRefsToDownload.length === 0) {
|
|
1128
1128
|
if (appListingError === null) {
|
|
1129
1129
|
output.completeItem('0 found');
|
|
1130
1130
|
}
|
|
@@ -1133,22 +1133,22 @@ const downloadHandler = async (options) => {
|
|
|
1133
1133
|
}
|
|
1134
1134
|
}
|
|
1135
1135
|
else {
|
|
1136
|
-
output.updateActive(`0 of ${
|
|
1136
|
+
output.updateActive(`0 of ${appRefsToDownload.length} downloaded`);
|
|
1137
1137
|
const baseDir = (0, contentAsCodePaths_1.getDownloadFolder)(options.path);
|
|
1138
1138
|
const appsDir = path.join(baseDir, 'apps');
|
|
1139
1139
|
const takenFolders = new Set();
|
|
1140
1140
|
let appSuccessCount = 0;
|
|
1141
1141
|
let appSkippedNotBuiltCount = 0;
|
|
1142
1142
|
const appFailures = [];
|
|
1143
|
-
for (const
|
|
1143
|
+
for (const appRef of appRefsToDownload) {
|
|
1144
1144
|
try {
|
|
1145
1145
|
// eslint-disable-next-line no-await-in-loop
|
|
1146
|
-
const code = (0, appsDownload_1.ensureDownloadedAppContext)(
|
|
1146
|
+
const code = (0, appsDownload_1.ensureDownloadedAppContext)(appRef, await (0, apiClient_1.lightdashApi)({
|
|
1147
1147
|
method: 'GET',
|
|
1148
|
-
url: `/api/v1/ee/projects/${projectId}/apps/${
|
|
1148
|
+
url: `/api/v1/ee/projects/${projectId}/apps/${appRef}/download`,
|
|
1149
1149
|
body: undefined,
|
|
1150
1150
|
}));
|
|
1151
|
-
const folder = (0, appCodeFiles_1.
|
|
1151
|
+
const folder = (0, appCodeFiles_1.resolveAppFolderName)(code.manifest, takenFolders);
|
|
1152
1152
|
takenFolders.add(folder);
|
|
1153
1153
|
const appDir = path.join(appsDir, folder);
|
|
1154
1154
|
const manifest = {
|
|
@@ -1176,21 +1176,21 @@ const downloadHandler = async (options) => {
|
|
|
1176
1176
|
const outcome = (0, appsDownload_1.classifyAppDownloadError)(appErr);
|
|
1177
1177
|
if (outcome.kind === 'skip-not-built') {
|
|
1178
1178
|
appSkippedNotBuiltCount += 1;
|
|
1179
|
-
globalState_1.default.debug(`> Skipped app ${
|
|
1179
|
+
globalState_1.default.debug(`> Skipped app ${appRef}: no built version to download`);
|
|
1180
1180
|
}
|
|
1181
1181
|
else {
|
|
1182
1182
|
appFailures.push({
|
|
1183
|
-
|
|
1183
|
+
appRef,
|
|
1184
1184
|
message: outcome.message,
|
|
1185
1185
|
});
|
|
1186
|
-
globalState_1.default.log(styles.error(`Failed to download app ${
|
|
1186
|
+
globalState_1.default.log(styles.error(`Failed to download app ${appRef}: ${outcome.message}`));
|
|
1187
1187
|
}
|
|
1188
1188
|
}
|
|
1189
1189
|
output.updateActive(`${appSuccessCount +
|
|
1190
1190
|
appSkippedNotBuiltCount +
|
|
1191
|
-
appFailures.length} of ${
|
|
1191
|
+
appFailures.length} of ${appRefsToDownload.length} processed`);
|
|
1192
1192
|
}
|
|
1193
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(appSuccessCount,
|
|
1193
|
+
const summary = (0, appsDownload_1.appsDownloadSummary)(appSuccessCount, appRefsToDownload.length, appFailures, appsDir, appSkippedNotBuiltCount);
|
|
1194
1194
|
output.completeItem(`${appSuccessCount} downloaded${appSkippedNotBuiltCount > 0
|
|
1195
1195
|
? `, ${appSkippedNotBuiltCount} skipped`
|
|
1196
1196
|
: ''}${appFailures.length > 0
|
|
@@ -1769,8 +1769,8 @@ const uploadHandler = async (options) => {
|
|
|
1769
1769
|
else if (shouldUploadApps) {
|
|
1770
1770
|
output.startItem('Data apps');
|
|
1771
1771
|
// --include-apps uploads every folder on disk; explicit references
|
|
1772
|
-
// filter folders by their manifest appUuid
|
|
1773
|
-
const
|
|
1772
|
+
// filter folders by their manifest slug or appUuid
|
|
1773
|
+
const uploadFilter = (0, appsDownload_1.getDataAppUploadFilter)(explicitAppReferences, options.includeApps === true);
|
|
1774
1774
|
const baseDir = (0, contentAsCodePaths_1.getDownloadFolder)(options.path);
|
|
1775
1775
|
const appsDir = path.join(baseDir, 'apps');
|
|
1776
1776
|
let appFolderEntries;
|
|
@@ -1792,43 +1792,19 @@ const uploadHandler = async (options) => {
|
|
|
1792
1792
|
if (subDirs.length === 0) {
|
|
1793
1793
|
globalState_1.default.log(styles.warning(`No app folders found in ${appsDir}.`));
|
|
1794
1794
|
}
|
|
1795
|
+
const matchedRefs = new Set();
|
|
1795
1796
|
for (const subDir of subDirs) {
|
|
1796
1797
|
const folderPath = path.join(appsDir, subDir.name);
|
|
1797
1798
|
try {
|
|
1798
1799
|
// eslint-disable-next-line no-await-in-loop
|
|
1799
1800
|
const code = await (0, appCodeFiles_1.readBundleFromDir)(folderPath);
|
|
1800
|
-
if (
|
|
1801
|
-
|
|
1802
|
-
globalState_1.default.debug(`Skipping app folder "${subDir.name}" (uuid ${code.manifest.appUuid} not in filter)`);
|
|
1801
|
+
if (!(0, appsDownload_1.uploadFilterMatches)(uploadFilter, code.manifest)) {
|
|
1802
|
+
globalState_1.default.debug(`Skipping app folder "${subDir.name}" (not in filter)`);
|
|
1803
1803
|
// eslint-disable-next-line no-continue
|
|
1804
1804
|
continue;
|
|
1805
1805
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
if (uploadDecision === 'needs-confirmation') {
|
|
1809
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1810
|
-
// eslint-disable-next-line no-await-in-loop
|
|
1811
|
-
const { confirmed } = await inquirer_1.default.prompt([
|
|
1812
|
-
{
|
|
1813
|
-
type: 'confirm',
|
|
1814
|
-
name: 'confirmed',
|
|
1815
|
-
message: `"${subDir.name}" was downloaded from project ${code.manifest.projectUuid}, but you are uploading to project ${projectId}. This will CREATE a new app. Continue?`,
|
|
1816
|
-
default: false,
|
|
1817
|
-
},
|
|
1818
|
-
]);
|
|
1819
|
-
if (!confirmed) {
|
|
1820
|
-
globalState_1.default.log(`Skipped "${subDir.name}" (cross-project create declined). Pass --create-new to make this explicit. If this app was already moved to the target project, set appUuid and projectUuid in lightdash-app.yml to the moved app instead.`);
|
|
1821
|
-
appsSkipped += 1;
|
|
1822
|
-
// eslint-disable-next-line no-continue
|
|
1823
|
-
continue;
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
else {
|
|
1827
|
-
globalState_1.default.log(styles.error(`Cannot upload "${subDir.name}": its manifest targets project ${code.manifest.projectUuid} but you are uploading to project ${projectId}. Pass --create-new to create a new app in the target project. If this app was already moved there, set appUuid and projectUuid in lightdash-app.yml to the moved app instead.`));
|
|
1828
|
-
appsFailed += 1;
|
|
1829
|
-
// eslint-disable-next-line no-continue
|
|
1830
|
-
continue;
|
|
1831
|
-
}
|
|
1806
|
+
if (uploadFilter) {
|
|
1807
|
+
(0, appsDownload_1.matchedUploadRefs)(uploadFilter, code.manifest).forEach((ref) => matchedRefs.add(ref));
|
|
1832
1808
|
}
|
|
1833
1809
|
// Read declared dependencies from the app folder (optional).
|
|
1834
1810
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -1870,14 +1846,14 @@ const uploadHandler = async (options) => {
|
|
|
1870
1846
|
warningLines.forEach((line) => globalState_1.default.log(line));
|
|
1871
1847
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1872
1848
|
// eslint-disable-next-line no-await-in-loop
|
|
1873
|
-
const { proceed } = await inquirer_1.default.prompt([
|
|
1849
|
+
const { proceed } = await output.promptWhilePaused(() => inquirer_1.default.prompt([
|
|
1874
1850
|
{
|
|
1875
1851
|
type: 'confirm',
|
|
1876
1852
|
name: 'proceed',
|
|
1877
1853
|
message: `Upload "${subDir.name}" with custom dependencies?`,
|
|
1878
1854
|
default: true,
|
|
1879
1855
|
},
|
|
1880
|
-
]);
|
|
1856
|
+
]));
|
|
1881
1857
|
if (!proceed) {
|
|
1882
1858
|
globalState_1.default.log(`Skipped "${subDir.name}" (custom dependency upload declined).`);
|
|
1883
1859
|
appsSkipped += 1;
|
|
@@ -1897,7 +1873,7 @@ const uploadHandler = async (options) => {
|
|
|
1897
1873
|
createNew: options.createNew === true,
|
|
1898
1874
|
});
|
|
1899
1875
|
// eslint-disable-next-line no-await-in-loop
|
|
1900
|
-
const { appUuid, version, action, warnings } = await (0, apiClient_1.lightdashApi)({
|
|
1876
|
+
const { appUuid, version, action, slug, warnings } = await (0, apiClient_1.lightdashApi)({
|
|
1901
1877
|
method: 'POST',
|
|
1902
1878
|
url: `/api/v1/ee/projects/${projectId}/apps/upload`,
|
|
1903
1879
|
body: JSON.stringify(body),
|
|
@@ -1913,42 +1889,20 @@ const uploadHandler = async (options) => {
|
|
|
1913
1889
|
}
|
|
1914
1890
|
const actionLabel = action === 'create' ? 'created' : 'updated';
|
|
1915
1891
|
globalState_1.default.log(styles.success(`Uploaded "${code.manifest.name}" — ${actionLabel} v${version} (${appUuid}). Building in the background; the app will show "building" until the server finishes.`));
|
|
1892
|
+
if (code.manifest.slug === undefined) {
|
|
1893
|
+
globalState_1.default.log(styles.warning((0, appsDownload_1.preSlugUploadHint)({
|
|
1894
|
+
folder: subDir.name,
|
|
1895
|
+
slug,
|
|
1896
|
+
})));
|
|
1897
|
+
}
|
|
1898
|
+
else if (slug === undefined) {
|
|
1899
|
+
// Bundle sent a slug but the response has none: the
|
|
1900
|
+
// server predates slug identity and matched by uuid
|
|
1901
|
+
// only (slug-only bundles may have just duplicated).
|
|
1902
|
+
globalState_1.default.log(styles.warning((0, appsDownload_1.preSlugServerHint)(subDir.name)));
|
|
1903
|
+
}
|
|
1916
1904
|
if (action === 'create') {
|
|
1917
1905
|
globalState_1.default.log(`New app: ${config.context.serverUrl}/projects/${projectId}/apps/${appUuid}`);
|
|
1918
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1919
|
-
// eslint-disable-next-line no-await-in-loop
|
|
1920
|
-
const { retarget } = await inquirer_1.default.prompt([
|
|
1921
|
-
{
|
|
1922
|
-
type: 'confirm',
|
|
1923
|
-
name: 'retarget',
|
|
1924
|
-
message: `Update ${subDir.name}/lightdash-app.yml to target the new app? This sets appUuid ${appUuid}, projectUuid ${projectId}, version ${version} — future uploads will update this app.`,
|
|
1925
|
-
default: true,
|
|
1926
|
-
},
|
|
1927
|
-
]);
|
|
1928
|
-
if (retarget) {
|
|
1929
|
-
// eslint-disable-next-line no-await-in-loop
|
|
1930
|
-
await (0, appCodeFiles_1.retargetManifest)(folderPath, {
|
|
1931
|
-
appUuid,
|
|
1932
|
-
projectUuid: projectId,
|
|
1933
|
-
version,
|
|
1934
|
-
});
|
|
1935
|
-
globalState_1.default.log(styles.success(`Updated ${subDir.name}/lightdash-app.yml → appUuid ${appUuid}, projectUuid ${projectId}, version ${version}.`));
|
|
1936
|
-
}
|
|
1937
|
-
else {
|
|
1938
|
-
globalState_1.default.log(styles.warning((0, appsDownload_1.manifestRetargetHint)({
|
|
1939
|
-
folder: subDir.name,
|
|
1940
|
-
appUuid,
|
|
1941
|
-
projectUuid: projectId,
|
|
1942
|
-
})));
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
else {
|
|
1946
|
-
globalState_1.default.log(styles.warning((0, appsDownload_1.manifestRetargetHint)({
|
|
1947
|
-
folder: subDir.name,
|
|
1948
|
-
appUuid,
|
|
1949
|
-
projectUuid: projectId,
|
|
1950
|
-
})));
|
|
1951
|
-
}
|
|
1952
1906
|
}
|
|
1953
1907
|
}
|
|
1954
1908
|
catch (appErr) {
|
|
@@ -1962,6 +1916,12 @@ const uploadHandler = async (options) => {
|
|
|
1962
1916
|
globalState_1.default.log(styles.error(`Failed to upload app folder "${subDir.name}"${status ? ` [HTTP ${status}]` : ''}: ${(0, common_1.getErrorMessage)(appErr)}${hint}`));
|
|
1963
1917
|
}
|
|
1964
1918
|
}
|
|
1919
|
+
if (uploadFilter) {
|
|
1920
|
+
const unmatchedWarning = (0, appsDownload_1.unmatchedUploadRefsWarning)([...uploadFilter].filter((ref) => !matchedRefs.has(ref)));
|
|
1921
|
+
if (unmatchedWarning) {
|
|
1922
|
+
globalState_1.default.log(styles.warning(unmatchedWarning));
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1965
1925
|
if (appsCreated > 0)
|
|
1966
1926
|
changes['data apps created'] = appsCreated;
|
|
1967
1927
|
if (appsUpdated > 0)
|
|
@@ -1972,6 +1932,13 @@ const uploadHandler = async (options) => {
|
|
|
1972
1932
|
changes['data apps skipped'] = appsSkipped;
|
|
1973
1933
|
const appSummary = summarizeUploadChanges(changesBeforeApps, changes);
|
|
1974
1934
|
output.completeItem(appSummary.detail, appSummary.variant);
|
|
1935
|
+
if (appsFailed > 0) {
|
|
1936
|
+
// App uploads are fire-and-forget per folder, so failures are
|
|
1937
|
+
// logged and tallied rather than thrown — but the process must
|
|
1938
|
+
// still exit non-zero or CI pipelines read the run as green.
|
|
1939
|
+
globalState_1.default.log(styles.error(`${appsFailed} data app upload(s) failed — see errors above.`));
|
|
1940
|
+
process.exitCode = 1;
|
|
1941
|
+
}
|
|
1975
1942
|
}
|
|
1976
1943
|
const end = Date.now();
|
|
1977
1944
|
await analytics_1.LightdashAnalytics.track({
|
|
@@ -7,6 +7,7 @@ const os = tslib_1.__importStar(require("os"));
|
|
|
7
7
|
const path = tslib_1.__importStar(require("path"));
|
|
8
8
|
const vitest_1 = require("vitest");
|
|
9
9
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
10
|
+
const appsDownload_1 = require("./apps/appsDownload");
|
|
10
11
|
const apiClient_1 = require("./dbt/apiClient");
|
|
11
12
|
const download_1 = require("./download");
|
|
12
13
|
vitest_1.vi.mock('./dbt/apiClient', async (importOriginal) => ({
|
|
@@ -44,6 +45,35 @@ describe('hasContentFilters', () => {
|
|
|
44
45
|
}))).toBe(false);
|
|
45
46
|
});
|
|
46
47
|
});
|
|
48
|
+
describe('data app upload loop filter wiring', () => {
|
|
49
|
+
const makeManifest = (overrides = {}) => ({
|
|
50
|
+
codeVersion: 1,
|
|
51
|
+
appUuid: 'app-uuid-unmatched',
|
|
52
|
+
projectUuid: 'proj-uuid-1',
|
|
53
|
+
version: 1,
|
|
54
|
+
name: 'App',
|
|
55
|
+
description: '',
|
|
56
|
+
template: null,
|
|
57
|
+
downloadedAt: '2026-06-25T00:00:00.000Z',
|
|
58
|
+
...overrides,
|
|
59
|
+
});
|
|
60
|
+
it('skips a folder matching neither slug nor uuid, and uploads one matched by slug', () => {
|
|
61
|
+
// Mirrors the --apps <appReferences...> wiring in the upload loop:
|
|
62
|
+
// the filter is built once from the CLI-supplied references, then
|
|
63
|
+
// matched per folder via uploadFilterMatches.
|
|
64
|
+
const uploadFilter = (0, appsDownload_1.getDataAppUploadFilter)(['sales-app'], false);
|
|
65
|
+
const unmatchedFolder = makeManifest({
|
|
66
|
+
appUuid: 'other-uuid',
|
|
67
|
+
slug: 'other-slug',
|
|
68
|
+
});
|
|
69
|
+
const matchedBySlugFolder = makeManifest({
|
|
70
|
+
appUuid: 'matched-uuid',
|
|
71
|
+
slug: 'sales-app',
|
|
72
|
+
});
|
|
73
|
+
expect((0, appsDownload_1.uploadFilterMatches)(uploadFilter, unmatchedFolder)).toBe(false);
|
|
74
|
+
expect((0, appsDownload_1.uploadFilterMatches)(uploadFilter, matchedBySlugFolder)).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
47
77
|
const makeLooseDashboard = (slug, chartSlugs) => ({
|
|
48
78
|
slug,
|
|
49
79
|
name: slug,
|
package/dist/index.js
CHANGED
|
@@ -374,7 +374,7 @@ const downloadCommand = commander_1.program
|
|
|
374
374
|
.option('--include-scheduled-deliveries', 'include all scheduled deliveries in the download', false)
|
|
375
375
|
.option('--include-all', 'include all optional content in the download', false)
|
|
376
376
|
.option('--strip-pivot-series', 'strip per-value pivot series config from chart YAML for portable downloads', false)
|
|
377
|
-
.option('--apps <appReferences...>', 'Download only specified data apps by UUID or URL (enterprise). Works for apps not added to a space.')
|
|
377
|
+
.option('--apps <appReferences...>', 'Download only the specified data apps, by UUID, slug, or app URL (enterprise). Works for apps not added to a space.')
|
|
378
378
|
.option('--include-apps', "Include all of the project's data apps (enterprise), capped at --apps-limit (default: 50)", false)
|
|
379
379
|
.option('--apps-limit <number>', 'Maximum number of data apps downloaded by --include-apps or --include-all (default: 50)', undefined)
|
|
380
380
|
.option('--apps-only', 'Download only data apps (implies --skip-charts --skip-dashboards --skip-spaces). Requires --apps <appReferences...>, --include-apps, or --include-all.', false)
|
|
@@ -410,9 +410,9 @@ const uploadCommand = commander_1.program
|
|
|
410
410
|
.option('--include-charts', 'Include charts updates when uploading dashboards', false)
|
|
411
411
|
.option('--validate', 'Validate charts and dashboards after upload', false)
|
|
412
412
|
.option('--gzip', 'Enable gzip compression for request bodies', false)
|
|
413
|
-
.option('--apps <appReferences...>', 'Upload only specified data apps by UUID or URL (enterprise).')
|
|
413
|
+
.option('--apps <appReferences...>', 'Upload only the specified data apps, by UUID, slug, or app URL (enterprise).')
|
|
414
414
|
.option('--include-apps', 'Upload all app folders on disk (enterprise).', false)
|
|
415
|
-
.option('--create-new', 'Always create a new app from the uploaded code instead of updating the app referenced by lightdash-app.yml.', false)
|
|
415
|
+
.option('--create-new', 'Always create a new app from the uploaded code instead of updating the app referenced by lightdash-app.yml. The new app gets a fresh slug.', false)
|
|
416
416
|
.option('--organization', 'upload all organization-scoped resources without selecting a project', false)
|
|
417
417
|
.option('--send-invites', 'send invitations to eligible pending users during organization upload', false);
|
|
418
418
|
uploadCommand.action(withOrganizationMode(download_1.uploadHandler));
|
|
@@ -39,6 +39,7 @@ export declare const createContentAsCodeOutput: ({ operation, scope, }: CreateCo
|
|
|
39
39
|
updateActive: (detail: string) => void;
|
|
40
40
|
completeItem: (detail: string, variant?: ContentAsCodeOutputVariant) => void;
|
|
41
41
|
runItem: <T>({ label, action, detail, variant, }: RunContentAsCodeItemProps<T>) => Promise<T>;
|
|
42
|
+
promptWhilePaused: <T>(prompt: () => Promise<T>) => Promise<T>;
|
|
42
43
|
prepareForFailureDetails: () => void;
|
|
43
44
|
complete: (path: string, elapsedSeconds: number) => boolean;
|
|
44
45
|
fail: (detail: string, elapsedSeconds: number, showFallback: boolean) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contentAsCodeOutput.d.ts","sourceRoot":"","sources":["../../src/terminal/contentAsCodeOutput.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,MAAM,MAAM,uBAAuB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,0BAA0B,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACnC,SAAS,EAAE,UAAU,GAAG,QAAQ,CAAC;IACjC,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,uBAAuB,EAAE,CAAC;CACpC,CAAC;AAEF,KAAK,0BAA0B,GAAG,IAAI,CAClC,wBAAwB,EACxB,WAAW,GAAG,OAAO,GAAG,OAAO,CAClC,GAAG;IACA,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,yBAAyB,GAAG,IAAI,CACjC,wBAAwB,EACxB,WAAW,GAAG,OAAO,GAAG,gBAAgB,GAAG,OAAO,CACrD,GAAG;IACA,UAAU,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;CACxD,CAAC;AAkBF,eAAO,MAAM,iBAAiB,eAAgB,MAAM,WACR,CAAC;AAE7C,eAAO,MAAM,yBAAyB,mCAInC,IAAI,CAAC,uBAAuB,EAAE,OAAO,GAAG,QAAQ,GAAG,YAAY,CAAC,WAG3B,CAAC;AAczC,eAAO,MAAM,2BAA2B,4DAMrC,0BAA0B,WAe5B,CAAC;AAEF,eAAO,MAAM,0BAA0B,6DAMpC,yBAAyB,WAY3B,CAAC;AAEF,eAAO,MAAM,2BAA2B,uDAMrC,wBAAwB,WAW1B,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,OAOzC,CAAC;AAEN,eAAO,MAAM,yBAAyB,YAAa,MAAM,KAAG,IAM3D,CAAC;AAEF,eAAO,MAAM,2BAA2B,UAC7B,wBAAwB,KAChC,OAOF,CAAC;AAEF,KAAK,8BAA8B,GAAG,IAAI,CACtC,wBAAwB,EACxB,WAAW,GAAG,OAAO,CACxB,CAAC;AAEF,KAAK,yBAAyB,CAAC,CAAC,IAAI;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,0BAA0B,GAAG,SAAS,CAAC;CAClE,CAAC;AAEF,eAAO,MAAM,yBAAyB,0BAGnC,8BAA8B;uBASH,MAAM,WAAW,MAAM;2BAwBnB,MAAM;2BAcxB,MAAM,YACJ,0BAA0B;cAkBjB,CAAC,uCAKrB,yBAAyB,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAAC;;
|
|
1
|
+
{"version":3,"file":"contentAsCodeOutput.d.ts","sourceRoot":"","sources":["../../src/terminal/contentAsCodeOutput.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,MAAM,MAAM,uBAAuB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,0BAA0B,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACnC,SAAS,EAAE,UAAU,GAAG,QAAQ,CAAC;IACjC,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,uBAAuB,EAAE,CAAC;CACpC,CAAC;AAEF,KAAK,0BAA0B,GAAG,IAAI,CAClC,wBAAwB,EACxB,WAAW,GAAG,OAAO,GAAG,OAAO,CAClC,GAAG;IACA,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,yBAAyB,GAAG,IAAI,CACjC,wBAAwB,EACxB,WAAW,GAAG,OAAO,GAAG,gBAAgB,GAAG,OAAO,CACrD,GAAG;IACA,UAAU,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;CACxD,CAAC;AAkBF,eAAO,MAAM,iBAAiB,eAAgB,MAAM,WACR,CAAC;AAE7C,eAAO,MAAM,yBAAyB,mCAInC,IAAI,CAAC,uBAAuB,EAAE,OAAO,GAAG,QAAQ,GAAG,YAAY,CAAC,WAG3B,CAAC;AAczC,eAAO,MAAM,2BAA2B,4DAMrC,0BAA0B,WAe5B,CAAC;AAEF,eAAO,MAAM,0BAA0B,6DAMpC,yBAAyB,WAY3B,CAAC;AAEF,eAAO,MAAM,2BAA2B,uDAMrC,wBAAwB,WAW1B,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,OAOzC,CAAC;AAEN,eAAO,MAAM,yBAAyB,YAAa,MAAM,KAAG,IAM3D,CAAC;AAEF,eAAO,MAAM,2BAA2B,UAC7B,wBAAwB,KAChC,OAOF,CAAC;AAEF,KAAK,8BAA8B,GAAG,IAAI,CACtC,wBAAwB,EACxB,WAAW,GAAG,OAAO,CACxB,CAAC;AAEF,KAAK,yBAAyB,CAAC,CAAC,IAAI;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,0BAA0B,GAAG,SAAS,CAAC;CAClE,CAAC;AAEF,eAAO,MAAM,yBAAyB,0BAGnC,8BAA8B;uBASH,MAAM,WAAW,MAAM;2BAwBnB,MAAM;2BAcxB,MAAM,YACJ,0BAA0B;cAkBjB,CAAC,uCAKrB,yBAAyB,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAAC;wBAWX,CAAC,UACtB,MAAM,OAAO,CAAC,CAAC,CAAC,KACzB,OAAO,CAAC,CAAC,CAAC;;qBA+BQ,MAAM,kBAAkB,MAAM,KAAG,OAAO;mBAc7C,MAAM,kBACE,MAAM,gBACR,OAAO;CA0BhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
|
@@ -134,11 +134,35 @@ const createContentAsCodeOutput = ({ operation, scope, }) => {
|
|
|
134
134
|
completeItem(detail(value), variant?.(value));
|
|
135
135
|
return value;
|
|
136
136
|
};
|
|
137
|
+
// The spinner repaints its line continuously, drawing over interactive
|
|
138
|
+
// prompts rendered beneath it — the prompt becomes invisible and the
|
|
139
|
+
// command looks hung while waiting for input. Stop the spinner for the
|
|
140
|
+
// prompt's lifetime, then resume with the current progress text.
|
|
141
|
+
const promptWhilePaused = async (prompt) => {
|
|
142
|
+
if (!spinner)
|
|
143
|
+
return prompt();
|
|
144
|
+
spinner.stop();
|
|
145
|
+
try {
|
|
146
|
+
return await prompt();
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
spinner.start(useTree
|
|
150
|
+
? (0, exports.formatContentAsCodeProgress)({
|
|
151
|
+
operation,
|
|
152
|
+
scope,
|
|
153
|
+
items,
|
|
154
|
+
activeLabel,
|
|
155
|
+
activeDetail,
|
|
156
|
+
})
|
|
157
|
+
: `${runningLabel} ${activeLabel.toLowerCase()}`);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
137
160
|
return {
|
|
138
161
|
startItem,
|
|
139
162
|
updateActive,
|
|
140
163
|
completeItem,
|
|
141
164
|
runItem,
|
|
165
|
+
promptWhilePaused,
|
|
142
166
|
prepareForFailureDetails: () => {
|
|
143
167
|
if (!useTree && spinner) {
|
|
144
168
|
spinner.stop();
|