@lightdash/cli 3.70.0 → 3.72.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.
@@ -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;AAgB3B;;;;;;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,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CACpB,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,CA8Id,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":"AAkBA,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;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;CACjB,KAAG,OAAO,CAAC,kBAAkB,CAsM7B,CAAC"}
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
- req.pipe(upstreamReq);
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.70.0",
3
+ "version": "3.72.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.70.0",
26
- "@lightdash/warehouses": "3.70.0",
25
+ "@lightdash/common": "3.72.0",
26
+ "@lightdash/warehouses": "3.72.0",
27
27
  "@sidvind/better-ajv-errors": "7.0.0",
28
28
  "@types/columnify": "1.5.1",
29
29
  "ajv": "8.18.0",