@lightdash/cli 1.55.0 → 1.56.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/uploadRetry.d.ts +32 -0
- package/dist/handlers/apps/uploadRetry.d.ts.map +1 -0
- package/dist/handlers/apps/uploadRetry.js +60 -0
- package/dist/handlers/apps/uploadRetry.test.d.ts +2 -0
- package/dist/handlers/apps/uploadRetry.test.d.ts.map +1 -0
- package/dist/handlers/apps/uploadRetry.test.js +103 -0
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +19 -4
- package/package.json +3 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const APP_BUILD_WAIT_BUDGET_MS: number;
|
|
2
|
+
export declare const APP_BUILD_WAIT_INITIAL_DELAY_MS: number;
|
|
3
|
+
export declare const APP_BUILD_WAIT_MAX_DELAY_MS: number;
|
|
4
|
+
/**
|
|
5
|
+
* Shared wait budget for one upload run. Only time spent sleeping on the
|
|
6
|
+
* build cap is deducted, and every accepted upload refills it — so the budget
|
|
7
|
+
* bounds consecutive unproductive waiting (a stuck build queue), not the
|
|
8
|
+
* total duration of a large healthy run. Once exhausted, remaining apps fail
|
|
9
|
+
* fast on 429 exactly as they did before retries existed.
|
|
10
|
+
*/
|
|
11
|
+
export type BuildLimitWaitState = {
|
|
12
|
+
remainingMs: () => number;
|
|
13
|
+
spend: (ms: number) => void;
|
|
14
|
+
refill: () => void;
|
|
15
|
+
};
|
|
16
|
+
export declare const createBuildLimitWaitState: (budgetMs?: number) => BuildLimitWaitState;
|
|
17
|
+
export declare const isBuildLimitError: (err: unknown) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Runs `post` and, when the server rejects it with the per-project build cap
|
|
20
|
+
* (HTTP 429), retries with capped exponential backoff until the shared wait
|
|
21
|
+
* budget runs out. Builds take minutes, so slots free up slowly — the backoff
|
|
22
|
+
* settles on a slow poll rather than hammering the endpoint. Any other error,
|
|
23
|
+
* or a 429 after the budget is spent, is rethrown to the caller's normal
|
|
24
|
+
* failure handling.
|
|
25
|
+
*/
|
|
26
|
+
export declare const withBuildLimitRetry: <T>(post: () => Promise<T>, waitState: BuildLimitWaitState, opts: {
|
|
27
|
+
onWait: (attempt: number, delayMs: number) => void;
|
|
28
|
+
initialDelayMs?: number;
|
|
29
|
+
maxDelayMs?: number;
|
|
30
|
+
sleep?: (ms: number) => Promise<void>;
|
|
31
|
+
}) => Promise<T>;
|
|
32
|
+
//# sourceMappingURL=uploadRetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadRetry.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/uploadRetry.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AACvD,eAAO,MAAM,+BAA+B,QAAW,CAAC;AACxD,eAAO,MAAM,2BAA2B,QAAY,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,yBAAyB,cACxB,MAAM,KACjB,mBAWF,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAS,OAAO,KAAG,OACU,CAAC;AAO5D;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAAU,CAAC,QACjC,MAAM,OAAO,CAAC,CAAC,CAAC,aACX,mBAAmB,QACxB;IACF,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC,KACF,OAAO,CAAC,CAAC,CAwBX,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withBuildLimitRetry = exports.isBuildLimitError = exports.createBuildLimitWaitState = exports.APP_BUILD_WAIT_MAX_DELAY_MS = exports.APP_BUILD_WAIT_INITIAL_DELAY_MS = exports.APP_BUILD_WAIT_BUDGET_MS = void 0;
|
|
4
|
+
const common_1 = require("@lightdash/common");
|
|
5
|
+
exports.APP_BUILD_WAIT_BUDGET_MS = 10 * 60 * 1000;
|
|
6
|
+
exports.APP_BUILD_WAIT_INITIAL_DELAY_MS = 5 * 1000;
|
|
7
|
+
exports.APP_BUILD_WAIT_MAX_DELAY_MS = 30 * 1000;
|
|
8
|
+
const createBuildLimitWaitState = (budgetMs = exports.APP_BUILD_WAIT_BUDGET_MS) => {
|
|
9
|
+
let remainingMs = budgetMs;
|
|
10
|
+
return {
|
|
11
|
+
remainingMs: () => remainingMs,
|
|
12
|
+
spend: (ms) => {
|
|
13
|
+
remainingMs -= ms;
|
|
14
|
+
},
|
|
15
|
+
refill: () => {
|
|
16
|
+
remainingMs = budgetMs;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
exports.createBuildLimitWaitState = createBuildLimitWaitState;
|
|
21
|
+
const isBuildLimitError = (err) => err instanceof common_1.LightdashError && err.statusCode === 429;
|
|
22
|
+
exports.isBuildLimitError = isBuildLimitError;
|
|
23
|
+
const defaultSleep = (ms) => new Promise((resolve) => {
|
|
24
|
+
setTimeout(resolve, ms);
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Runs `post` and, when the server rejects it with the per-project build cap
|
|
28
|
+
* (HTTP 429), retries with capped exponential backoff until the shared wait
|
|
29
|
+
* budget runs out. Builds take minutes, so slots free up slowly — the backoff
|
|
30
|
+
* settles on a slow poll rather than hammering the endpoint. Any other error,
|
|
31
|
+
* or a 429 after the budget is spent, is rethrown to the caller's normal
|
|
32
|
+
* failure handling.
|
|
33
|
+
*/
|
|
34
|
+
const withBuildLimitRetry = async (post, waitState, opts) => {
|
|
35
|
+
const maxDelayMs = opts.maxDelayMs ?? exports.APP_BUILD_WAIT_MAX_DELAY_MS;
|
|
36
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
37
|
+
let delayMs = opts.initialDelayMs ?? exports.APP_BUILD_WAIT_INITIAL_DELAY_MS;
|
|
38
|
+
let attempt = 0;
|
|
39
|
+
for (;;) {
|
|
40
|
+
try {
|
|
41
|
+
// eslint-disable-next-line no-await-in-loop
|
|
42
|
+
const result = await post();
|
|
43
|
+
waitState.refill();
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (!(0, exports.isBuildLimitError)(err) || waitState.remainingMs() <= 0) {
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
attempt += 1;
|
|
51
|
+
const waitMs = Math.min(delayMs, waitState.remainingMs());
|
|
52
|
+
opts.onWait(attempt, waitMs);
|
|
53
|
+
// eslint-disable-next-line no-await-in-loop
|
|
54
|
+
await sleep(waitMs);
|
|
55
|
+
waitState.spend(waitMs);
|
|
56
|
+
delayMs = Math.min(delayMs * 2, maxDelayMs);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
exports.withBuildLimitRetry = withBuildLimitRetry;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadRetry.test.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/uploadRetry.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const common_1 = require("@lightdash/common");
|
|
4
|
+
const uploadRetry_1 = require("./uploadRetry");
|
|
5
|
+
const buildLimitError = () => new common_1.LightdashError({
|
|
6
|
+
message: 'Too many app builds in progress for this project (5/5). Wait for some to finish and try again.',
|
|
7
|
+
name: 'TooManyRequestsError',
|
|
8
|
+
statusCode: 429,
|
|
9
|
+
data: {},
|
|
10
|
+
});
|
|
11
|
+
describe('withBuildLimitRetry', () => {
|
|
12
|
+
it('retries 429s with backoff until the upload is accepted and refills the budget', async () => {
|
|
13
|
+
const sleeps = [];
|
|
14
|
+
const sleep = vi.fn(async (ms) => {
|
|
15
|
+
sleeps.push(ms);
|
|
16
|
+
});
|
|
17
|
+
const onWait = vi.fn();
|
|
18
|
+
const post = vi
|
|
19
|
+
.fn()
|
|
20
|
+
.mockRejectedValueOnce(buildLimitError())
|
|
21
|
+
.mockRejectedValueOnce(buildLimitError())
|
|
22
|
+
.mockResolvedValue('accepted');
|
|
23
|
+
const waitState = (0, uploadRetry_1.createBuildLimitWaitState)(600_000);
|
|
24
|
+
waitState.spend(500_000); // partially spent by earlier apps
|
|
25
|
+
const result = await (0, uploadRetry_1.withBuildLimitRetry)(post, waitState, {
|
|
26
|
+
onWait,
|
|
27
|
+
initialDelayMs: 5_000,
|
|
28
|
+
maxDelayMs: 30_000,
|
|
29
|
+
sleep,
|
|
30
|
+
});
|
|
31
|
+
expect(result).toBe('accepted');
|
|
32
|
+
expect(post).toHaveBeenCalledTimes(3);
|
|
33
|
+
expect(sleeps).toEqual([5_000, 10_000]);
|
|
34
|
+
expect(onWait).toHaveBeenNthCalledWith(1, 1, 5_000);
|
|
35
|
+
expect(onWait).toHaveBeenNthCalledWith(2, 2, 10_000);
|
|
36
|
+
// Progress refills the shared budget for the remaining apps
|
|
37
|
+
expect(waitState.remainingMs()).toBe(600_000);
|
|
38
|
+
});
|
|
39
|
+
it('caps the backoff delay at maxDelayMs', async () => {
|
|
40
|
+
const sleeps = [];
|
|
41
|
+
const post = vi
|
|
42
|
+
.fn()
|
|
43
|
+
.mockRejectedValueOnce(buildLimitError())
|
|
44
|
+
.mockRejectedValueOnce(buildLimitError())
|
|
45
|
+
.mockRejectedValueOnce(buildLimitError())
|
|
46
|
+
.mockRejectedValueOnce(buildLimitError())
|
|
47
|
+
.mockResolvedValue('accepted');
|
|
48
|
+
await (0, uploadRetry_1.withBuildLimitRetry)(post, (0, uploadRetry_1.createBuildLimitWaitState)(600_000), {
|
|
49
|
+
onWait: vi.fn(),
|
|
50
|
+
initialDelayMs: 5_000,
|
|
51
|
+
maxDelayMs: 15_000,
|
|
52
|
+
sleep: async (ms) => {
|
|
53
|
+
sleeps.push(ms);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
expect(sleeps).toEqual([5_000, 10_000, 15_000, 15_000]);
|
|
57
|
+
});
|
|
58
|
+
it('rethrows non-429 errors without sleeping', async () => {
|
|
59
|
+
const sleep = vi.fn();
|
|
60
|
+
const notFound = new common_1.LightdashError({
|
|
61
|
+
message: 'not found',
|
|
62
|
+
name: 'NotFoundError',
|
|
63
|
+
statusCode: 404,
|
|
64
|
+
data: {},
|
|
65
|
+
});
|
|
66
|
+
const post = vi.fn().mockRejectedValue(notFound);
|
|
67
|
+
await expect((0, uploadRetry_1.withBuildLimitRetry)(post, (0, uploadRetry_1.createBuildLimitWaitState)(), {
|
|
68
|
+
onWait: vi.fn(),
|
|
69
|
+
sleep,
|
|
70
|
+
})).rejects.toThrow('not found');
|
|
71
|
+
expect(post).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(sleep).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
it('rethrows the 429 once the wait budget is exhausted', async () => {
|
|
75
|
+
const sleeps = [];
|
|
76
|
+
const post = vi.fn().mockRejectedValue(buildLimitError());
|
|
77
|
+
const waitState = (0, uploadRetry_1.createBuildLimitWaitState)(12_000);
|
|
78
|
+
await expect((0, uploadRetry_1.withBuildLimitRetry)(post, waitState, {
|
|
79
|
+
onWait: vi.fn(),
|
|
80
|
+
initialDelayMs: 5_000,
|
|
81
|
+
maxDelayMs: 30_000,
|
|
82
|
+
sleep: async (ms) => {
|
|
83
|
+
sleeps.push(ms);
|
|
84
|
+
},
|
|
85
|
+
})).rejects.toThrow('Too many app builds in progress');
|
|
86
|
+
// 5s, then 10s capped to the 7s remaining, then the budget is spent
|
|
87
|
+
expect(sleeps).toEqual([5_000, 7_000]);
|
|
88
|
+
expect(waitState.remainingMs()).toBe(0);
|
|
89
|
+
expect(post).toHaveBeenCalledTimes(3);
|
|
90
|
+
});
|
|
91
|
+
it('fails fast on 429 when a previous app already exhausted the budget', async () => {
|
|
92
|
+
const sleep = vi.fn();
|
|
93
|
+
const post = vi.fn().mockRejectedValue(buildLimitError());
|
|
94
|
+
const waitState = (0, uploadRetry_1.createBuildLimitWaitState)(600_000);
|
|
95
|
+
waitState.spend(600_000);
|
|
96
|
+
await expect((0, uploadRetry_1.withBuildLimitRetry)(post, waitState, {
|
|
97
|
+
onWait: vi.fn(),
|
|
98
|
+
sleep,
|
|
99
|
+
})).rejects.toThrow('Too many app builds in progress');
|
|
100
|
+
expect(post).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(sleep).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -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,EAEf,wBAAwB,EAgBxB,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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"}
|
|
@@ -20,6 +20,7 @@ const contentAsCodeOutput_1 = require("../terminal/contentAsCodeOutput");
|
|
|
20
20
|
const appCodeFiles_1 = require("./apps/appCodeFiles");
|
|
21
21
|
const appsDownload_1 = require("./apps/appsDownload");
|
|
22
22
|
const scaffolding_1 = require("./apps/scaffolding");
|
|
23
|
+
const uploadRetry_1 = require("./apps/uploadRetry");
|
|
23
24
|
const projectResources_1 = require("./contentAsCode/projectResources");
|
|
24
25
|
const resource_1 = require("./contentAsCode/resource");
|
|
25
26
|
const contentAsCodePaths_1 = require("./contentAsCodePaths");
|
|
@@ -1821,6 +1822,7 @@ const uploadHandler = async (options) => {
|
|
|
1821
1822
|
: `No app folders found in ${appsDir} for the dashboard(s) being uploaded. Re-run 'lightdash download' to fetch their apps.`));
|
|
1822
1823
|
}
|
|
1823
1824
|
const matchedRefs = new Set();
|
|
1825
|
+
const buildWaitState = (0, uploadRetry_1.createBuildLimitWaitState)();
|
|
1824
1826
|
for (const subDir of subDirs) {
|
|
1825
1827
|
const folderPath = path.join(appsDir, subDir.name);
|
|
1826
1828
|
try {
|
|
@@ -1930,10 +1932,17 @@ const uploadHandler = async (options) => {
|
|
|
1930
1932
|
force: options.force,
|
|
1931
1933
|
});
|
|
1932
1934
|
// eslint-disable-next-line no-await-in-loop
|
|
1933
|
-
const { appUuid, version, action, slug, warnings } = await (0, apiClient_1.lightdashApi)({
|
|
1935
|
+
const { appUuid, version, action, slug, warnings } = await (0, uploadRetry_1.withBuildLimitRetry)(() => (0, apiClient_1.lightdashApi)({
|
|
1934
1936
|
method: 'POST',
|
|
1935
1937
|
url: `/api/v1/ee/projects/${projectId}/apps/upload`,
|
|
1936
1938
|
body: JSON.stringify(body),
|
|
1939
|
+
}), buildWaitState, {
|
|
1940
|
+
onWait: (attempt, delayMs) => {
|
|
1941
|
+
if (attempt === 1) {
|
|
1942
|
+
globalState_1.default.log(styles.warning(`Project build limit reached — waiting for builds to finish before uploading "${subDir.name}"…`));
|
|
1943
|
+
}
|
|
1944
|
+
globalState_1.default.debug(`> Build cap retry ${attempt} for "${subDir.name}" in ${delayMs}ms`);
|
|
1945
|
+
},
|
|
1937
1946
|
});
|
|
1938
1947
|
// e.g. a manifest external-connection link whose slug is
|
|
1939
1948
|
// missing in the target project was skipped
|
|
@@ -1980,9 +1989,15 @@ const uploadHandler = async (options) => {
|
|
|
1980
1989
|
break;
|
|
1981
1990
|
}
|
|
1982
1991
|
appsFailed += 1;
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1992
|
+
let hint = '';
|
|
1993
|
+
if (status === 404) {
|
|
1994
|
+
hint =
|
|
1995
|
+
' — the enterprise "data apps" feature may not be enabled on this instance';
|
|
1996
|
+
}
|
|
1997
|
+
else if (status === 429) {
|
|
1998
|
+
hint =
|
|
1999
|
+
' — gave up waiting for a free build slot; re-run the upload once builds finish (unchanged apps are skipped)';
|
|
2000
|
+
}
|
|
1986
2001
|
globalState_1.default.log(styles.error(`Failed to upload app folder "${subDir.name}"${status ? ` [HTTP ${status}]` : ''}: ${(0, common_1.getErrorMessage)(appErr)}${hint}`));
|
|
1987
2002
|
}
|
|
1988
2003
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.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": "1.
|
|
45
|
-
"@lightdash/warehouses": "1.
|
|
44
|
+
"@lightdash/common": "1.56.0",
|
|
45
|
+
"@lightdash/warehouses": "1.56.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/inquirer": "8.2.4",
|