@lightdash/cli 0.3422.1 → 0.3423.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/dbt/validation.test.d.ts +2 -0
- package/dist/dbt/validation.test.d.ts.map +1 -0
- package/dist/dbt/validation.test.js +45 -0
- package/dist/handlers/download.d.ts +6 -1
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +220 -101
- package/dist/handlers/download.test.js +22 -2
- package/dist/handlers/organizationContent/index.d.ts.map +1 -1
- package/dist/handlers/organizationContent/index.js +20 -51
- package/dist/handlers/organizationContent/index.test.js +3 -1
- package/dist/terminal/contentAsCodeOutput.d.ts +15 -5
- package/dist/terminal/contentAsCodeOutput.d.ts.map +1 -1
- package/dist/terminal/contentAsCodeOutput.js +83 -35
- package/dist/terminal/contentAsCodeOutput.test.js +64 -6
- package/package.json +3 -3
|
@@ -13,7 +13,7 @@ vitest_1.vi.mock('./dbt/apiClient', async (importOriginal) => ({
|
|
|
13
13
|
...(await importOriginal()),
|
|
14
14
|
lightdashApi: vitest_1.vi.fn(),
|
|
15
15
|
}));
|
|
16
|
-
const { assertUniqueSpacePaths, downloadSpaces, getDashboardChartSlugs, getFlatSpaceFileNames, readAiAgentFiles, readSpaceFiles, readSpaceNames, sanitizeChartForDownload, shouldFallBackToEmbeddedSpaces, shouldDownloadAiAgents, upsertSpaces, upsertVirtualViews, validateSpaceIdentity, writeSpaceFiles, } = download_1.testHelpers;
|
|
16
|
+
const { assertUniqueSpacePaths, downloadSpaces, getDashboardChartSlugs, getFlatSpaceFileNames, readAiAgentFiles, readSpaceFiles, readSpaceNames, sanitizeChartForDownload, shouldFallBackToEmbeddedSpaces, shouldDownloadAiAgents, summarizeUploadChanges, upsertSpaces, upsertVirtualViews, validateSpaceIdentity, writeSpaceFiles, } = download_1.testHelpers;
|
|
17
17
|
const makeLooseDashboard = (slug, chartSlugs) => ({
|
|
18
18
|
slug,
|
|
19
19
|
name: slug,
|
|
@@ -31,6 +31,24 @@ const writeFolderDashboard = async (baseDir, slug, chartSlugs) => {
|
|
|
31
31
|
const yaml = `contentType: dashboard\nname: ${slug}\nslug: ${slug}\nspaceSlug: test-space\ntiles:\n${tilesYaml}\nversion: 1\n`;
|
|
32
32
|
await fs_1.promises.writeFile(path.join(baseDir, 'dashboards', `${slug}.yml`), yaml);
|
|
33
33
|
};
|
|
34
|
+
describe('summarizeUploadChanges', () => {
|
|
35
|
+
it('reports only phase deltas and warns when resources fail', () => {
|
|
36
|
+
expect(summarizeUploadChanges({ 'charts created': 1, 'spaces skipped': 3 }, {
|
|
37
|
+
'charts created': 3,
|
|
38
|
+
'spaces skipped': 3,
|
|
39
|
+
'charts skipped': 2,
|
|
40
|
+
'charts with errors': 1,
|
|
41
|
+
})).toEqual({
|
|
42
|
+
detail: '2 created, 2 skipped, 1 with errors',
|
|
43
|
+
variant: 'warning',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it('reports an empty phase without inventing a resource count', () => {
|
|
47
|
+
expect(summarizeUploadChanges({}, {})).toEqual({
|
|
48
|
+
detail: 'no changes',
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
34
52
|
const pivotedSeries = (pivotValue, overrides = {}) => ({
|
|
35
53
|
type: common_1.CartesianSeriesType.BAR,
|
|
36
54
|
encode: {
|
|
@@ -378,7 +396,9 @@ spaceName: Finance
|
|
|
378
396
|
version: 1
|
|
379
397
|
`);
|
|
380
398
|
try {
|
|
381
|
-
|
|
399
|
+
const onProgress = vitest_1.vi.fn();
|
|
400
|
+
await (0, download_1.downloadContent)(['pivoted-chart'], 'charts', 'project-uuid', 'project', tmpDir, false, false, false, false, false, onProgress);
|
|
401
|
+
expect(onProgress).toHaveBeenCalledWith('1 of 1 charts downloaded');
|
|
382
402
|
await expect(readSpaceFiles(tmpDir)).resolves.toEqual([
|
|
383
403
|
expect.objectContaining({
|
|
384
404
|
filePath: existingFile,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AA0B3C,KAAK,0BAA0B,GAAG;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AA0B3C,KAAK,0BAA0B,GAAG;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAkBF,eAAO,MAAM,4BAA4B,GAAI,aAAa,MAAM,KAAG,MAClC,CAAC;AAElC,eAAO,MAAM,2BAA2B,GAAU,yBAG/C,0BAA0B,KAAG,OAAO,CAAC,IAAI,CA2F3C,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAU,sCAI7C,0BAA0B,KAAG,OAAO,CAAC,IAAI,CA0I3C,CAAC"}
|
|
@@ -20,12 +20,6 @@ const isPartialUploadError = resource_1.isCodeResourcePhaseError;
|
|
|
20
20
|
const isGroupServiceDisabledError = (error) => error instanceof common_1.LightdashError &&
|
|
21
21
|
error.statusCode === 403 &&
|
|
22
22
|
error.message === 'Group service is not enabled';
|
|
23
|
-
const getElapsedMilliseconds = (startedAt) => Date.now() - startedAt;
|
|
24
|
-
const measureAction = async (action) => {
|
|
25
|
-
const startedAt = Date.now();
|
|
26
|
-
const value = await action();
|
|
27
|
-
return { value, durationMs: getElapsedMilliseconds(startedAt) };
|
|
28
|
-
};
|
|
29
23
|
const getOrganizationContentFolder = (customPath) => (0, contentAsCodePaths_1.getDownloadFolder)(customPath);
|
|
30
24
|
exports.getOrganizationContentFolder = getOrganizationContentFolder;
|
|
31
25
|
const downloadOrganizationContent = async ({ customPath, config, }) => {
|
|
@@ -46,44 +40,29 @@ const downloadOrganizationContent = async ({ customPath, config, }) => {
|
|
|
46
40
|
const output = (0, contentAsCodeOutput_1.createContentAsCodeOutput)({
|
|
47
41
|
operation: 'download',
|
|
48
42
|
scope: 'organization',
|
|
49
|
-
initialLabel: 'Custom roles',
|
|
50
43
|
});
|
|
51
44
|
try {
|
|
52
|
-
const
|
|
53
|
-
output.completeItem({
|
|
45
|
+
const customRolesTotal = await output.runItem({
|
|
54
46
|
label: 'Custom roles',
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
const
|
|
59
|
-
output.completeItem({
|
|
47
|
+
action: () => (0, customRoles_1.downloadCustomRoles)(organizationUuid, organizationContentPath),
|
|
48
|
+
detail: (total) => `${total} downloaded`,
|
|
49
|
+
});
|
|
50
|
+
const usersTotal = await output.runItem({
|
|
60
51
|
label: 'Users',
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
const groupsStartedAt = Date.now();
|
|
52
|
+
action: () => (0, users_1.downloadUsers)(organizationUuid, organizationContentPath),
|
|
53
|
+
detail: (total) => `${total} downloaded`,
|
|
54
|
+
});
|
|
65
55
|
let groupsTotal = 0;
|
|
66
|
-
|
|
56
|
+
output.startItem('Groups');
|
|
67
57
|
try {
|
|
68
58
|
groupsTotal = await (0, groups_1.downloadGroups)(organizationUuid, organizationContentPath);
|
|
69
|
-
|
|
70
|
-
output.completeItem({
|
|
71
|
-
label: 'Groups',
|
|
72
|
-
detail: `${groupsTotal} downloaded`,
|
|
73
|
-
durationMs: groupsDurationMs,
|
|
74
|
-
}, null);
|
|
59
|
+
output.completeItem(`${groupsTotal} downloaded`);
|
|
75
60
|
}
|
|
76
61
|
catch (error) {
|
|
77
|
-
groupsDurationMs = getElapsedMilliseconds(groupsStartedAt);
|
|
78
62
|
if (!isGroupServiceDisabledError(error)) {
|
|
79
63
|
throw error;
|
|
80
64
|
}
|
|
81
|
-
output.completeItem(
|
|
82
|
-
label: 'Groups',
|
|
83
|
-
detail: 'service unavailable',
|
|
84
|
-
durationMs: groupsDurationMs,
|
|
85
|
-
variant: 'warning',
|
|
86
|
-
}, null);
|
|
65
|
+
output.completeItem('service unavailable', 'warning');
|
|
87
66
|
globalState_1.default.debug('> Warning: groups were not downloaded because the group service is not enabled');
|
|
88
67
|
}
|
|
89
68
|
const renderedSummary = output.complete(organizationContentPath, (Date.now() - start) / 1000);
|
|
@@ -137,10 +116,10 @@ const uploadOrganizationContent = async ({ customPath, config, sendInvites = fal
|
|
|
137
116
|
const output = (0, contentAsCodeOutput_1.createContentAsCodeOutput)({
|
|
138
117
|
operation: 'upload',
|
|
139
118
|
scope: 'organization',
|
|
140
|
-
initialLabel: 'Custom roles',
|
|
141
119
|
});
|
|
142
120
|
try {
|
|
143
|
-
|
|
121
|
+
output.startItem('Custom roles');
|
|
122
|
+
const summary = await (0, customRoles_1.uploadCustomRoles)(organizationUuid, organizationContentPath);
|
|
144
123
|
const summaryMessage = (0, customRoles_1.formatCustomRoleUploadSummary)(summary);
|
|
145
124
|
if (summary.failed > 0) {
|
|
146
125
|
summary.failures.forEach(({ message }) => globalState_1.default.log(styles.error(message)));
|
|
@@ -149,12 +128,9 @@ const uploadOrganizationContent = async ({ customPath, config, sendInvites = fal
|
|
|
149
128
|
globalState_1.default.log(styles.warning(`Skipped users and ${skippedGroups} groups because custom roles failed`));
|
|
150
129
|
throw createCustomRolePartialUploadError(`Processed custom roles: ${summaryMessage}`);
|
|
151
130
|
}
|
|
152
|
-
output.completeItem(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
durationMs: customRolesDurationMs,
|
|
156
|
-
}, 'Users');
|
|
157
|
-
const { value: userSummary, durationMs: usersDurationMs } = await measureAction(() => (0, users_1.uploadUsers)(organizationUuid, organizationContentPath, sendInvites));
|
|
131
|
+
output.completeItem(summaryMessage);
|
|
132
|
+
output.startItem('Users');
|
|
133
|
+
const userSummary = await (0, users_1.uploadUsers)(organizationUuid, organizationContentPath, sendInvites);
|
|
158
134
|
const userSummaryMessage = (0, users_1.formatUserUploadSummary)(userSummary);
|
|
159
135
|
if (userSummary.failed > 0) {
|
|
160
136
|
userSummary.failures.forEach(({ message }) => globalState_1.default.log(styles.error(message)));
|
|
@@ -163,23 +139,16 @@ const uploadOrganizationContent = async ({ customPath, config, sendInvites = fal
|
|
|
163
139
|
globalState_1.default.log(styles.warning(`Skipped ${skippedGroups} groups because users failed`));
|
|
164
140
|
throw createUserPartialUploadError(`Processed users: ${userSummaryMessage}`);
|
|
165
141
|
}
|
|
166
|
-
output.completeItem(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
durationMs: usersDurationMs,
|
|
170
|
-
}, 'Groups');
|
|
171
|
-
const { value: groupSummary, durationMs: groupsDurationMs } = await measureAction(() => (0, groups_1.uploadGroups)(organizationUuid, organizationContentPath));
|
|
142
|
+
output.completeItem(userSummaryMessage);
|
|
143
|
+
output.startItem('Groups');
|
|
144
|
+
const groupSummary = await (0, groups_1.uploadGroups)(organizationUuid, organizationContentPath);
|
|
172
145
|
const groupSummaryMessage = (0, groups_1.formatGroupUploadSummary)(groupSummary);
|
|
173
146
|
if (groupSummary.failed > 0) {
|
|
174
147
|
groupSummary.failures.forEach(({ message }) => globalState_1.default.log(styles.error(message)));
|
|
175
148
|
output.prepareForFailureDetails();
|
|
176
149
|
throw createGroupPartialUploadError(`Processed groups: ${groupSummaryMessage}`);
|
|
177
150
|
}
|
|
178
|
-
output.completeItem(
|
|
179
|
-
label: 'Groups',
|
|
180
|
-
detail: groupSummaryMessage,
|
|
181
|
-
durationMs: groupsDurationMs,
|
|
182
|
-
}, null);
|
|
151
|
+
output.completeItem(groupSummaryMessage);
|
|
183
152
|
const renderedSummary = output.complete(organizationContentPath, (Date.now() - start) / 1000);
|
|
184
153
|
if (!renderedSummary) {
|
|
185
154
|
globalState_1.default.log(styles.success(`Uploaded organization content from ${organizationContentPath}`));
|
|
@@ -28,6 +28,7 @@ describe('organization content download', () => {
|
|
|
28
28
|
const spinner = {
|
|
29
29
|
start: vi.fn(),
|
|
30
30
|
succeed: vi.fn(),
|
|
31
|
+
warn: vi.fn(),
|
|
31
32
|
stop: vi.fn(),
|
|
32
33
|
fail: vi.fn(),
|
|
33
34
|
};
|
|
@@ -55,7 +56,7 @@ describe('organization content download', () => {
|
|
|
55
56
|
data: {},
|
|
56
57
|
}));
|
|
57
58
|
await expect((0, index_1.downloadOrganizationContent)({ config })).resolves.toBeUndefined();
|
|
58
|
-
expect(spinner.
|
|
59
|
+
expect(spinner.warn).toHaveBeenCalledOnce();
|
|
59
60
|
expect(spinner.fail).not.toHaveBeenCalled();
|
|
60
61
|
expect(globalState_1.default.debug).toHaveBeenCalledWith('> Warning: groups were not downloaded because the group service is not enabled');
|
|
61
62
|
});
|
|
@@ -74,6 +75,7 @@ describe('organization content upload sequencing', () => {
|
|
|
74
75
|
const spinner = {
|
|
75
76
|
start: vi.fn(),
|
|
76
77
|
succeed: vi.fn(),
|
|
78
|
+
warn: vi.fn(),
|
|
77
79
|
stop: vi.fn(),
|
|
78
80
|
fail: vi.fn(),
|
|
79
81
|
};
|
|
@@ -14,25 +14,35 @@ export type ContentAsCodeOutputProps = {
|
|
|
14
14
|
};
|
|
15
15
|
type ContentAsCodeProgressProps = Pick<ContentAsCodeOutputProps, 'operation' | 'scope' | 'items'> & {
|
|
16
16
|
activeLabel: string;
|
|
17
|
+
activeDetail?: string;
|
|
17
18
|
};
|
|
18
19
|
type ContentAsCodeFailureProps = Pick<ContentAsCodeOutputProps, 'operation' | 'scope' | 'elapsedSeconds' | 'items'> & {
|
|
19
20
|
failedItem: Omit<ContentAsCodeOutputItem, 'variant'>;
|
|
20
21
|
};
|
|
21
22
|
export declare const formatDurationTag: (durationMs: number) => string;
|
|
22
23
|
export declare const formatContentAsCodeAction: ({ label, detail, durationMs, }: Pick<ContentAsCodeOutputItem, "label" | "detail" | "durationMs">) => string;
|
|
23
|
-
export declare const formatContentAsCodeProgress: ({ operation, scope, items, activeLabel, }: ContentAsCodeProgressProps) => string;
|
|
24
|
+
export declare const formatContentAsCodeProgress: ({ operation, scope, items, activeLabel, activeDetail, }: ContentAsCodeProgressProps) => string;
|
|
24
25
|
export declare const formatContentAsCodeFailure: ({ operation, scope, elapsedSeconds, items, failedItem, }: ContentAsCodeFailureProps) => string;
|
|
25
26
|
export declare const formatContentAsCodeComplete: ({ operation, scope, path, elapsedSeconds, items, }: ContentAsCodeOutputProps) => string;
|
|
26
27
|
export declare const canRenderContentAsCodeTree: () => boolean;
|
|
28
|
+
export declare const logContentAsCodeDiscovery: (message: string) => void;
|
|
27
29
|
export declare const renderContentAsCodeComplete: (props: ContentAsCodeOutputProps) => boolean;
|
|
28
|
-
type CreateContentAsCodeOutputProps = Pick<ContentAsCodeOutputProps, 'operation' | 'scope'
|
|
29
|
-
|
|
30
|
+
type CreateContentAsCodeOutputProps = Pick<ContentAsCodeOutputProps, 'operation' | 'scope'>;
|
|
31
|
+
type RunContentAsCodeItemProps<T> = {
|
|
32
|
+
label: string;
|
|
33
|
+
action: () => Promise<T>;
|
|
34
|
+
detail: (value: T) => string;
|
|
35
|
+
variant?: (value: T) => ContentAsCodeOutputVariant | undefined;
|
|
30
36
|
};
|
|
31
|
-
export declare const createContentAsCodeOutput: ({ operation, scope,
|
|
32
|
-
|
|
37
|
+
export declare const createContentAsCodeOutput: ({ operation, scope, }: CreateContentAsCodeOutputProps) => {
|
|
38
|
+
startItem: (label: string, detail?: string) => void;
|
|
39
|
+
updateActive: (detail: string) => void;
|
|
40
|
+
completeItem: (detail: string, variant?: ContentAsCodeOutputVariant) => void;
|
|
41
|
+
runItem: <T>({ label, action, detail, variant, }: RunContentAsCodeItemProps<T>) => Promise<T>;
|
|
33
42
|
prepareForFailureDetails: () => void;
|
|
34
43
|
complete: (path: string, elapsedSeconds: number) => boolean;
|
|
35
44
|
fail: (detail: string, elapsedSeconds: number, showFallback: boolean) => void;
|
|
36
45
|
};
|
|
46
|
+
export type ContentAsCodeOutput = ReturnType<typeof createContentAsCodeOutput>;
|
|
37
47
|
export {};
|
|
38
48
|
//# sourceMappingURL=contentAsCodeOutput.d.ts.map
|
|
@@ -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;
|
|
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,GAAI,YAAY,MAAM,WACR,CAAC;AAE7C,eAAO,MAAM,yBAAyB,GAAI,gCAIvC,IAAI,CAAC,uBAAuB,EAAE,OAAO,GAAG,QAAQ,GAAG,YAAY,CAAC,WAG3B,CAAC;AAczC,eAAO,MAAM,2BAA2B,GAAI,yDAMzC,0BAA0B,WAe5B,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,0DAMxC,yBAAyB,WAY3B,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,oDAMzC,wBAAwB,WAW1B,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,OAOzC,CAAC;AAEN,eAAO,MAAM,yBAAyB,GAAI,SAAS,MAAM,KAAG,IAM3D,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACpC,OAAO,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,GAAI,uBAGvC,8BAA8B;uBASH,MAAM,WAAW,MAAM;2BAwBnB,MAAM;2BAcxB,MAAM,YACJ,0BAA0B;cAkBjB,CAAC,uCAKrB,yBAAyB,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAAC;;qBAiBvB,MAAM,kBAAkB,MAAM,KAAG,OAAO;mBAc7C,MAAM,kBACE,MAAM,gBACR,OAAO;CA0BhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createContentAsCodeOutput = exports.renderContentAsCodeComplete = exports.canRenderContentAsCodeTree = exports.formatContentAsCodeComplete = exports.formatContentAsCodeFailure = exports.formatContentAsCodeProgress = exports.formatContentAsCodeAction = exports.formatDurationTag = void 0;
|
|
3
|
+
exports.createContentAsCodeOutput = exports.renderContentAsCodeComplete = exports.logContentAsCodeDiscovery = exports.canRenderContentAsCodeTree = exports.formatContentAsCodeComplete = exports.formatContentAsCodeFailure = exports.formatContentAsCodeProgress = exports.formatContentAsCodeAction = exports.formatDurationTag = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
6
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
@@ -21,10 +21,11 @@ const formatTreeItem = ({ label, detail, durationMs, variant = 'success', }) =>
|
|
|
21
21
|
const status = variant === 'success' ? chalk_1.default.green('✓') : chalk_1.default.yellow('⚠');
|
|
22
22
|
return ` ${TREE_CONNECTOR} ${status} ${chalk_1.default.bold(label)} ${chalk_1.default.gray(`· ${detail}`)} ${(0, exports.formatDurationTag)(durationMs)}`;
|
|
23
23
|
};
|
|
24
|
-
const formatContentAsCodeProgress = ({ operation, scope, items, activeLabel, }) => {
|
|
24
|
+
const formatContentAsCodeProgress = ({ operation, scope, items, activeLabel, activeDetail, }) => {
|
|
25
25
|
const header = `${chalk_1.default.bold(getOperationLabel(operation, 'running'))} content as code ${chalk_1.default.gray(`(${scope})`)}`;
|
|
26
26
|
const completedItems = items.map(formatTreeItem);
|
|
27
|
-
const activeItem = ` ${TREE_CONNECTOR} ${chalk_1.default.yellow('◐')} ${chalk_1.default.bold(activeLabel)} ${chalk_1.default.gray(`· ${
|
|
27
|
+
const activeItem = ` ${TREE_CONNECTOR} ${chalk_1.default.yellow('◐')} ${chalk_1.default.bold(activeLabel)} ${chalk_1.default.gray(`· ${activeDetail ??
|
|
28
|
+
`${getOperationLabel(operation, 'running').toLowerCase()}…`}`)}`;
|
|
28
29
|
return [header, ...completedItems, activeItem].join('\n');
|
|
29
30
|
};
|
|
30
31
|
exports.formatContentAsCodeProgress = formatContentAsCodeProgress;
|
|
@@ -49,6 +50,15 @@ const canRenderContentAsCodeTree = () => Boolean(process.stderr.isTTY &&
|
|
|
49
50
|
process.env.NO_UNICODE !== '1' &&
|
|
50
51
|
process.env.NO_UNICODE !== 'true');
|
|
51
52
|
exports.canRenderContentAsCodeTree = canRenderContentAsCodeTree;
|
|
53
|
+
const logContentAsCodeDiscovery = (message) => {
|
|
54
|
+
if ((0, exports.canRenderContentAsCodeTree)()) {
|
|
55
|
+
globalState_1.default.debug(message);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
globalState_1.default.log(message);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.logContentAsCodeDiscovery = logContentAsCodeDiscovery;
|
|
52
62
|
const renderContentAsCodeComplete = (props) => {
|
|
53
63
|
if (!(0, exports.canRenderContentAsCodeTree)()) {
|
|
54
64
|
return false;
|
|
@@ -57,52 +67,80 @@ const renderContentAsCodeComplete = (props) => {
|
|
|
57
67
|
return true;
|
|
58
68
|
};
|
|
59
69
|
exports.renderContentAsCodeComplete = renderContentAsCodeComplete;
|
|
60
|
-
const createContentAsCodeOutput = ({ operation, scope,
|
|
70
|
+
const createContentAsCodeOutput = ({ operation, scope, }) => {
|
|
61
71
|
const useTree = (0, exports.canRenderContentAsCodeTree)();
|
|
62
72
|
const items = [];
|
|
63
|
-
let activeLabel =
|
|
73
|
+
let activeLabel = 'Content';
|
|
74
|
+
let activeDetail;
|
|
64
75
|
let activeStartedAt = Date.now();
|
|
65
76
|
const runningLabel = getOperationLabel(operation, 'running');
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
operation,
|
|
69
|
-
scope,
|
|
70
|
-
items,
|
|
71
|
-
activeLabel,
|
|
72
|
-
})
|
|
73
|
-
: `${runningLabel} ${activeLabel.toLowerCase()}`);
|
|
74
|
-
const setActive = (label) => {
|
|
77
|
+
let spinner;
|
|
78
|
+
const startItem = (label, detail) => {
|
|
75
79
|
activeLabel = label;
|
|
80
|
+
activeDetail = detail;
|
|
76
81
|
activeStartedAt = Date.now();
|
|
82
|
+
const text = useTree
|
|
83
|
+
? (0, exports.formatContentAsCodeProgress)({
|
|
84
|
+
operation,
|
|
85
|
+
scope,
|
|
86
|
+
items,
|
|
87
|
+
activeLabel,
|
|
88
|
+
activeDetail,
|
|
89
|
+
})
|
|
90
|
+
: `${runningLabel} ${activeLabel.toLowerCase()}`;
|
|
91
|
+
if (!spinner) {
|
|
92
|
+
spinner = globalState_1.default.startSpinner(text);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
77
95
|
if (useTree) {
|
|
96
|
+
spinner.text = text;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
spinner.start(text);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const updateActive = (detail) => {
|
|
103
|
+
activeDetail = detail;
|
|
104
|
+
if (useTree && spinner) {
|
|
78
105
|
spinner.text = (0, exports.formatContentAsCodeProgress)({
|
|
79
106
|
operation,
|
|
80
107
|
scope,
|
|
81
108
|
items,
|
|
82
109
|
activeLabel,
|
|
110
|
+
activeDetail,
|
|
83
111
|
});
|
|
84
112
|
}
|
|
85
|
-
else {
|
|
86
|
-
spinner.start(`${runningLabel} ${activeLabel.toLowerCase()}`);
|
|
87
|
-
}
|
|
88
113
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
const completeItem = (detail, variant) => {
|
|
115
|
+
const item = {
|
|
116
|
+
label: activeLabel,
|
|
117
|
+
detail,
|
|
118
|
+
durationMs: Date.now() - activeStartedAt,
|
|
119
|
+
variant,
|
|
120
|
+
};
|
|
121
|
+
items.push(item);
|
|
122
|
+
if (!useTree && spinner) {
|
|
123
|
+
if (item.variant === 'warning') {
|
|
124
|
+
spinner.warn((0, exports.formatContentAsCodeAction)(item));
|
|
99
125
|
}
|
|
100
|
-
|
|
101
|
-
|
|
126
|
+
else {
|
|
127
|
+
spinner.succeed((0, exports.formatContentAsCodeAction)(item));
|
|
102
128
|
}
|
|
103
|
-
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const runItem = async ({ label, action, detail, variant, }) => {
|
|
132
|
+
startItem(label);
|
|
133
|
+
const value = await action();
|
|
134
|
+
completeItem(detail(value), variant?.(value));
|
|
135
|
+
return value;
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
startItem,
|
|
139
|
+
updateActive,
|
|
140
|
+
completeItem,
|
|
141
|
+
runItem,
|
|
104
142
|
prepareForFailureDetails: () => {
|
|
105
|
-
if (!useTree) {
|
|
143
|
+
if (!useTree && spinner) {
|
|
106
144
|
spinner.stop();
|
|
107
145
|
}
|
|
108
146
|
},
|
|
@@ -110,7 +148,7 @@ const createContentAsCodeOutput = ({ operation, scope, initialLabel, }) => {
|
|
|
110
148
|
if (!useTree) {
|
|
111
149
|
return false;
|
|
112
150
|
}
|
|
113
|
-
spinner
|
|
151
|
+
spinner?.stop();
|
|
114
152
|
return (0, exports.renderContentAsCodeComplete)({
|
|
115
153
|
operation,
|
|
116
154
|
scope,
|
|
@@ -121,7 +159,7 @@ const createContentAsCodeOutput = ({ operation, scope, initialLabel, }) => {
|
|
|
121
159
|
},
|
|
122
160
|
fail: (detail, elapsedSeconds, showFallback) => {
|
|
123
161
|
if (useTree) {
|
|
124
|
-
|
|
162
|
+
const failure = (0, exports.formatContentAsCodeFailure)({
|
|
125
163
|
operation,
|
|
126
164
|
scope,
|
|
127
165
|
elapsedSeconds,
|
|
@@ -131,10 +169,20 @@ const createContentAsCodeOutput = ({ operation, scope, initialLabel, }) => {
|
|
|
131
169
|
detail,
|
|
132
170
|
durationMs: Date.now() - activeStartedAt,
|
|
133
171
|
},
|
|
134
|
-
})
|
|
172
|
+
});
|
|
173
|
+
if (spinner) {
|
|
174
|
+
spinner.fail(failure);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
process.stderr.write(`${failure}\n`);
|
|
178
|
+
}
|
|
135
179
|
}
|
|
136
180
|
else if (showFallback) {
|
|
137
|
-
|
|
181
|
+
const failure = `Failed to ${operation} ${scope} content: ${detail}`;
|
|
182
|
+
if (spinner)
|
|
183
|
+
spinner.fail(failure);
|
|
184
|
+
else
|
|
185
|
+
process.stderr.write(`${failure}\n`);
|
|
138
186
|
}
|
|
139
187
|
},
|
|
140
188
|
};
|
|
@@ -98,6 +98,7 @@ describe('content as code terminal output', () => {
|
|
|
98
98
|
text: '',
|
|
99
99
|
start: vi.fn(),
|
|
100
100
|
succeed: vi.fn(),
|
|
101
|
+
warn: vi.fn(),
|
|
101
102
|
stop: vi.fn(),
|
|
102
103
|
fail: vi.fn(),
|
|
103
104
|
};
|
|
@@ -108,13 +109,10 @@ describe('content as code terminal output', () => {
|
|
|
108
109
|
const output = (0, contentAsCodeOutput_1.createContentAsCodeOutput)({
|
|
109
110
|
operation: 'download',
|
|
110
111
|
scope: 'organization',
|
|
111
|
-
initialLabel: 'Custom roles',
|
|
112
112
|
});
|
|
113
|
-
output.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
durationMs: 85,
|
|
117
|
-
}, 'Users');
|
|
113
|
+
output.startItem('Custom roles');
|
|
114
|
+
output.completeItem('34 downloaded');
|
|
115
|
+
output.startItem('Users');
|
|
118
116
|
expect(spinner.succeed).not.toHaveBeenCalled();
|
|
119
117
|
expect(spinner.text).toContain('└ ◐ Users · downloading…');
|
|
120
118
|
expect(output.complete('/tmp/lightdash', 0.6)).toBe(true);
|
|
@@ -127,4 +125,64 @@ describe('content as code terminal output', () => {
|
|
|
127
125
|
vi.unstubAllEnvs();
|
|
128
126
|
vi.restoreAllMocks();
|
|
129
127
|
});
|
|
128
|
+
it('updates the active tree item with resource progress', () => {
|
|
129
|
+
const originalIsTTY = process.stderr.isTTY;
|
|
130
|
+
Object.defineProperty(process.stderr, 'isTTY', {
|
|
131
|
+
configurable: true,
|
|
132
|
+
value: true,
|
|
133
|
+
});
|
|
134
|
+
vi.stubEnv('CI', 'false');
|
|
135
|
+
vi.stubEnv('TERM', 'xterm');
|
|
136
|
+
vi.stubEnv('NO_UNICODE', 'false');
|
|
137
|
+
const spinner = {
|
|
138
|
+
text: '',
|
|
139
|
+
start: vi.fn(),
|
|
140
|
+
succeed: vi.fn(),
|
|
141
|
+
warn: vi.fn(),
|
|
142
|
+
stop: vi.fn(),
|
|
143
|
+
fail: vi.fn(),
|
|
144
|
+
};
|
|
145
|
+
vi.spyOn(globalState_1.default, 'startSpinner').mockReturnValue(spinner);
|
|
146
|
+
const output = (0, contentAsCodeOutput_1.createContentAsCodeOutput)({
|
|
147
|
+
operation: 'download',
|
|
148
|
+
scope: 'project',
|
|
149
|
+
});
|
|
150
|
+
output.startItem('Charts');
|
|
151
|
+
output.updateActive('25 of 100 downloaded');
|
|
152
|
+
expect(spinner.text).toContain('└ ◐ Charts · 25 of 100 downloaded');
|
|
153
|
+
Object.defineProperty(process.stderr, 'isTTY', {
|
|
154
|
+
configurable: true,
|
|
155
|
+
value: originalIsTTY,
|
|
156
|
+
});
|
|
157
|
+
vi.unstubAllEnvs();
|
|
158
|
+
vi.restoreAllMocks();
|
|
159
|
+
});
|
|
160
|
+
it('hides routine discovery in tree mode unless verbose output is enabled', () => {
|
|
161
|
+
const originalIsTTY = process.stderr.isTTY;
|
|
162
|
+
Object.defineProperty(process.stderr, 'isTTY', {
|
|
163
|
+
configurable: true,
|
|
164
|
+
value: true,
|
|
165
|
+
});
|
|
166
|
+
vi.stubEnv('CI', 'false');
|
|
167
|
+
vi.stubEnv('TERM', 'xterm');
|
|
168
|
+
vi.stubEnv('NO_UNICODE', 'false');
|
|
169
|
+
const debug = vi
|
|
170
|
+
.spyOn(globalState_1.default, 'debug')
|
|
171
|
+
.mockImplementation(() => undefined);
|
|
172
|
+
const log = vi
|
|
173
|
+
.spyOn(globalState_1.default, 'log')
|
|
174
|
+
.mockImplementation(() => undefined);
|
|
175
|
+
(0, contentAsCodeOutput_1.logContentAsCodeDiscovery)('Found 3 chart files');
|
|
176
|
+
expect(debug).toHaveBeenCalledWith('Found 3 chart files');
|
|
177
|
+
expect(log).not.toHaveBeenCalled();
|
|
178
|
+
vi.stubEnv('CI', 'true');
|
|
179
|
+
(0, contentAsCodeOutput_1.logContentAsCodeDiscovery)('Found 3 chart files');
|
|
180
|
+
expect(log).toHaveBeenCalledWith('Found 3 chart files');
|
|
181
|
+
Object.defineProperty(process.stderr, 'isTTY', {
|
|
182
|
+
configurable: true,
|
|
183
|
+
value: originalIsTTY,
|
|
184
|
+
});
|
|
185
|
+
vi.unstubAllEnvs();
|
|
186
|
+
vi.restoreAllMocks();
|
|
187
|
+
});
|
|
130
188
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3423.0",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"unique-names-generator": "4.7.1",
|
|
42
42
|
"uuid": "11.1.1",
|
|
43
43
|
"yaml": "2.8.3",
|
|
44
|
-
"@lightdash/common": "0.
|
|
45
|
-
"@lightdash/warehouses": "0.
|
|
44
|
+
"@lightdash/common": "0.3423.0",
|
|
45
|
+
"@lightdash/warehouses": "0.3423.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/inquirer": "8.2.4",
|