@lightdash/cli 1.34.0 → 1.36.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DataAppCode, type DataAppCodeFile, type DataAppContext, type DataAppDependencies, type ImportAppCodeRequestBody } from '@lightdash/common';
|
|
1
|
+
import { type DataAppCode, type DataAppCodeFile, type DataAppContext, type DataAppDependencies, type DataAppManifest, type ImportAppCodeRequestBody } from '@lightdash/common';
|
|
2
2
|
/**
|
|
3
3
|
* Derives a collision-safe folder name for a data app.
|
|
4
4
|
* Uses the slugified app name. If that slug is already taken by another app,
|
|
@@ -14,14 +14,10 @@ export declare const buildImportBody: (code: DataAppCode, targetProjectUuid: str
|
|
|
14
14
|
createNew?: boolean;
|
|
15
15
|
}) => ImportAppCodeRequestBody;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* The persistent slug from a slug-aware server is the folder name (stable
|
|
18
|
+
* across app renames). Pre-slug servers fall back to name-derived folders.
|
|
19
19
|
*/
|
|
20
|
-
export declare const
|
|
21
|
-
appUuid: string;
|
|
22
|
-
projectUuid: string;
|
|
23
|
-
version: number;
|
|
24
|
-
}) => Promise<void>;
|
|
20
|
+
export declare const resolveAppFolderName: (manifest: DataAppManifest, takenFolders: Set<string>) => string;
|
|
25
21
|
/**
|
|
26
22
|
* Writes server-provided dependency files as the app folder's package.json +
|
|
27
23
|
* pnpm-lock.yaml. Called after the scaffold write so they override the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appCodeFiles.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/appCodeFiles.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"appCodeFiles.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/appCodeFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAChC,MAAM,mBAAmB,CAAC;AAO3B;;;;GAIG;AACH,eAAO,MAAM,aAAa,SAChB,MAAM,WACH,MAAM,gBACD,GAAG,CAAC,MAAM,CAAC,KAC1B,MAcF,CAAC;AAEF,eAAO,MAAM,eAAe,QACnB,MAAM,SACJ,eAAe,EAAE,KACzB,OAAO,CAAC,IAAI,CAsBd,CAAC;AAEF,eAAO,MAAM,iBAAiB,QACrB,MAAM,WACF,cAAc,KACxB,OAAO,CAAC,IAAI,CASd,CAAC;AAEF,eAAO,MAAM,gBAAgB,QACpB,MAAM,QACL,WAAW,KAClB,OAAO,CAAC,IAAI,CAWd,CAAC;AAmCF,eAAO,MAAM,eAAe,SAClB,WAAW,qBACE,MAAM,QACnB;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,KAC5D,wBAoBF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,aACnB,eAAe,gBACX,GAAG,CAAC,MAAM,CAAC,KAC1B,MAgBF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAC1B,MAAM,QACL,mBAAmB,KAC1B,OAAO,CAAC,IAAI,CAGd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IAKpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAC3B,MAAM,KACZ,OAAO,CAAC,oBAAoB,GAAG,IAAI,CA6BrC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,eAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,wBACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7C,MAAM,EAOH,CAAC;AAEP;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,yBACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,eAC/B,MAAM,KACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAgBvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,SAC3B,WAAW,cACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAC5B,mBAAmB,KAC1B,WAC4E,CAAC;AAEhF,eAAO,MAAM,iBAAiB,QAAe,MAAM,KAAG,OAAO,CAAC,WAAW,CAuBxE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readBundleFromDir = exports.attachDependenciesToCode = exports.applySdkMirrorToTemplateDeps = exports.buildDepsWarningLines = exports.readDependenciesFromDir = exports.writeDependenciesToDir = exports.
|
|
3
|
+
exports.readBundleFromDir = exports.attachDependenciesToCode = exports.applySdkMirrorToTemplateDeps = exports.buildDepsWarningLines = exports.readDependenciesFromDir = exports.writeDependenciesToDir = exports.resolveAppFolderName = exports.buildImportBody = exports.writeBundleToDir = exports.writeContextToDir = exports.writeFilesToDir = exports.appFolderName = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@lightdash/common");
|
|
6
6
|
const fs_1 = require("fs");
|
|
@@ -95,25 +95,37 @@ const buildImportBody = (code, targetProjectUuid, opts) => {
|
|
|
95
95
|
targetAppUuid = opts.app;
|
|
96
96
|
}
|
|
97
97
|
else if (targetProjectUuid === code.manifest.projectUuid) {
|
|
98
|
+
// Pre-slug bundles carry an appUuid (uuid-fallback identity, and
|
|
99
|
+
// same-project append on pre-slug servers). Slug-only bundles yield
|
|
100
|
+
// undefined here — slug-aware servers resolve by slug instead.
|
|
98
101
|
targetAppUuid = code.manifest.appUuid;
|
|
99
102
|
}
|
|
100
103
|
return {
|
|
101
104
|
code,
|
|
102
105
|
targetAppUuid,
|
|
103
106
|
spaceUuid: opts.space,
|
|
107
|
+
...(opts.createNew ? { createNew: true } : {}),
|
|
104
108
|
};
|
|
105
109
|
};
|
|
106
110
|
exports.buildImportBody = buildImportBody;
|
|
107
111
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
112
|
+
* The persistent slug from a slug-aware server is the folder name (stable
|
|
113
|
+
* across app renames). Pre-slug servers fall back to name-derived folders.
|
|
110
114
|
*/
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
const resolveAppFolderName = (manifest, takenFolders) => {
|
|
116
|
+
// Defense-in-depth: a server of unknown version, or a hand-tampered
|
|
117
|
+
// manifest on disk, must not steer the local write path via an
|
|
118
|
+
// unvalidated slug (e.g. `../../etc`) — only trust it once it passes the
|
|
119
|
+
// same shape check the server enforces on upload.
|
|
120
|
+
if (manifest.slug !== undefined && (0, common_1.isValidDataAppSlug)(manifest.slug)) {
|
|
121
|
+
return manifest.slug;
|
|
122
|
+
}
|
|
123
|
+
// The fallback needs a uuid for its collision/untitled suffixes. Real
|
|
124
|
+
// pre-slug servers always emit appUuid; only a tampered slug-only
|
|
125
|
+
// manifest lands here without one.
|
|
126
|
+
return (0, exports.appFolderName)(manifest.name, manifest.appUuid ?? 'unknown', takenFolders);
|
|
115
127
|
};
|
|
116
|
-
exports.
|
|
128
|
+
exports.resolveAppFolderName = resolveAppFolderName;
|
|
117
129
|
/**
|
|
118
130
|
* Writes server-provided dependency files as the app folder's package.json +
|
|
119
131
|
* pnpm-lock.yaml. Called after the scaffold write so they override the
|
|
@@ -73,6 +73,17 @@ describe('buildImportBody', () => {
|
|
|
73
73
|
});
|
|
74
74
|
expect(body.targetAppUuid).toBeUndefined();
|
|
75
75
|
});
|
|
76
|
+
it('includes createNew: true in the body when the flag is set', () => {
|
|
77
|
+
const code = makeCode('app-uuid-1', 'proj-uuid-1');
|
|
78
|
+
const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', { createNew: true });
|
|
79
|
+
expect(body.createNew).toBe(true);
|
|
80
|
+
expect(body.targetAppUuid).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
it('omits the createNew key entirely when the flag is not set', () => {
|
|
83
|
+
const code = makeCode('app-uuid-1', 'proj-uuid-1');
|
|
84
|
+
const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', {});
|
|
85
|
+
expect('createNew' in body).toBe(false);
|
|
86
|
+
});
|
|
76
87
|
});
|
|
77
88
|
const bundle = {
|
|
78
89
|
manifest: {
|
|
@@ -134,23 +145,6 @@ it('upload reads back only src/ files, ignoring scaffolding and context', async
|
|
|
134
145
|
expect(read.files.every((f) => f.path.startsWith('src/'))).toBe(true);
|
|
135
146
|
expect(read.files.map((f) => f.path).sort()).toEqual(bundle.files.map((f) => f.path).sort());
|
|
136
147
|
});
|
|
137
|
-
describe('retargetManifest', () => {
|
|
138
|
-
it('rewrites appUuid, projectUuid and version, preserving other fields', async () => {
|
|
139
|
-
const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
|
|
140
|
-
await (0, appCodeFiles_1.writeBundleToDir)(dir, bundle);
|
|
141
|
-
await (0, appCodeFiles_1.retargetManifest)(dir, {
|
|
142
|
-
appUuid: 'new-app-uuid',
|
|
143
|
-
projectUuid: 'new-proj-uuid',
|
|
144
|
-
version: 1,
|
|
145
|
-
});
|
|
146
|
-
const read = await (0, appCodeFiles_1.readBundleFromDir)(dir);
|
|
147
|
-
expect(read.manifest.appUuid).toBe('new-app-uuid');
|
|
148
|
-
expect(read.manifest.projectUuid).toBe('new-proj-uuid');
|
|
149
|
-
expect(read.manifest.version).toBe(1);
|
|
150
|
-
expect(read.manifest.name).toBe('N');
|
|
151
|
-
expect(read.manifest.downloadedAt).toBe('2026-06-30T00:00:00.000Z');
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
148
|
it('throws a clear error when the manifest is not valid YAML', async () => {
|
|
155
149
|
const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
|
|
156
150
|
await fs_1.promises.writeFile(path.join(dir, 'lightdash-app.yml'), 'foo: [bar', 'utf-8');
|
|
@@ -214,6 +208,32 @@ describe('appFolderName', () => {
|
|
|
214
208
|
expect((0, appCodeFiles_1.appFolderName)('!!!', 'abcd1234-ef56-7890-ab12-cdef01234567', new Set())).toBe('untitled-app-abcd1234');
|
|
215
209
|
});
|
|
216
210
|
});
|
|
211
|
+
describe('resolveAppFolderName', () => {
|
|
212
|
+
it('uses the manifest slug when present', () => {
|
|
213
|
+
const code = makeCode('app-uuid-1', 'proj-uuid-1');
|
|
214
|
+
expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, slug: 'sales-app' }, new Set())).toBe('sales-app');
|
|
215
|
+
});
|
|
216
|
+
it('falls back to appFolderName when the manifest has no slug', () => {
|
|
217
|
+
const uuid = 'abcd1234-ef56-7890-ab12-cdef01234567';
|
|
218
|
+
const code = makeCode(uuid, 'proj-uuid-1');
|
|
219
|
+
expect((0, appCodeFiles_1.resolveAppFolderName)(code.manifest, new Set())).toBe((0, appCodeFiles_1.appFolderName)(code.manifest.name, uuid, new Set()));
|
|
220
|
+
});
|
|
221
|
+
it('uses the slug for uuid-free manifests (slug-aware servers)', () => {
|
|
222
|
+
const code = makeCode('app-uuid-1', 'proj-uuid-1');
|
|
223
|
+
expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, appUuid: undefined, slug: 'sales-app' }, new Set())).toBe('sales-app');
|
|
224
|
+
});
|
|
225
|
+
// Defense-in-depth: a tampered manifest (or a server of unknown version)
|
|
226
|
+
// must not be able to steer the write path via an invalid slug.
|
|
227
|
+
it('falls back to appFolderName when the slug is a path-traversal attempt', () => {
|
|
228
|
+
const uuid = 'abcd1234-ef56-7890-ab12-cdef01234567';
|
|
229
|
+
const code = makeCode(uuid, 'proj-uuid-1');
|
|
230
|
+
expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, slug: '../../../../tmp/evil' }, new Set())).toBe((0, appCodeFiles_1.appFolderName)(code.manifest.name, uuid, new Set()));
|
|
231
|
+
});
|
|
232
|
+
it('still uses a valid manifest slug (unaffected by the new validation)', () => {
|
|
233
|
+
const code = makeCode('app-uuid-1', 'proj-uuid-1');
|
|
234
|
+
expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, slug: 'sales-app-2' }, new Set())).toBe('sales-app-2');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
217
237
|
// ─── readDependenciesFromDir ──────────────────────────────────────────────────
|
|
218
238
|
const makePackageJson = (deps = {}) => JSON.stringify({ name: 'test-app', dependencies: deps });
|
|
219
239
|
const LOCKFILE_CONTENT = 'lockfileVersion: 9.0\n\npackages:\n react@19.0.0:\n';
|
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
import { type DataAppCodeDownload } from '@lightdash/common';
|
|
1
|
+
import { type DataAppCodeDownload, type DataAppManifest } from '@lightdash/common';
|
|
2
2
|
export declare const DEFAULT_APPS_LIMIT = 50;
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const getDataAppReference: (reference: string) => string;
|
|
4
4
|
export declare const getDataAppUploadFilter: (references: string[], includeApps: boolean) => Set<string> | null;
|
|
5
|
+
/**
|
|
6
|
+
* --include-apps uploads every folder; explicit --apps references filter by
|
|
7
|
+
* the manifest's slug or appUuid (references may be either, or app URLs).
|
|
8
|
+
*/
|
|
9
|
+
export declare const uploadFilterMatches: (filter: Set<string> | null, manifest: DataAppManifest) => boolean;
|
|
10
|
+
/** The filter entries this manifest satisfies (for unmatched-ref reporting). */
|
|
11
|
+
export declare const matchedUploadRefs: (filter: Set<string>, manifest: DataAppManifest) => string[];
|
|
12
|
+
/**
|
|
13
|
+
* Warning for --apps references that matched no local app folder. Uuid-shaped
|
|
14
|
+
* refs (including refs parsed out of app URLs) get the slug-identity
|
|
15
|
+
* explanation: id-free bundles can only be selected by slug.
|
|
16
|
+
*/
|
|
17
|
+
export declare const unmatchedUploadRefsWarning: (unmatched: string[]) => string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Shown when the upload response carries no slug even though the bundle sent
|
|
20
|
+
* one — the server predates slug identity, so it ignored the slug and matched
|
|
21
|
+
* (or created) by uuid only. A same-slug upload may have just created a
|
|
22
|
+
* duplicate app instead of appending.
|
|
23
|
+
*/
|
|
24
|
+
export declare const preSlugServerHint: (folder: string) => string;
|
|
5
25
|
/**
|
|
6
26
|
* Resolves the --apps-limit flag. Commander passes the raw string (or
|
|
7
27
|
* undefined when the flag was not given, so an explicit flag is
|
|
@@ -23,15 +43,15 @@ export type AppsDownloadSelection = {
|
|
|
23
43
|
mode: 'none';
|
|
24
44
|
} | {
|
|
25
45
|
mode: 'explicit';
|
|
26
|
-
|
|
46
|
+
appRefs: string[];
|
|
27
47
|
} | {
|
|
28
48
|
mode: 'list-all';
|
|
29
|
-
|
|
49
|
+
extraAppRefs: string[];
|
|
30
50
|
};
|
|
31
51
|
/**
|
|
32
|
-
* Explicit
|
|
33
|
-
* app in the project via the project-wide apps endpoint, falling back
|
|
34
|
-
* the space-scoped content API on servers that predate it.
|
|
52
|
+
* Explicit references (--apps) are fetched directly; --include-apps lists
|
|
53
|
+
* every app in the project via the project-wide apps endpoint, falling back
|
|
54
|
+
* to the space-scoped content API on servers that predate it.
|
|
35
55
|
*/
|
|
36
56
|
export declare const selectAppsToDownload: (request: {
|
|
37
57
|
apps?: string[];
|
|
@@ -45,8 +65,16 @@ export declare const capListedApps: (listedUuids: string[], limit: number) => {
|
|
|
45
65
|
* Pre-context servers return a download payload without `context`.
|
|
46
66
|
*/
|
|
47
67
|
export declare const ensureDownloadedAppContext: (appUuid: string, code: DataAppCodeDownload) => DataAppCodeDownload;
|
|
68
|
+
/**
|
|
69
|
+
* Shown after uploading a bundle whose manifest predates slug identity —
|
|
70
|
+
* uploads keep working via the uuid fallback, but the user should upgrade.
|
|
71
|
+
*/
|
|
72
|
+
export declare const preSlugUploadHint: (args: {
|
|
73
|
+
folder: string;
|
|
74
|
+
slug: string | undefined;
|
|
75
|
+
}) => string;
|
|
48
76
|
export type AppDownloadFailure = {
|
|
49
|
-
|
|
77
|
+
appRef: string;
|
|
50
78
|
message: string;
|
|
51
79
|
};
|
|
52
80
|
export type AppDownloadErrorOutcome = {
|
|
@@ -62,15 +90,6 @@ export type AppDownloadErrorOutcome = {
|
|
|
62
90
|
* message over a canned guess.
|
|
63
91
|
*/
|
|
64
92
|
export declare const classifyAppDownloadError: (err: unknown) => AppDownloadErrorOutcome;
|
|
65
|
-
/**
|
|
66
|
-
* Shown when a newly created app's folder manifest was NOT retargeted —
|
|
67
|
-
* spells out the consequence and the manual fix.
|
|
68
|
-
*/
|
|
69
|
-
export declare const manifestRetargetHint: (args: {
|
|
70
|
-
folder: string;
|
|
71
|
-
appUuid: string;
|
|
72
|
-
projectUuid: string;
|
|
73
|
-
}) => string;
|
|
74
93
|
/**
|
|
75
94
|
* Sums changes entries that represent actual upserts — excluding both
|
|
76
95
|
* 'skipped' and 'failed' keys so that failures don't suppress the
|
|
@@ -82,15 +101,6 @@ export declare const computeUpsertedTotal: (changes: Record<string, number>) =>
|
|
|
82
101
|
* items — the condition that should display the "all skipped" warning.
|
|
83
102
|
*/
|
|
84
103
|
export declare const shouldWarnAllSkipped: (changes: Record<string, number>) => boolean;
|
|
85
|
-
/**
|
|
86
|
-
* Determines how an app upload should proceed given a potential project
|
|
87
|
-
* mismatch between the manifest and the upload target.
|
|
88
|
-
*
|
|
89
|
-
* 'proceed' — upload immediately (same project, or --create-new).
|
|
90
|
-
* 'needs-confirmation' — projects differ and --create-new was not passed;
|
|
91
|
-
* caller must prompt (TTY) or reject (non-TTY).
|
|
92
|
-
*/
|
|
93
|
-
export declare const classifyAppUpload: (manifestProjectUuid: string, targetProjectUuid: string, createNew: boolean) => 'proceed' | 'needs-confirmation';
|
|
94
104
|
export declare const appsDownloadSummary: (successCount: number, total: number, failures: AppDownloadFailure[], appsDir: string, skippedNotBuiltCount: number) => {
|
|
95
105
|
ok: boolean;
|
|
96
106
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appsDownload.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/appsDownload.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"appsDownload.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/appsDownload.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.appsDownloadSummary = exports.
|
|
3
|
+
exports.appsDownloadSummary = exports.shouldWarnAllSkipped = exports.computeUpsertedTotal = exports.classifyAppDownloadError = exports.preSlugUploadHint = exports.ensureDownloadedAppContext = exports.capListedApps = exports.selectAppsToDownload = exports.shouldFallBackToSpaceScopedListing = exports.resolveAppsLimit = exports.preSlugServerHint = exports.unmatchedUploadRefsWarning = exports.matchedUploadRefs = exports.uploadFilterMatches = exports.getDataAppUploadFilter = exports.getDataAppReference = exports.DEFAULT_APPS_LIMIT = void 0;
|
|
4
4
|
const common_1 = require("@lightdash/common");
|
|
5
5
|
const uuid_1 = require("uuid");
|
|
6
6
|
exports.DEFAULT_APPS_LIMIT = 50;
|
|
7
|
-
const
|
|
7
|
+
const getDataAppReference = (reference) => {
|
|
8
8
|
let url;
|
|
9
9
|
try {
|
|
10
10
|
url = new URL(reference);
|
|
@@ -20,9 +20,42 @@ const getDataAppUuidFromReference = (reference) => {
|
|
|
20
20
|
(0, uuid_1.validate)(pathSegments[index + 1]));
|
|
21
21
|
return appSegmentIndex >= 0 ? pathSegments[appSegmentIndex + 1] : reference;
|
|
22
22
|
};
|
|
23
|
-
exports.
|
|
24
|
-
const getDataAppUploadFilter = (references, includeApps) => includeApps ? null : new Set(references.map(exports.
|
|
23
|
+
exports.getDataAppReference = getDataAppReference;
|
|
24
|
+
const getDataAppUploadFilter = (references, includeApps) => includeApps ? null : new Set(references.map(exports.getDataAppReference));
|
|
25
25
|
exports.getDataAppUploadFilter = getDataAppUploadFilter;
|
|
26
|
+
/**
|
|
27
|
+
* --include-apps uploads every folder; explicit --apps references filter by
|
|
28
|
+
* the manifest's slug or appUuid (references may be either, or app URLs).
|
|
29
|
+
*/
|
|
30
|
+
const uploadFilterMatches = (filter, manifest) => filter === null ||
|
|
31
|
+
(manifest.appUuid !== undefined && filter.has(manifest.appUuid)) ||
|
|
32
|
+
(manifest.slug !== undefined && filter.has(manifest.slug));
|
|
33
|
+
exports.uploadFilterMatches = uploadFilterMatches;
|
|
34
|
+
/** The filter entries this manifest satisfies (for unmatched-ref reporting). */
|
|
35
|
+
const matchedUploadRefs = (filter, manifest) => [manifest.appUuid, manifest.slug].filter((ref) => ref !== undefined && filter.has(ref));
|
|
36
|
+
exports.matchedUploadRefs = matchedUploadRefs;
|
|
37
|
+
/**
|
|
38
|
+
* Warning for --apps references that matched no local app folder. Uuid-shaped
|
|
39
|
+
* refs (including refs parsed out of app URLs) get the slug-identity
|
|
40
|
+
* explanation: id-free bundles can only be selected by slug.
|
|
41
|
+
*/
|
|
42
|
+
const unmatchedUploadRefsWarning = (unmatched) => {
|
|
43
|
+
if (unmatched.length === 0)
|
|
44
|
+
return null;
|
|
45
|
+
const base = `No local app folder matched: ${unmatched.join(', ')}.`;
|
|
46
|
+
return unmatched.some((ref) => (0, uuid_1.validate)(ref))
|
|
47
|
+
? `${base} Bundles downloaded with slug identity carry no uuid — select them by slug (the folder name) instead of a UUID or app URL.`
|
|
48
|
+
: base;
|
|
49
|
+
};
|
|
50
|
+
exports.unmatchedUploadRefsWarning = unmatchedUploadRefsWarning;
|
|
51
|
+
/**
|
|
52
|
+
* Shown when the upload response carries no slug even though the bundle sent
|
|
53
|
+
* one — the server predates slug identity, so it ignored the slug and matched
|
|
54
|
+
* (or created) by uuid only. A same-slug upload may have just created a
|
|
55
|
+
* duplicate app instead of appending.
|
|
56
|
+
*/
|
|
57
|
+
const preSlugServerHint = (folder) => `This server predates slug-based app identity, so "${folder}" was matched by uuid only. If you expected to update an existing app, verify no duplicate was created, and upgrade the server (or use a matching CLI version).`;
|
|
58
|
+
exports.preSlugServerHint = preSlugServerHint;
|
|
26
59
|
/**
|
|
27
60
|
* Resolves the --apps-limit flag. Commander passes the raw string (or
|
|
28
61
|
* undefined when the flag was not given, so an explicit flag is
|
|
@@ -53,17 +86,17 @@ exports.resolveAppsLimit = resolveAppsLimit;
|
|
|
53
86
|
const shouldFallBackToSpaceScopedListing = (err) => err instanceof common_1.LightdashError && err.statusCode === 404;
|
|
54
87
|
exports.shouldFallBackToSpaceScopedListing = shouldFallBackToSpaceScopedListing;
|
|
55
88
|
/**
|
|
56
|
-
* Explicit
|
|
57
|
-
* app in the project via the project-wide apps endpoint, falling back
|
|
58
|
-
* the space-scoped content API on servers that predate it.
|
|
89
|
+
* Explicit references (--apps) are fetched directly; --include-apps lists
|
|
90
|
+
* every app in the project via the project-wide apps endpoint, falling back
|
|
91
|
+
* to the space-scoped content API on servers that predate it.
|
|
59
92
|
*/
|
|
60
93
|
const selectAppsToDownload = (request) => {
|
|
61
|
-
const
|
|
94
|
+
const explicitRefs = (request.apps ?? []).map(exports.getDataAppReference);
|
|
62
95
|
if (request.includeApps) {
|
|
63
|
-
return { mode: 'list-all',
|
|
96
|
+
return { mode: 'list-all', extraAppRefs: explicitRefs };
|
|
64
97
|
}
|
|
65
|
-
if (
|
|
66
|
-
return { mode: 'explicit',
|
|
98
|
+
if (explicitRefs.length > 0) {
|
|
99
|
+
return { mode: 'explicit', appRefs: explicitRefs };
|
|
67
100
|
}
|
|
68
101
|
return { mode: 'none' };
|
|
69
102
|
};
|
|
@@ -83,6 +116,14 @@ const ensureDownloadedAppContext = (appUuid, code) => {
|
|
|
83
116
|
return code;
|
|
84
117
|
};
|
|
85
118
|
exports.ensureDownloadedAppContext = ensureDownloadedAppContext;
|
|
119
|
+
/**
|
|
120
|
+
* Shown after uploading a bundle whose manifest predates slug identity —
|
|
121
|
+
* uploads keep working via the uuid fallback, but the user should upgrade.
|
|
122
|
+
*/
|
|
123
|
+
const preSlugUploadHint = (args) => `${args.folder}/lightdash-app.yml predates slug identity. Re-download the app to upgrade${args.slug !== undefined
|
|
124
|
+
? ` (or add \`slug: ${args.slug}\` to lightdash-app.yml)`
|
|
125
|
+
: ''}. Uploads keep working via uuid matching meanwhile.`;
|
|
126
|
+
exports.preSlugUploadHint = preSlugUploadHint;
|
|
86
127
|
/**
|
|
87
128
|
* A 404 saying the app has no ready version means it exists but never
|
|
88
129
|
* finished a successful build — nothing to download, so it's a skip, not a
|
|
@@ -108,12 +149,6 @@ const classifyAppDownloadError = (err) => {
|
|
|
108
149
|
return { kind: 'fail', message: (0, common_1.getErrorMessage)(err) };
|
|
109
150
|
};
|
|
110
151
|
exports.classifyAppDownloadError = classifyAppDownloadError;
|
|
111
|
-
/**
|
|
112
|
-
* Shown when a newly created app's folder manifest was NOT retargeted —
|
|
113
|
-
* spells out the consequence and the manual fix.
|
|
114
|
-
*/
|
|
115
|
-
const manifestRetargetHint = (args) => `${args.folder}/lightdash-app.yml still targets the original app, so future uploads here will ask to create again. To update the new app instead, set appUuid: ${args.appUuid} and projectUuid: ${args.projectUuid} in lightdash-app.yml.`;
|
|
116
|
-
exports.manifestRetargetHint = manifestRetargetHint;
|
|
117
152
|
/**
|
|
118
153
|
* Sums changes entries that represent actual upserts — excluding both
|
|
119
154
|
* 'skipped' and 'failed' keys so that failures don't suppress the
|
|
@@ -134,21 +169,6 @@ const shouldWarnAllSkipped = (changes) => {
|
|
|
134
169
|
return totalSkipped > 0 && (0, exports.computeUpsertedTotal)(changes) === 0;
|
|
135
170
|
};
|
|
136
171
|
exports.shouldWarnAllSkipped = shouldWarnAllSkipped;
|
|
137
|
-
/**
|
|
138
|
-
* Determines how an app upload should proceed given a potential project
|
|
139
|
-
* mismatch between the manifest and the upload target.
|
|
140
|
-
*
|
|
141
|
-
* 'proceed' — upload immediately (same project, or --create-new).
|
|
142
|
-
* 'needs-confirmation' — projects differ and --create-new was not passed;
|
|
143
|
-
* caller must prompt (TTY) or reject (non-TTY).
|
|
144
|
-
*/
|
|
145
|
-
const classifyAppUpload = (manifestProjectUuid, targetProjectUuid, createNew) => {
|
|
146
|
-
if (createNew || manifestProjectUuid === targetProjectUuid) {
|
|
147
|
-
return 'proceed';
|
|
148
|
-
}
|
|
149
|
-
return 'needs-confirmation';
|
|
150
|
-
};
|
|
151
|
-
exports.classifyAppUpload = classifyAppUpload;
|
|
152
172
|
const appsDownloadSummary = (successCount, total, failures, appsDir, skippedNotBuiltCount) => {
|
|
153
173
|
const attempted = total - skippedNotBuiltCount;
|
|
154
174
|
const skippedSuffix = skippedNotBuiltCount > 0
|
|
@@ -161,7 +181,7 @@ const appsDownloadSummary = (successCount, total, failures, appsDir, skippedNotB
|
|
|
161
181
|
return {
|
|
162
182
|
ok: false,
|
|
163
183
|
message: `${base} — ${failures.length} failed`,
|
|
164
|
-
failureLines: failures.map((failure) => ` ✖ ${failure.
|
|
184
|
+
failureLines: failures.map((failure) => ` ✖ ${failure.appRef}: ${failure.message}`),
|
|
165
185
|
};
|
|
166
186
|
};
|
|
167
187
|
exports.appsDownloadSummary = appsDownloadSummary;
|