@lightdash/cli 3.69.3 → 3.71.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.build +1 -1
- package/dist/handlers/apps/preview.d.ts +3 -0
- package/dist/handlers/apps/preview.d.ts.map +1 -1
- package/dist/handlers/apps/preview.js +29 -1
- package/dist/handlers/apps/previewProxy.d.ts +10 -0
- package/dist/handlers/apps/previewProxy.d.ts.map +1 -1
- package/dist/handlers/apps/previewProxy.js +90 -1
- package/dist/index.js +1 -0
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DataAppManifest } from '@lightdash/common';
|
|
2
|
+
import { type CompiledQueryLogEntry } from './previewProxy';
|
|
2
3
|
/**
|
|
3
4
|
* Environment passed to the `npm run dev` child process. Only the small set of
|
|
4
5
|
* host variables needed to launch a portable child process is inherited. In
|
|
@@ -70,9 +71,11 @@ type AppsPreviewOptions = {
|
|
|
70
71
|
url?: string;
|
|
71
72
|
token?: string;
|
|
72
73
|
port?: string;
|
|
74
|
+
showSql: boolean;
|
|
73
75
|
assumeYes: boolean;
|
|
74
76
|
verbose: boolean;
|
|
75
77
|
};
|
|
78
|
+
export declare const formatCompiledQueryLog: (entry: CompiledQueryLogEntry) => string;
|
|
76
79
|
export declare const resolvePreviewPort: (raw: string | undefined) => number | undefined;
|
|
77
80
|
export declare const appsPreviewHandler: (pathArg: string | undefined, options: AppsPreviewOptions) => Promise<void>;
|
|
78
81
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,eAAe,EACvB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,eAAe,EACvB,MAAM,mBAAmB,CAAC;AAc3B,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE/E;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,SAAU;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACjC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CA+CxB,CAAC;AAEF,eAAO,MAAM,wBAAwB,SAAgB;IACjD,QAAQ,EAAE,eAAe,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,KAAG,OAAO,CAAC,MAAM,EAAE,CA8EnB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,WACtC,MAAM,KACf,OAAO,CAAC,IAAI,CAWd,CAAC;AAEF,eAAO,MAAM,oBAAoB,SAAgB;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;CACf,KAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CA2BlD,CAAC;AAQF,eAAO,MAAM,wBAAwB,WACzB,MAAM,KACf,OAAO,CAAC,IAAI,CAMd,CAAC;AAyBF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,SAAgB;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;CACtB,KAAG,OAAO,CAAC,IAAI,CAwEf,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,SAAU;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACrB,KAAG,MACgT,CAAC;AAErT,eAAO,MAAM,uBAAuB,SAAgB;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,KAAG,OAAO,CAAC,OAAO,CAoBlB,CAAC;AAKF,eAAO,MAAM,6BAA6B,WAC9B,MAAM,KACf,OAAO,CAAC,OAAO,CAmBjB,CAAC;AAkBF,KAAK,kBAAkB,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,sBAAsB,UACxB,qBAAqB,KAC7B,MA4BF,CAAC;AAEF,eAAO,MAAM,kBAAkB,QACtB,MAAM,GAAG,SAAS,KACxB,MAAM,GAAG,SASX,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAClB,MAAM,GAAG,SAAS,WAClB,kBAAkB,KAC5B,OAAO,CAAC,IAAI,CAiJd,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.appsPreviewHandler = exports.resolvePreviewPort = exports.removeLegacyPreviewCredential = exports.preflightPreviewRequest = exports.projectNotFoundMessage = exports.ensureNodeModules = exports.assertNodeModulesPresent = exports.resolvePreviewTarget = exports.assertScaffoldingSupportsPreviewProxy = exports.fetchBrowserImageOrigins = exports.buildPreviewChildEnv = void 0;
|
|
3
|
+
exports.appsPreviewHandler = exports.resolvePreviewPort = exports.formatCompiledQueryLog = exports.removeLegacyPreviewCredential = exports.preflightPreviewRequest = exports.projectNotFoundMessage = exports.ensureNodeModules = exports.assertNodeModulesPresent = exports.resolvePreviewTarget = exports.assertScaffoldingSupportsPreviewProxy = exports.fetchBrowserImageOrigins = exports.buildPreviewChildEnv = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@lightdash/common");
|
|
6
6
|
const crypto_1 = require("crypto");
|
|
@@ -328,6 +328,31 @@ const isUserStoppedDevServer = (error) => {
|
|
|
328
328
|
exitCode === 130 ||
|
|
329
329
|
exitCode === 143);
|
|
330
330
|
};
|
|
331
|
+
const formatCompiledQueryLog = (entry) => {
|
|
332
|
+
const fields = [
|
|
333
|
+
entry.dimensions.length > 0
|
|
334
|
+
? `dimensions: ${entry.dimensions.join(', ')}`
|
|
335
|
+
: null,
|
|
336
|
+
entry.metrics.length > 0
|
|
337
|
+
? `metrics: ${entry.metrics.join(', ')}`
|
|
338
|
+
: null,
|
|
339
|
+
].filter((line) => line !== null);
|
|
340
|
+
const sql = entry.compiledSql !== null
|
|
341
|
+
? entry.compiledSql.trim()
|
|
342
|
+
: 'Could not compile this query (see any errors above, or check your access to the explore).';
|
|
343
|
+
return [
|
|
344
|
+
styles.title(`\n— Query against explore '${entry.exploreName}'`),
|
|
345
|
+
...fields.map((line) => styles.secondary(` ${line}`)),
|
|
346
|
+
...entry.compilationErrors.map((error) => styles.warning(` ${error}`)),
|
|
347
|
+
...(entry.runtimeOnlyTransformations.length > 0
|
|
348
|
+
? [
|
|
349
|
+
styles.warning(` Note: this query also applies ${entry.runtimeOnlyTransformations.join(' and ')} at run time, which the SQL below does not include.`),
|
|
350
|
+
]
|
|
351
|
+
: []),
|
|
352
|
+
sql,
|
|
353
|
+
].join('\n');
|
|
354
|
+
};
|
|
355
|
+
exports.formatCompiledQueryLog = formatCompiledQueryLog;
|
|
331
356
|
const resolvePreviewPort = (raw) => {
|
|
332
357
|
if (raw === undefined)
|
|
333
358
|
return undefined;
|
|
@@ -412,6 +437,9 @@ const appsPreviewHandler = async (pathArg, options) => {
|
|
|
412
437
|
proxyAuthorization,
|
|
413
438
|
projectUuid: target.projectUuid,
|
|
414
439
|
nonce: proxyNonce,
|
|
440
|
+
onCompiledQuery: options.showSql
|
|
441
|
+
? (entry) => globalState_1.default.log((0, exports.formatCompiledQueryLog)(entry))
|
|
442
|
+
: undefined,
|
|
415
443
|
});
|
|
416
444
|
globalState_1.default.log(`Auth proxy on 127.0.0.1:${proxy.port} (not the app URL) — your credential is not passed to the app; SDK traffic is restricted to project ${target.projectUuid}.`);
|
|
417
445
|
globalState_1.default.log(styles.warning(`Preview renders YOUR data with YOUR permissions and user attributes — viewers of the deployed app may see different data.`));
|
|
@@ -3,6 +3,15 @@ export type PreviewProxyHandle = {
|
|
|
3
3
|
port: number;
|
|
4
4
|
close: () => Promise<void>;
|
|
5
5
|
};
|
|
6
|
+
/** What --show-sql reports for each metric query the previewed app runs. */
|
|
7
|
+
export type CompiledQueryLogEntry = {
|
|
8
|
+
exploreName: string;
|
|
9
|
+
dimensions: string[];
|
|
10
|
+
metrics: string[];
|
|
11
|
+
compiledSql: string | null;
|
|
12
|
+
compilationErrors: string[];
|
|
13
|
+
runtimeOnlyTransformations: string[];
|
|
14
|
+
};
|
|
6
15
|
/**
|
|
7
16
|
* Loopback proxy that owns the preview credential. The vite dev server
|
|
8
17
|
* forwards /api traffic here; only routes a deployed app could reach through
|
|
@@ -16,5 +25,6 @@ export declare const startPreviewProxy: (args: {
|
|
|
16
25
|
proxyAuthorization?: string;
|
|
17
26
|
projectUuid: string;
|
|
18
27
|
nonce: string;
|
|
28
|
+
onCompiledQuery?: (entry: CompiledQueryLogEntry) => void;
|
|
19
29
|
}) => Promise<PreviewProxyHandle>;
|
|
20
30
|
//# sourceMappingURL=previewProxy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"previewProxy.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/previewProxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"previewProxy.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/previewProxy.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,0BAA0B,8BAA8B,CAAC;AA6CtE,MAAM,MAAM,kBAAkB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,qBAAqB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAG5B,0BAA0B,EAAE,MAAM,EAAE,CAAC;CACxC,CAAC;AAyCF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,SAAU;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IAGd,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC5D,KAAG,OAAO,CAAC,kBAAkB,CAwU7B,CAAC"}
|
|
@@ -111,6 +111,85 @@ const startPreviewProxy = (args) => {
|
|
|
111
111
|
upstreamReq.on('error', () => resolve(null));
|
|
112
112
|
upstreamReq.end();
|
|
113
113
|
});
|
|
114
|
+
// Resolves the parsed body of a successful upstream POST, or null.
|
|
115
|
+
const postUpstreamJson = (path, body) => new Promise((resolve) => {
|
|
116
|
+
const payload = Buffer.from(body, 'utf-8');
|
|
117
|
+
const upstreamReq = requestFn({
|
|
118
|
+
protocol: upstream.protocol,
|
|
119
|
+
hostname: upstream.hostname,
|
|
120
|
+
port: upstream.port || undefined,
|
|
121
|
+
path,
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: withUpstreamCredentials({
|
|
124
|
+
accept: 'application/json',
|
|
125
|
+
'content-type': 'application/json',
|
|
126
|
+
'content-length': payload.length,
|
|
127
|
+
}),
|
|
128
|
+
}, (upstreamRes) => {
|
|
129
|
+
readBody(upstreamRes).then((text) => {
|
|
130
|
+
const status = upstreamRes.statusCode ?? 502;
|
|
131
|
+
resolve(status >= 200 && status < 300
|
|
132
|
+
? parseJson(text)
|
|
133
|
+
: null);
|
|
134
|
+
}, () => resolve(null));
|
|
135
|
+
});
|
|
136
|
+
upstreamReq.on('error', () => resolve(null));
|
|
137
|
+
upstreamReq.end(payload);
|
|
138
|
+
});
|
|
139
|
+
// --show-sql: compile the metric query the app just ran (same payload the
|
|
140
|
+
// customer-facing compileQuery endpoint takes) and report the SQL.
|
|
141
|
+
const compileAndReport = async (requestBody) => {
|
|
142
|
+
const report = args.onCompiledQuery;
|
|
143
|
+
if (report === undefined)
|
|
144
|
+
return;
|
|
145
|
+
const parsed = parseJson(requestBody);
|
|
146
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
147
|
+
return;
|
|
148
|
+
const { query, parameters, usePreAggregateCache, pivotConfiguration, dateZoom, dashboardFilters, } = parsed;
|
|
149
|
+
if (typeof query !== 'object' || query === null)
|
|
150
|
+
return;
|
|
151
|
+
const metricQuery = query;
|
|
152
|
+
if (typeof metricQuery.exploreName !== 'string')
|
|
153
|
+
return;
|
|
154
|
+
const toStrings = (value) => Array.isArray(value)
|
|
155
|
+
? value.filter((item) => typeof item === 'string')
|
|
156
|
+
: [];
|
|
157
|
+
// compileQuery cannot apply these execution-time transformations, so
|
|
158
|
+
// the printed SQL would silently diverge — disclose them instead.
|
|
159
|
+
const hasDashboardFilters = typeof dashboardFilters === 'object' &&
|
|
160
|
+
dashboardFilters !== null &&
|
|
161
|
+
Object.values(dashboardFilters).some((group) => Array.isArray(group) && group.length > 0);
|
|
162
|
+
const runtimeOnlyTransformations = [
|
|
163
|
+
...(dateZoom !== undefined || metricQuery.dateZoom !== undefined
|
|
164
|
+
? ['date zoom']
|
|
165
|
+
: []),
|
|
166
|
+
...(hasDashboardFilters ? ['dashboard filters'] : []),
|
|
167
|
+
];
|
|
168
|
+
const entry = {
|
|
169
|
+
exploreName: metricQuery.exploreName,
|
|
170
|
+
dimensions: toStrings(metricQuery.dimensions),
|
|
171
|
+
metrics: toStrings(metricQuery.metrics),
|
|
172
|
+
compiledSql: null,
|
|
173
|
+
compilationErrors: [],
|
|
174
|
+
runtimeOnlyTransformations,
|
|
175
|
+
};
|
|
176
|
+
const compiled = await postUpstreamJson(`/api/v1/projects/${encodeURIComponent(args.projectUuid)}/explores/${encodeURIComponent(metricQuery.exploreName)}/compileQuery`, JSON.stringify({
|
|
177
|
+
...query,
|
|
178
|
+
parameters,
|
|
179
|
+
usePreAggregateCache,
|
|
180
|
+
pivotConfiguration,
|
|
181
|
+
}));
|
|
182
|
+
if (typeof compiled === 'object' && compiled !== null) {
|
|
183
|
+
const { results } = compiled;
|
|
184
|
+
if (typeof results === 'object' && results !== null) {
|
|
185
|
+
const { query: sql, compilationErrors } = results;
|
|
186
|
+
if (typeof sql === 'string')
|
|
187
|
+
entry.compiledSql = sql;
|
|
188
|
+
entry.compilationErrors = toStrings(compilationErrors);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
report(entry);
|
|
192
|
+
};
|
|
114
193
|
// Host-answered virtual route: replies with only the explore's base
|
|
115
194
|
// table, never the explore itself.
|
|
116
195
|
const answerExploreBaseTable = async (req, res) => {
|
|
@@ -189,7 +268,17 @@ const startPreviewProxy = (args) => {
|
|
|
189
268
|
res.destroy();
|
|
190
269
|
}
|
|
191
270
|
});
|
|
192
|
-
|
|
271
|
+
if (args.onCompiledQuery !== undefined &&
|
|
272
|
+
(0, common_1.isAppSdkMetricQueryRoute)(method, pathname)) {
|
|
273
|
+
// Buffer so the same bytes are both forwarded and compiled.
|
|
274
|
+
readBody(req).then((body) => {
|
|
275
|
+
upstreamReq.end(body);
|
|
276
|
+
compileAndReport(body).catch(() => { });
|
|
277
|
+
}, () => upstreamReq.destroy());
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
req.pipe(upstreamReq);
|
|
281
|
+
}
|
|
193
282
|
});
|
|
194
283
|
return new Promise((resolve, reject) => {
|
|
195
284
|
server.once('error', reject);
|
package/dist/index.js
CHANGED
|
@@ -558,6 +558,7 @@ appsProgram
|
|
|
558
558
|
.option('--token <token>', 'API key / personal access token (default: your login config)')
|
|
559
559
|
.option('-y, --assume-yes', 'assume yes to the dependency install prompts when node_modules is missing', false)
|
|
560
560
|
.option('--port <port>', 'port for the app dev server (default: Vite picks one, usually 5173)')
|
|
561
|
+
.option('--show-sql', 'print the compiled SQL for each metric query the app runs', false)
|
|
561
562
|
.option('--verbose', undefined, false)
|
|
562
563
|
.action(preview_1.appsPreviewHandler);
|
|
563
564
|
appsProgram
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.71.0",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"@actions/core": "1.11.1",
|
|
23
23
|
"@aws-sdk/credential-providers": "3.995.0",
|
|
24
24
|
"@casl/ability": "6.8.0",
|
|
25
|
-
"@lightdash/common": "3.
|
|
26
|
-
"@lightdash/warehouses": "3.
|
|
25
|
+
"@lightdash/common": "3.71.0",
|
|
26
|
+
"@lightdash/warehouses": "3.71.0",
|
|
27
27
|
"@sidvind/better-ajv-errors": "7.0.0",
|
|
28
28
|
"@types/columnify": "1.5.1",
|
|
29
29
|
"ajv": "8.18.0",
|