@lightdash/cli 1.79.1 → 1.80.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 +0 -5
- package/dist/handlers/apps/appCodeFiles.d.ts.map +1 -1
- package/dist/handlers/apps/appCodeFiles.js +1 -41
- package/dist/handlers/apps/appCodeFiles.test.js +0 -60
- package/dist/handlers/apps/appsDownload.d.ts +19 -19
- package/dist/handlers/apps/appsDownload.d.ts.map +1 -1
- package/dist/handlers/apps/appsDownload.js +29 -18
- package/dist/handlers/apps/appsDownload.test.js +32 -77
- package/dist/handlers/apps/preview.d.ts +1 -0
- package/dist/handlers/apps/preview.d.ts.map +1 -1
- package/dist/handlers/apps/preview.js +7 -3
- package/dist/handlers/apps/preview.test.js +13 -4
- package/dist/handlers/download.d.ts +2 -0
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +56 -35
- package/dist/index.js +4 -1
- package/package.json +3 -3
|
@@ -11,6 +11,7 @@ const yaml = tslib_1.__importStar(require("js-yaml"));
|
|
|
11
11
|
const groupBy_1 = tslib_1.__importDefault(require("lodash/groupBy"));
|
|
12
12
|
const p_limit_1 = tslib_1.__importDefault(require("p-limit"));
|
|
13
13
|
const path = tslib_1.__importStar(require("path"));
|
|
14
|
+
const uuid_1 = require("uuid");
|
|
14
15
|
const analytics_1 = require("../analytics/analytics");
|
|
15
16
|
const config_1 = require("../config");
|
|
16
17
|
const env_1 = require("../env");
|
|
@@ -1674,8 +1675,22 @@ const uploadHandler = async (options) => {
|
|
|
1674
1675
|
if (options.spacesOnly && options.skipSpaces) {
|
|
1675
1676
|
throw new common_1.ParameterError('Nothing to upload: --spaces-only cannot be combined with --skip-spaces.');
|
|
1676
1677
|
}
|
|
1678
|
+
if (options.appsOnly && options.spacesOnly) {
|
|
1679
|
+
throw new common_1.ParameterError('--apps-only cannot be combined with --spaces-only.');
|
|
1680
|
+
}
|
|
1681
|
+
if (options.appsOnly &&
|
|
1682
|
+
(options.charts.length > 0 || options.dashboards.length > 0)) {
|
|
1683
|
+
throw new common_1.ParameterError('--apps-only cannot be combined with --charts or --dashboards.');
|
|
1684
|
+
}
|
|
1685
|
+
if (options.appsOnly &&
|
|
1686
|
+
options.apps === undefined &&
|
|
1687
|
+
options.includeApps !== true) {
|
|
1688
|
+
throw new common_1.ParameterError('Nothing to upload: --apps-only requires --apps <appReferences...> or --include-apps.');
|
|
1689
|
+
}
|
|
1677
1690
|
const isOrganizationUpload = options.organization === true;
|
|
1678
|
-
|
|
1691
|
+
// --apps-only rides the existing filter machinery: every non-app phase
|
|
1692
|
+
// skips exactly as it does when only app refs are passed.
|
|
1693
|
+
const hasFilters = hasContentFilters(options) || options.appsOnly === true;
|
|
1679
1694
|
const shouldReconcileSpaces = !isOrganizationUpload && !options.skipSpaces && !hasFilters;
|
|
1680
1695
|
let preflightSpaceFiles = [];
|
|
1681
1696
|
if (shouldReconcileSpaces) {
|
|
@@ -1875,37 +1890,41 @@ const uploadHandler = async (options) => {
|
|
|
1875
1890
|
output.startItem('Data apps');
|
|
1876
1891
|
// Explicit refs filter by slug/appUuid; --include-apps uploads all.
|
|
1877
1892
|
// A pure auto-push run applies no filter — gated per folder below.
|
|
1878
|
-
|
|
1893
|
+
let uploadFilter = isExplicitAppSelection
|
|
1879
1894
|
? (0, appsDownload_1.getDataAppUploadFilter)(explicitAppReferences, options.includeApps === true)
|
|
1880
1895
|
: null;
|
|
1881
|
-
//
|
|
1882
|
-
//
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
targetProjectUuid: projectId,
|
|
1886
|
-
};
|
|
1887
|
-
// Only the auto-push gate below (skipped entirely when explicit)
|
|
1888
|
-
// reads presence, so an explicit run never needs the listing.
|
|
1889
|
-
if (!isExplicitAppSelection && autoPushAppSlugs.length > 0) {
|
|
1896
|
+
// uuid/URL --apps refs resolve to slugs against the target
|
|
1897
|
+
// project's listing so they can match slug-identity local folders.
|
|
1898
|
+
const filterHasUuidRefs = uploadFilter !== null && [...uploadFilter].some(uuid_1.validate);
|
|
1899
|
+
if (filterHasUuidRefs && uploadFilter !== null) {
|
|
1890
1900
|
try {
|
|
1891
1901
|
const projectApps = await (0, apiClient_1.lightdashApi)({
|
|
1892
1902
|
method: 'GET',
|
|
1893
1903
|
url: `/api/v1/ee/projects/${projectId}/apps`,
|
|
1894
1904
|
body: undefined,
|
|
1895
1905
|
});
|
|
1896
|
-
|
|
1897
|
-
// error, but it is not an answer either.
|
|
1898
|
-
if (projectApps.every((app) => app.slug !== undefined)) {
|
|
1899
|
-
presence = {
|
|
1900
|
-
kind: 'known',
|
|
1901
|
-
slugs: new Set(projectApps.map((app) => app.slug)),
|
|
1902
|
-
};
|
|
1903
|
-
}
|
|
1906
|
+
uploadFilter = (0, appsDownload_1.resolveUploadFilterUuids)(uploadFilter, projectApps);
|
|
1904
1907
|
}
|
|
1905
1908
|
catch (listErr) {
|
|
1906
1909
|
globalState_1.default.debug(`Could not list target project apps: ${(0, common_1.getErrorMessage)(listErr)}`);
|
|
1907
1910
|
}
|
|
1908
1911
|
}
|
|
1912
|
+
// The server applies the space on creates only; existing apps
|
|
1913
|
+
// keep their space.
|
|
1914
|
+
let appSpaceUuid;
|
|
1915
|
+
if (options.appSpace !== undefined) {
|
|
1916
|
+
if ((0, uuid_1.validate)(options.appSpace)) {
|
|
1917
|
+
appSpaceUuid = options.appSpace;
|
|
1918
|
+
}
|
|
1919
|
+
else {
|
|
1920
|
+
const spaces = await (0, apiClient_1.lightdashApi)({
|
|
1921
|
+
method: 'GET',
|
|
1922
|
+
url: `/api/v1/projects/${projectId}/spaces`,
|
|
1923
|
+
body: undefined,
|
|
1924
|
+
});
|
|
1925
|
+
appSpaceUuid = (0, appsDownload_1.resolveAppSpaceUuid)(options.appSpace, spaces);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1909
1928
|
const baseDir = (0, contentAsCodePaths_1.getDownloadFolder)(options.path);
|
|
1910
1929
|
const appsDir = path.join(baseDir, 'apps');
|
|
1911
1930
|
let appFolderEntries;
|
|
@@ -1957,19 +1976,10 @@ const uploadHandler = async (options) => {
|
|
|
1957
1976
|
// eslint-disable-next-line no-continue
|
|
1958
1977
|
continue;
|
|
1959
1978
|
}
|
|
1960
|
-
//
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
presence,
|
|
1965
|
-
folderChanged,
|
|
1966
|
-
force: options.force === true,
|
|
1967
|
-
})) {
|
|
1968
|
-
globalState_1.default.debug(`Skipping app "${subDir.name}" — unchanged and already in the target project`);
|
|
1969
|
-
appsSkipped += 1;
|
|
1970
|
-
// eslint-disable-next-line no-continue
|
|
1971
|
-
continue;
|
|
1972
|
-
}
|
|
1979
|
+
// Auto-push candidates always POST: the server's
|
|
1980
|
+
// byte-compare skip is the single unchanged authority,
|
|
1981
|
+
// and it runs before the build cap, so identical apps
|
|
1982
|
+
// cost no build slots.
|
|
1973
1983
|
}
|
|
1974
1984
|
// Read declared dependencies from the app folder (optional).
|
|
1975
1985
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -2011,14 +2021,25 @@ const uploadHandler = async (options) => {
|
|
|
2011
2021
|
const warningLines = (0, appCodeFiles_1.buildDepsWarningLines)(customDeps, templateDeps);
|
|
2012
2022
|
globalState_1.default.log(styles.warning(`"${subDir.name}" declares custom dependencies that will be installed in the build sandbox:`));
|
|
2013
2023
|
warningLines.forEach((line) => globalState_1.default.log(line));
|
|
2014
|
-
if (
|
|
2024
|
+
if (options.allowCustomDependencies !== true) {
|
|
2025
|
+
const canPrompt = process.stdin.isTTY === true &&
|
|
2026
|
+
process.stdout.isTTY === true &&
|
|
2027
|
+
!globalState_1.default.isNonInteractive();
|
|
2028
|
+
if (!canPrompt) {
|
|
2029
|
+
// Fail closed: installing packages in the
|
|
2030
|
+
// build sandbox needs explicit approval.
|
|
2031
|
+
globalState_1.default.log(styles.error(`Skipping "${subDir.name}": it declares custom dependencies, which need approval. Pass --allow-custom-dependencies to approve in non-interactive runs.`));
|
|
2032
|
+
appsFailed += 1;
|
|
2033
|
+
// eslint-disable-next-line no-continue
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2015
2036
|
// eslint-disable-next-line no-await-in-loop
|
|
2016
2037
|
const { proceed } = await output.promptWhilePaused(() => inquirer_1.default.prompt([
|
|
2017
2038
|
{
|
|
2018
2039
|
type: 'confirm',
|
|
2019
2040
|
name: 'proceed',
|
|
2020
2041
|
message: `Upload "${subDir.name}" with custom dependencies?`,
|
|
2021
|
-
default:
|
|
2042
|
+
default: false,
|
|
2022
2043
|
},
|
|
2023
2044
|
]));
|
|
2024
2045
|
if (!proceed) {
|
|
@@ -2028,7 +2049,6 @@ const uploadHandler = async (options) => {
|
|
|
2028
2049
|
continue;
|
|
2029
2050
|
}
|
|
2030
2051
|
}
|
|
2031
|
-
// Non-TTY: proceed without prompting (upload is deliberate).
|
|
2032
2052
|
codeToUpload = (0, appCodeFiles_1.attachDependenciesToCode)(code, customDeps, {
|
|
2033
2053
|
packageJson: rawDeps.packageJson,
|
|
2034
2054
|
lockfile: rawDeps.lockfile,
|
|
@@ -2037,6 +2057,7 @@ const uploadHandler = async (options) => {
|
|
|
2037
2057
|
// Empty custom set: upload payload identical to today's format.
|
|
2038
2058
|
}
|
|
2039
2059
|
const body = (0, appCodeFiles_1.buildImportBody)(codeToUpload, projectId, {
|
|
2060
|
+
space: appSpaceUuid,
|
|
2040
2061
|
createNew: options.createNew === true,
|
|
2041
2062
|
force: options.force,
|
|
2042
2063
|
});
|
package/dist/index.js
CHANGED
|
@@ -413,8 +413,11 @@ const uploadCommand = commander_1.program
|
|
|
413
413
|
.option('--include-virtual-views', 'Include the virtual views used by the charts and dashboards being uploaded', false)
|
|
414
414
|
.option('--validate', 'Validate charts and dashboards after upload', false)
|
|
415
415
|
.option('--gzip', 'Enable gzip compression for request bodies', false)
|
|
416
|
-
.option('--apps <appReferences...>', 'Upload only the specified data apps, by slug
|
|
416
|
+
.option('--apps <appReferences...>', 'Upload only the specified data apps, by slug (the app folder name), app URL, or UUID (enterprise). URL and UUID refs are resolved against the target project.')
|
|
417
417
|
.option('--include-apps', 'Upload all app folders on disk (enterprise).', false)
|
|
418
|
+
.option('--apps-only', 'Upload only data apps, skipping charts, dashboards, and space reconciliation. Requires --apps <appReferences...> or --include-apps.', false)
|
|
419
|
+
.option('--allow-custom-dependencies', 'Approve uploading apps that declare custom dependencies without prompting. Required for non-interactive runs of such uploads.', false)
|
|
420
|
+
.option('--app-space <spaceRef>', 'Space (slug or UUID) for data apps this upload creates. Existing apps keep their space.')
|
|
418
421
|
.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)
|
|
419
422
|
.option('--organization', 'upload all organization-scoped resources without selecting a project', false)
|
|
420
423
|
.option('--send-invites', 'send invitations to eligible pending users during organization upload', false);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.80.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/
|
|
45
|
-
"@lightdash/
|
|
44
|
+
"@lightdash/common": "1.80.0",
|
|
45
|
+
"@lightdash/warehouses": "1.80.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/inquirer": "8.2.4",
|