@lightdash/cli 1.79.1 → 1.81.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.
@@ -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
- const hasFilters = hasContentFilters(options);
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
- const uploadFilter = isExplicitAppSelection
1893
+ let uploadFilter = isExplicitAppSelection
1879
1894
  ? (0, appsDownload_1.getDataAppUploadFilter)(explicitAppReferences, options.includeApps === true)
1880
1895
  : null;
1881
- // The manifest always names the source project, so comparing it
1882
- // to the target would re-push every run — list the target instead.
1883
- let presence = {
1884
- kind: 'unknown',
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
- // An older server answers without slugs; that is not an
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
- // eslint-disable-next-line no-await-in-loop
1961
- const folderChanged = await (0, appCodeFiles_1.appFolderNeedsUpdating)(folderPath, code.manifest);
1962
- if (!(0, appsDownload_1.shouldAutoPushApp)({
1963
- manifest: code.manifest,
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 (process.stdin.isTTY && process.stdout.isTTY) {
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: true,
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 — the app folder name (enterprise).')
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.79.1",
3
+ "version": "1.81.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/warehouses": "1.79.1",
45
- "@lightdash/common": "1.79.1"
44
+ "@lightdash/common": "1.81.0",
45
+ "@lightdash/warehouses": "1.81.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/inquirer": "8.2.4",