@lightdash/cli 2.228.0 → 2.229.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.
Files changed (177) hide show
  1. package/dist/.tsbuildinfo.build +1 -0
  2. package/package.json +4 -4
  3. package/dist/.tsbuildinfo +0 -1
  4. package/dist/config.test.d.ts +0 -2
  5. package/dist/config.test.d.ts.map +0 -1
  6. package/dist/config.test.js +0 -72
  7. package/dist/dbt/context.test.d.ts +0 -2
  8. package/dist/dbt/context.test.d.ts.map +0 -1
  9. package/dist/dbt/context.test.js +0 -152
  10. package/dist/dbt/manifest.test.d.ts +0 -2
  11. package/dist/dbt/manifest.test.d.ts.map +0 -1
  12. package/dist/dbt/manifest.test.js +0 -170
  13. package/dist/dbt/models.test.d.ts +0 -2
  14. package/dist/dbt/models.test.d.ts.map +0 -1
  15. package/dist/dbt/models.test.js +0 -139
  16. package/dist/dbt/profiles.test.d.ts +0 -2
  17. package/dist/dbt/profiles.test.d.ts.map +0 -1
  18. package/dist/dbt/profiles.test.js +0 -50
  19. package/dist/dbt/targets/athena.test.d.ts +0 -2
  20. package/dist/dbt/targets/athena.test.d.ts.map +0 -1
  21. package/dist/dbt/targets/athena.test.js +0 -78
  22. package/dist/dbt/targets/duckdb.test.d.ts +0 -2
  23. package/dist/dbt/targets/duckdb.test.d.ts.map +0 -1
  24. package/dist/dbt/targets/duckdb.test.js +0 -149
  25. package/dist/dbt/targets/redshift.test.d.ts +0 -2
  26. package/dist/dbt/targets/redshift.test.d.ts.map +0 -1
  27. package/dist/dbt/targets/redshift.test.js +0 -198
  28. package/dist/dbt/templating.test.d.ts +0 -2
  29. package/dist/dbt/templating.test.d.ts.map +0 -1
  30. package/dist/dbt/templating.test.js +0 -51
  31. package/dist/dbt/validation.test.d.ts +0 -2
  32. package/dist/dbt/validation.test.d.ts.map +0 -1
  33. package/dist/dbt/validation.test.js +0 -67
  34. package/dist/handlers/apps/appCodeFiles.test.d.ts +0 -2
  35. package/dist/handlers/apps/appCodeFiles.test.d.ts.map +0 -1
  36. package/dist/handlers/apps/appCodeFiles.test.js +0 -391
  37. package/dist/handlers/apps/appsDownload.test.d.ts +0 -2
  38. package/dist/handlers/apps/appsDownload.test.d.ts.map +0 -1
  39. package/dist/handlers/apps/appsDownload.test.js +0 -639
  40. package/dist/handlers/apps/build.test.d.ts +0 -2
  41. package/dist/handlers/apps/build.test.d.ts.map +0 -1
  42. package/dist/handlers/apps/build.test.js +0 -252
  43. package/dist/handlers/apps/createApp.test.d.ts +0 -2
  44. package/dist/handlers/apps/createApp.test.d.ts.map +0 -1
  45. package/dist/handlers/apps/createApp.test.js +0 -340
  46. package/dist/handlers/apps/pathContainment.test.d.ts +0 -2
  47. package/dist/handlers/apps/pathContainment.test.d.ts.map +0 -1
  48. package/dist/handlers/apps/pathContainment.test.js +0 -66
  49. package/dist/handlers/apps/preview.test.d.ts +0 -2
  50. package/dist/handlers/apps/preview.test.d.ts.map +0 -1
  51. package/dist/handlers/apps/preview.test.js +0 -353
  52. package/dist/handlers/apps/previewProxy.test.d.ts +0 -2
  53. package/dist/handlers/apps/previewProxy.test.d.ts.map +0 -1
  54. package/dist/handlers/apps/previewProxy.test.js +0 -178
  55. package/dist/handlers/apps/scaffolding.test.d.ts +0 -2
  56. package/dist/handlers/apps/scaffolding.test.d.ts.map +0 -1
  57. package/dist/handlers/apps/scaffolding.test.js +0 -151
  58. package/dist/handlers/apps/uploadRetry.test.d.ts +0 -2
  59. package/dist/handlers/apps/uploadRetry.test.d.ts.map +0 -1
  60. package/dist/handlers/apps/uploadRetry.test.js +0 -103
  61. package/dist/handlers/apps/validate.test.d.ts +0 -2
  62. package/dist/handlers/apps/validate.test.d.ts.map +0 -1
  63. package/dist/handlers/apps/validate.test.js +0 -526
  64. package/dist/handlers/compile.test.d.ts +0 -2
  65. package/dist/handlers/compile.test.d.ts.map +0 -1
  66. package/dist/handlers/compile.test.js +0 -151
  67. package/dist/handlers/compileProject.test.d.ts +0 -2
  68. package/dist/handlers/compileProject.test.d.ts.map +0 -1
  69. package/dist/handlers/compileProject.test.js +0 -637
  70. package/dist/handlers/compileWarehouseColumnValidation.test.d.ts +0 -2
  71. package/dist/handlers/compileWarehouseColumnValidation.test.d.ts.map +0 -1
  72. package/dist/handlers/compileWarehouseColumnValidation.test.js +0 -493
  73. package/dist/handlers/connectSnowflake.test.d.ts +0 -2
  74. package/dist/handlers/connectSnowflake.test.d.ts.map +0 -1
  75. package/dist/handlers/connectSnowflake.test.js +0 -320
  76. package/dist/handlers/contentAsCode/fileNames.test.d.ts +0 -2
  77. package/dist/handlers/contentAsCode/fileNames.test.d.ts.map +0 -1
  78. package/dist/handlers/contentAsCode/fileNames.test.js +0 -79
  79. package/dist/handlers/contentAsCode/registry.test.d.ts +0 -2
  80. package/dist/handlers/contentAsCode/registry.test.d.ts.map +0 -1
  81. package/dist/handlers/contentAsCode/registry.test.js +0 -18
  82. package/dist/handlers/contentAsCode/resource.test.d.ts +0 -2
  83. package/dist/handlers/contentAsCode/resource.test.d.ts.map +0 -1
  84. package/dist/handlers/contentAsCode/resource.test.js +0 -121
  85. package/dist/handlers/dbt/getDbtVersion.test.d.ts +0 -2
  86. package/dist/handlers/dbt/getDbtVersion.test.d.ts.map +0 -1
  87. package/dist/handlers/dbt/getDbtVersion.test.js +0 -106
  88. package/dist/handlers/deploy.test.d.ts +0 -2
  89. package/dist/handlers/deploy.test.d.ts.map +0 -1
  90. package/dist/handlers/deploy.test.js +0 -244
  91. package/dist/handlers/download.test.d.ts +0 -2
  92. package/dist/handlers/download.test.d.ts.map +0 -1
  93. package/dist/handlers/download.test.js +0 -1739
  94. package/dist/handlers/exportChartImage.test.d.ts +0 -2
  95. package/dist/handlers/exportChartImage.test.d.ts.map +0 -1
  96. package/dist/handlers/exportChartImage.test.js +0 -55
  97. package/dist/handlers/homepagesAsCode.test.d.ts +0 -2
  98. package/dist/handlers/homepagesAsCode.test.d.ts.map +0 -1
  99. package/dist/handlers/homepagesAsCode.test.js +0 -72
  100. package/dist/handlers/lint.test.d.ts +0 -2
  101. package/dist/handlers/lint.test.d.ts.map +0 -1
  102. package/dist/handlers/lint.test.js +0 -221
  103. package/dist/handlers/login.test.d.ts +0 -2
  104. package/dist/handlers/login.test.d.ts.map +0 -1
  105. package/dist/handlers/login.test.js +0 -24
  106. package/dist/handlers/metadataFile.test.d.ts +0 -2
  107. package/dist/handlers/metadataFile.test.d.ts.map +0 -1
  108. package/dist/handlers/metadataFile.test.js +0 -46
  109. package/dist/handlers/organizationContent/customRoles.test.d.ts +0 -2
  110. package/dist/handlers/organizationContent/customRoles.test.d.ts.map +0 -1
  111. package/dist/handlers/organizationContent/customRoles.test.js +0 -179
  112. package/dist/handlers/organizationContent/groups.test.d.ts +0 -2
  113. package/dist/handlers/organizationContent/groups.test.d.ts.map +0 -1
  114. package/dist/handlers/organizationContent/groups.test.js +0 -135
  115. package/dist/handlers/organizationContent/index.test.d.ts +0 -2
  116. package/dist/handlers/organizationContent/index.test.d.ts.map +0 -1
  117. package/dist/handlers/organizationContent/index.test.js +0 -212
  118. package/dist/handlers/organizationContent/themes.test.d.ts +0 -2
  119. package/dist/handlers/organizationContent/themes.test.d.ts.map +0 -1
  120. package/dist/handlers/organizationContent/themes.test.js +0 -180
  121. package/dist/handlers/organizationContent/users.test.d.ts +0 -2
  122. package/dist/handlers/organizationContent/users.test.d.ts.map +0 -1
  123. package/dist/handlers/organizationContent/users.test.js +0 -155
  124. package/dist/handlers/preAggregateAudit.test.d.ts +0 -2
  125. package/dist/handlers/preAggregateAudit.test.d.ts.map +0 -1
  126. package/dist/handlers/preAggregateAudit.test.js +0 -102
  127. package/dist/handlers/renameHandler.test.d.ts +0 -2
  128. package/dist/handlers/renameHandler.test.d.ts.map +0 -1
  129. package/dist/handlers/renameHandler.test.js +0 -189
  130. package/dist/handlers/resolveProjectFlag.test.d.ts +0 -2
  131. package/dist/handlers/resolveProjectFlag.test.d.ts.map +0 -1
  132. package/dist/handlers/resolveProjectFlag.test.js +0 -48
  133. package/dist/handlers/selectProject.test.d.ts +0 -2
  134. package/dist/handlers/selectProject.test.d.ts.map +0 -1
  135. package/dist/handlers/selectProject.test.js +0 -224
  136. package/dist/handlers/setProject.test.d.ts +0 -2
  137. package/dist/handlers/setProject.test.d.ts.map +0 -1
  138. package/dist/handlers/setProject.test.js +0 -63
  139. package/dist/handlers/slugUpdate.test.d.ts +0 -2
  140. package/dist/handlers/slugUpdate.test.d.ts.map +0 -1
  141. package/dist/handlers/slugUpdate.test.js +0 -265
  142. package/dist/handlers/timestampConversion.test.d.ts +0 -2
  143. package/dist/handlers/timestampConversion.test.d.ts.map +0 -1
  144. package/dist/handlers/timestampConversion.test.js +0 -87
  145. package/dist/handlers/upgradeCheck.test.d.ts +0 -2
  146. package/dist/handlers/upgradeCheck.test.d.ts.map +0 -1
  147. package/dist/handlers/upgradeCheck.test.js +0 -227
  148. package/dist/handlers/validate.test.d.ts +0 -2
  149. package/dist/handlers/validate.test.d.ts.map +0 -1
  150. package/dist/handlers/validate.test.js +0 -318
  151. package/dist/handlers/validateSpaceFilter.test.d.ts +0 -2
  152. package/dist/handlers/validateSpaceFilter.test.d.ts.map +0 -1
  153. package/dist/handlers/validateSpaceFilter.test.js +0 -79
  154. package/dist/handlers/warehouseConnection.test.d.ts +0 -2
  155. package/dist/handlers/warehouseConnection.test.d.ts.map +0 -1
  156. package/dist/handlers/warehouseConnection.test.js +0 -75
  157. package/dist/lightdash-config/lightdash-config.test.d.ts +0 -2
  158. package/dist/lightdash-config/lightdash-config.test.d.ts.map +0 -1
  159. package/dist/lightdash-config/lightdash-config.test.js +0 -69
  160. package/dist/prompts/fuzzySearchPrompt.test.d.ts +0 -2
  161. package/dist/prompts/fuzzySearchPrompt.test.d.ts.map +0 -1
  162. package/dist/prompts/fuzzySearchPrompt.test.js +0 -53
  163. package/dist/refsArgument.test.d.ts +0 -2
  164. package/dist/refsArgument.test.d.ts.map +0 -1
  165. package/dist/refsArgument.test.js +0 -42
  166. package/dist/releaseSafety/index.test.d.ts +0 -2
  167. package/dist/releaseSafety/index.test.d.ts.map +0 -1
  168. package/dist/releaseSafety/index.test.js +0 -154
  169. package/dist/releaseSafety/version.test.d.ts +0 -2
  170. package/dist/releaseSafety/version.test.d.ts.map +0 -1
  171. package/dist/releaseSafety/version.test.js +0 -22
  172. package/dist/skills.test.d.ts +0 -2
  173. package/dist/skills.test.d.ts.map +0 -1
  174. package/dist/skills.test.js +0 -112
  175. package/dist/terminal/contentAsCodeOutput.test.d.ts +0 -2
  176. package/dist/terminal/contentAsCodeOutput.test.d.ts.map +0 -1
  177. package/dist/terminal/contentAsCodeOutput.test.js +0 -231
@@ -1,526 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const common_1 = require("@lightdash/common");
5
- const fs_1 = require("fs");
6
- const os = tslib_1.__importStar(require("os"));
7
- const path = tslib_1.__importStar(require("path"));
8
- const appCodeFiles_1 = require("./appCodeFiles");
9
- const validate_1 = require("./validate");
10
- const semanticLayer = [
11
- 'models:',
12
- ' - name: orders',
13
- ' meta:',
14
- ' metrics:',
15
- ' total_revenue:',
16
- ' type: sum',
17
- ' columns:',
18
- ' - name: status',
19
- ].join('\n');
20
- const defaultManifest = {
21
- codeVersion: 1,
22
- projectUuid: 'project-uuid',
23
- slug: 'orders-app',
24
- version: 1,
25
- name: 'Orders app',
26
- description: '',
27
- template: null,
28
- downloadedAt: '2026-08-01T00:00:00.000Z',
29
- externalConnections: [{ alias: 'stripe', connectionSlug: 'stripe-api' }],
30
- };
31
- const validSource = `
32
- import { query } from '@lightdash/query-sdk';
33
- query('orders').dimensions(['status']).metrics(['total_revenue']);
34
- lightdash.externalFetch('stripe', { path: '/charges' });
35
- `;
36
- const makeApp = async (args) => {
37
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-validate-'));
38
- const code = {
39
- manifest: args?.manifest ?? defaultManifest,
40
- files: [
41
- {
42
- path: 'src/App.tsx',
43
- contentBase64: Buffer.from(args?.source ?? validSource).toString('base64'),
44
- },
45
- ],
46
- };
47
- await (0, appCodeFiles_1.writeBundleToDir)(dir, code);
48
- const layout = args?.semanticLayout ?? 'sharded';
49
- if (layout === 'legacy') {
50
- const contextDir = path.join(dir, '.lightdash', 'context');
51
- await fs_1.promises.mkdir(contextDir, { recursive: true });
52
- await fs_1.promises.writeFile(path.join(contextDir, 'semantic-layer.yml'), semanticLayer);
53
- }
54
- else if (layout === 'sharded') {
55
- const modelsDir = path.join(dir, '.lightdash', 'context', 'models');
56
- await fs_1.promises.mkdir(modelsDir, { recursive: true });
57
- await Promise.all([
58
- fs_1.promises.writeFile(path.join(dir, '.lightdash', 'context', 'semantic-layer.yml'), '# See models/'),
59
- fs_1.promises.writeFile(path.join(modelsDir, 'orders.yml'), semanticLayer),
60
- ]);
61
- }
62
- if (args?.packageJson !== undefined) {
63
- await fs_1.promises.writeFile(path.join(dir, 'package.json'), args.packageJson);
64
- }
65
- if (args?.lockfile !== undefined) {
66
- await fs_1.promises.writeFile(path.join(dir, 'pnpm-lock.yaml'), args.lockfile);
67
- }
68
- return dir;
69
- };
70
- const unusedLiveLoader = () => {
71
- throw new Error('live semantic layer should not be loaded');
72
- };
73
- const liveIndex = (0, common_1.buildDataAppExploreIndexFromModelFiles)([
74
- { path: 'orders.yml', content: semanticLayer },
75
- ]);
76
- describe('validateLocalDataApp', () => {
77
- it.each(['sharded', 'legacy'])('validates a correct app against a %s offline semantic layer', async (semanticLayout) => {
78
- const dir = await makeApp({ semanticLayout });
79
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
80
- live: false,
81
- loadLiveIndex: unusedLiveLoader,
82
- });
83
- expect(result.valid).toBe(true);
84
- expect(result.errors).toEqual([]);
85
- expect(result.coverage).toMatchObject({
86
- callSites: 2,
87
- fullyResolved: 2,
88
- unanalyzed: 0,
89
- });
90
- });
91
- it('reports semantic errors and undeclared externalFetch aliases with source locations', async () => {
92
- const dir = await makeApp({
93
- source: `
94
- import { query } from '@lightdash/query-sdk';
95
- query('ordrs').dimensions(['missing_field']);
96
- client.externalFetch('salesforce', { path: '/accounts' });
97
- `,
98
- });
99
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
100
- live: false,
101
- loadLiveIndex: unusedLiveLoader,
102
- });
103
- expect(result.valid).toBe(false);
104
- expect(result.errors).toEqual(expect.arrayContaining([
105
- expect.objectContaining({
106
- code: 'semantic_reference',
107
- message: "Explore 'ordrs' does not exist — did you mean 'orders'?",
108
- location: expect.objectContaining({ path: 'src/App.tsx' }),
109
- }),
110
- expect.objectContaining({
111
- code: 'external_connection',
112
- message: expect.stringContaining('salesforce'),
113
- location: expect.objectContaining({ path: 'src/App.tsx' }),
114
- }),
115
- ]));
116
- });
117
- it('counts dynamic query call sites as unanalyzed without failing', async () => {
118
- const dir = await makeApp({
119
- source: `
120
- import { query } from '@lightdash/query-sdk';
121
- function build(explore) {
122
- return query(explore).dimensions(['status']);
123
- }
124
- `,
125
- });
126
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
127
- live: false,
128
- loadLiveIndex: unusedLiveLoader,
129
- });
130
- expect(result.valid).toBe(true);
131
- expect(result.coverage).toMatchObject({
132
- callSites: 1,
133
- unresolved: 1,
134
- unanalyzed: 1,
135
- });
136
- });
137
- it('does not treat a dynamic externalFetch alias as an error', async () => {
138
- const dir = await makeApp({
139
- source: `
140
- function load(client, alias) {
141
- return client.externalFetch(alias, { path: '/resource' });
142
- }
143
- `,
144
- semanticLayout: 'none',
145
- });
146
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
147
- live: false,
148
- loadLiveIndex: unusedLiveLoader,
149
- });
150
- expect(result.valid).toBe(true);
151
- expect(result.errors).toEqual([]);
152
- expect(result.coverage).toMatchObject({
153
- callSites: 1,
154
- unresolved: 1,
155
- unanalyzed: 1,
156
- });
157
- });
158
- it('uses the live semantic layer and does not require a local snapshot', async () => {
159
- const dir = await makeApp({ semanticLayout: 'none' });
160
- const loadLiveIndex = vi.fn().mockResolvedValue(liveIndex);
161
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
162
- live: true,
163
- liveProjectUuid: 'configured-project-uuid',
164
- loadLiveIndex,
165
- });
166
- expect(result.valid).toBe(true);
167
- expect(result.projectUuid).toBe('configured-project-uuid');
168
- expect(loadLiveIndex).toHaveBeenCalledWith('configured-project-uuid');
169
- });
170
- it('explains how to configure a project for live validation', async () => {
171
- const dir = await makeApp({ semanticLayout: 'none' });
172
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
173
- live: true,
174
- loadLiveIndex: unusedLiveLoader,
175
- });
176
- expect(result.valid).toBe(false);
177
- expect(result.projectUuid).toBeNull();
178
- expect(result.errors).toContainEqual(expect.objectContaining({
179
- code: 'semantic_layer',
180
- message: expect.stringContaining('lightdash config set-project'),
181
- }));
182
- });
183
- it('gives an upgrade path instead of crashing on a missing offline snapshot', async () => {
184
- const dir = await makeApp({ semanticLayout: 'none' });
185
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
186
- live: false,
187
- loadLiveIndex: unusedLiveLoader,
188
- });
189
- expect(result.valid).toBe(false);
190
- expect(result.errors).toContainEqual(expect.objectContaining({
191
- code: 'semantic_layer',
192
- message: expect.stringContaining('Re-download the app'),
193
- }));
194
- });
195
- it('validates slug and vizSchema from the manifest', async () => {
196
- const manifest = {
197
- ...defaultManifest,
198
- slug: '../invalid',
199
- vizSchema: { fields: 'not-an-array' },
200
- };
201
- const dir = await makeApp({ manifest });
202
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
203
- live: false,
204
- loadLiveIndex: unusedLiveLoader,
205
- });
206
- expect(result.errors).toEqual(expect.arrayContaining([
207
- expect.objectContaining({
208
- code: 'manifest',
209
- message: expect.stringContaining('Invalid slug'),
210
- }),
211
- expect.objectContaining({
212
- code: 'manifest',
213
- message: expect.stringContaining('Invalid vizSchema'),
214
- }),
215
- ]));
216
- });
217
- it('validates the icon from the manifest', async () => {
218
- const manifest = {
219
- ...defaultManifest,
220
- icon: 'not-a-real-icon',
221
- };
222
- const dir = await makeApp({ manifest });
223
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
224
- live: false,
225
- loadLiveIndex: unusedLiveLoader,
226
- });
227
- expect(result.errors).toContainEqual(expect.objectContaining({
228
- code: 'manifest',
229
- message: expect.stringContaining('Invalid icon'),
230
- }));
231
- });
232
- it.each([null, 'chart-sankey'])('accepts icon %s', async (icon) => {
233
- const manifest = { ...defaultManifest, icon };
234
- const dir = await makeApp({ manifest });
235
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
236
- live: false,
237
- loadLiveIndex: unusedLiveLoader,
238
- });
239
- expect(result.errors).not.toContainEqual(expect.objectContaining({ code: 'manifest' }));
240
- });
241
- it('requires a pnpm lockfile for custom dependencies', async () => {
242
- const dir = await makeApp({
243
- packageJson: JSON.stringify({
244
- dependencies: { 'left-pad': '1.3.0' },
245
- }),
246
- });
247
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
248
- live: false,
249
- loadLiveIndex: unusedLiveLoader,
250
- });
251
- expect(result.errors).toContainEqual(expect.objectContaining({
252
- code: 'dependencies',
253
- message: expect.stringContaining('pnpm-lock.yaml'),
254
- }));
255
- });
256
- it('surfaces parser failures as warnings rather than errors', async () => {
257
- const dir = await makeApp({
258
- source: 'const broken = ;',
259
- semanticLayout: 'none',
260
- });
261
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
262
- live: false,
263
- loadLiveIndex: unusedLiveLoader,
264
- });
265
- expect(result.valid).toBe(true);
266
- expect(result.warnings).toContainEqual(expect.objectContaining({ code: 'source_parse' }));
267
- });
268
- it('includes Cloud-parity build failures when --build is enabled', async () => {
269
- const dir = await makeApp();
270
- const runBuild = vi.fn().mockResolvedValue([
271
- {
272
- code: 'build',
273
- message: 'Vite build failed: broken import',
274
- location: null,
275
- },
276
- ]);
277
- const result = await (0, validate_1.validateLocalDataApp)(dir, {
278
- build: true,
279
- live: false,
280
- loadLiveIndex: unusedLiveLoader,
281
- runBuild,
282
- });
283
- expect(runBuild).toHaveBeenCalledOnce();
284
- expect(result.valid).toBe(false);
285
- expect(result.errors).toContainEqual(expect.objectContaining({
286
- code: 'build',
287
- message: expect.stringContaining('broken import'),
288
- }));
289
- });
290
- });
291
- describe('validateDataAppBuild', () => {
292
- it('builds source in an isolated vendored scaffold with bare Vite', async () => {
293
- const dir = await makeApp();
294
- await fs_1.promises.mkdir(path.join(dir, 'node_modules'));
295
- const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
296
- const runCommand = vi.fn(async (command) => {
297
- expect(command).toMatchObject({
298
- command: 'vite',
299
- args: ['build'],
300
- preferLocal: true,
301
- timeoutMs: 60_000,
302
- });
303
- expect(command.args).not.toContain('tsc');
304
- await expect(fs_1.promises.readFile(path.join(command.cwd, 'vite.config.js'), 'utf-8')).resolves.toContain('defineConfig');
305
- await expect(fs_1.promises.readFile(path.join(command.cwd, 'src', 'App.tsx'), 'utf-8')).resolves.toContain("query('orders')");
306
- expect((await fs_1.promises.lstat(path.join(command.cwd, 'node_modules'))).isSymbolicLink()).toBe(true);
307
- });
308
- const issues = await (0, validate_1.validateDataAppBuild)({
309
- appDir: dir,
310
- bundle,
311
- runCommand,
312
- });
313
- expect(issues).toEqual([]);
314
- expect(runCommand).toHaveBeenCalledOnce();
315
- });
316
- it('restores custom dependencies with a frozen lockfile before Vite', async () => {
317
- const dir = await makeApp({
318
- packageJson: JSON.stringify({
319
- name: 'custom-app',
320
- version: '1.0.0',
321
- dependencies: { 'left-pad': '1.3.0' },
322
- devDependencies: { malicious: '1.0.0' },
323
- scripts: { build: 'tsc --noEmit' },
324
- }),
325
- lockfile: [
326
- "lockfileVersion: '9.0'",
327
- 'packages:',
328
- ' left-pad@1.3.0: {}',
329
- ].join('\n'),
330
- });
331
- const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
332
- const commands = [];
333
- const runCommand = vi.fn(async (command) => {
334
- commands.push(command);
335
- if (command.command === 'pnpm') {
336
- const packageJson = JSON.parse(await fs_1.promises.readFile(path.join(command.cwd, 'package.json'), 'utf-8'));
337
- expect(packageJson.dependencies).toEqual({
338
- 'left-pad': '1.3.0',
339
- });
340
- expect(packageJson.scripts).toEqual(expect.objectContaining({ build: 'vite build' }));
341
- expect(packageJson.devDependencies).not.toHaveProperty('malicious');
342
- }
343
- });
344
- const issues = await (0, validate_1.validateDataAppBuild)({
345
- appDir: dir,
346
- bundle,
347
- runCommand,
348
- });
349
- expect(issues).toEqual([]);
350
- expect(commands).toEqual([
351
- expect.objectContaining({
352
- command: 'pnpm',
353
- args: ['install', '--frozen-lockfile', '--ignore-scripts'],
354
- env: { CI: 'true' },
355
- timeoutMs: 120_000,
356
- }),
357
- expect.objectContaining({
358
- command: 'vite',
359
- args: ['build'],
360
- preferLocal: true,
361
- }),
362
- ]);
363
- });
364
- it('reports Vite output as a build validation error', async () => {
365
- const dir = await makeApp();
366
- await fs_1.promises.mkdir(path.join(dir, 'node_modules'));
367
- const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
368
- const viteError = Object.assign(new Error('Command failed'), {
369
- stderr: 'src/App.tsx:7: Could not resolve "missing-package"',
370
- stdout: 'vite v8 building for production',
371
- });
372
- const issues = await (0, validate_1.validateDataAppBuild)({
373
- appDir: dir,
374
- bundle,
375
- runCommand: vi.fn().mockRejectedValue(viteError),
376
- });
377
- expect(issues).toEqual([
378
- expect.objectContaining({
379
- code: 'build',
380
- message: expect.stringContaining('Could not resolve "missing-package"'),
381
- }),
382
- ]);
383
- });
384
- it('handles non-object command failures without masking them', async () => {
385
- const dir = await makeApp();
386
- await fs_1.promises.mkdir(path.join(dir, 'node_modules'));
387
- const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
388
- const issues = await (0, validate_1.validateDataAppBuild)({
389
- appDir: dir,
390
- bundle,
391
- runCommand: vi.fn().mockRejectedValue(null),
392
- });
393
- expect(issues).toEqual([
394
- expect.objectContaining({
395
- code: 'build',
396
- message: expect.stringContaining('Unknown object error'),
397
- }),
398
- ]);
399
- });
400
- it('requires local template dependencies without installing them', async () => {
401
- const dir = await makeApp();
402
- const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
403
- const runCommand = vi.fn();
404
- const issues = await (0, validate_1.validateDataAppBuild)({
405
- appDir: dir,
406
- bundle,
407
- runCommand,
408
- });
409
- expect(issues).toEqual([
410
- expect.objectContaining({
411
- code: 'dependencies',
412
- message: expect.stringContaining("Run 'npm install'"),
413
- }),
414
- ]);
415
- expect(runCommand).not.toHaveBeenCalled();
416
- });
417
- });
418
- describe('renderAppsValidationHuman', () => {
419
- const report = (0, validate_1.buildAppsValidationReport)([
420
- {
421
- path: '/tmp/orders-app',
422
- name: 'Orders app',
423
- projectUuid: 'project-uuid',
424
- template: null,
425
- valid: true,
426
- errors: [],
427
- warnings: [],
428
- coverage: {
429
- callSites: 14,
430
- fullyResolved: 11,
431
- partiallyResolved: 2,
432
- unresolved: 1,
433
- unanalyzed: 3,
434
- },
435
- unanalyzedReferences: [
436
- {
437
- kind: 'query',
438
- unresolved: ['explore', 'dimensions'],
439
- location: {
440
- path: 'src/DynamicQuery.tsx',
441
- line: 8,
442
- column: 17,
443
- },
444
- },
445
- {
446
- kind: 'globalFilter',
447
- unresolved: ['field'],
448
- location: {
449
- path: 'src/ResultsTable.tsx',
450
- line: 42,
451
- column: 21,
452
- },
453
- },
454
- {
455
- kind: 'externalFetch',
456
- unresolved: ['alias'],
457
- location: {
458
- path: 'src/ExternalData.tsx',
459
- line: 12,
460
- column: 9,
461
- },
462
- },
463
- ],
464
- },
465
- ], false, true);
466
- it('makes partial green coverage explicit', () => {
467
- const output = (0, validate_1.renderAppsValidationHuman)(report);
468
- expect(output).toContain("3 of 14 data-reference call site(s) couldn't be fully analyzed");
469
- expect(output).toContain('unresolved values were skipped');
470
- expect(output).toContain('Validation passed');
471
- expect(output).toContain('Offline snapshots may be stale');
472
- expect(output).toContain('running Vite production builds');
473
- expect(output).not.toContain('Static analysis gaps');
474
- });
475
- it('lists unresolved call sites and parts in verbose output', () => {
476
- const output = (0, validate_1.renderAppsValidationHuman)(report, true);
477
- expect(output).toContain('Static analysis gaps:');
478
- expect(output).toContain('src/DynamicQuery.tsx:8:17 — query (unresolved: explore, dimensions)');
479
- expect(output).toContain('src/ResultsTable.tsx:42:21 — global filter (unresolved: field)');
480
- });
481
- it('keeps verbose-only reference details out of JSON output', () => {
482
- const output = (0, validate_1.renderAppsValidationJson)(report);
483
- expect(JSON.parse(output)).not.toHaveProperty('apps.0.unanalyzedReferences');
484
- });
485
- const buildResult = (template, overrides = {}) => ({
486
- path: '/tmp/bundle',
487
- name: 'Bundle',
488
- projectUuid: null,
489
- template,
490
- valid: true,
491
- errors: [],
492
- warnings: [],
493
- coverage: {
494
- callSites: 0,
495
- fullyResolved: 0,
496
- partiallyResolved: 0,
497
- unresolved: 0,
498
- unanalyzed: 0,
499
- },
500
- unanalyzedReferences: [],
501
- ...overrides,
502
- });
503
- it('describes an all-chart-type run as custom chart types', () => {
504
- const vizReport = (0, validate_1.buildAppsValidationReport)([buildResult(common_1.DATA_APP_VIZ_TEMPLATE)], false);
505
- const output = (0, validate_1.renderAppsValidationHuman)(vizReport);
506
- expect(output).toContain('Validating 1 custom chart type(s)');
507
- expect(output).toContain('Validation passed for 1 custom chart type(s)');
508
- expect(output).not.toContain('data app(s)');
509
- expect(vizReport.summary.chartTypes).toBe(1);
510
- expect(vizReport.summary.dataApps).toBe(0);
511
- });
512
- it('describes a mixed run with both nouns', () => {
513
- const mixedReport = (0, validate_1.buildAppsValidationReport)([
514
- buildResult(common_1.DATA_APP_VIZ_TEMPLATE, {
515
- valid: false,
516
- errors: [
517
- { code: 'manifest', message: 'bad', location: null },
518
- ],
519
- }),
520
- buildResult(null),
521
- ], false);
522
- const output = (0, validate_1.renderAppsValidationHuman)(mixedReport);
523
- expect(output).toContain('Validating 1 data app(s) and 1 custom chart type(s)');
524
- expect(output).toContain('Validation failed with 1 error(s) across 1 data app(s) and 1 custom chart type(s).');
525
- });
526
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=compile.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile.test.d.ts","sourceRoot":"","sources":["../../src/handlers/compile.test.ts"],"names":[],"mappings":""}
@@ -1,151 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const common_1 = require("@lightdash/common");
5
- const promises_1 = tslib_1.__importDefault(require("fs/promises"));
6
- const os = tslib_1.__importStar(require("os"));
7
- const path = tslib_1.__importStar(require("path"));
8
- const lightdashLoader = tslib_1.__importStar(require("../lightdash/loader"));
9
- const compile_1 = require("./compile");
10
- vi.mock('execa');
11
- vi.mock('../analytics/analytics');
12
- vi.mock('../config', () => ({
13
- getConfig: vi.fn().mockResolvedValue({ user: null, context: null }),
14
- }));
15
- const modelWithBrokenMetric = {
16
- type: 'model',
17
- name: 'test_model',
18
- description: 'Test model',
19
- sql_from: 'SELECT * FROM test_table',
20
- dimensions: [
21
- {
22
- name: 'id',
23
- description: 'ID column',
24
- type: common_1.DimensionType.NUMBER,
25
- sql: 'id',
26
- },
27
- ],
28
- metrics: {
29
- broken_metric: {
30
- type: common_1.MetricType.SUM,
31
- sql: `\${missing_dimension}`,
32
- },
33
- },
34
- };
35
- const getCompileOptions = (projectDir) => ({
36
- projectDir,
37
- profilesDir: '',
38
- target: undefined,
39
- profile: undefined,
40
- vars: undefined,
41
- verbose: false,
42
- startOfWeek: 0,
43
- skipWarehouseCatalog: true,
44
- skipDbtCompile: true,
45
- useDbtList: false,
46
- select: undefined,
47
- models: undefined,
48
- threads: undefined,
49
- noVersionCheck: false,
50
- exclude: undefined,
51
- selector: undefined,
52
- state: undefined,
53
- fullRefresh: false,
54
- defer: false,
55
- targetPath: undefined,
56
- favorState: false,
57
- combineManifest: undefined,
58
- warehouseCredentials: false,
59
- disableTimestampConversion: false,
60
- });
61
- describe('compile', () => {
62
- let tempDir;
63
- beforeEach(async () => {
64
- vi.clearAllMocks();
65
- tempDir = await promises_1.default.mkdtemp(path.join(os.tmpdir(), 'lightdash-compile-test-'));
66
- // Create minimal lightdash.config.yml
67
- await promises_1.default.writeFile(path.join(tempDir, 'lightdash.config.yml'), `
68
- warehouse:
69
- type: postgres
70
- `);
71
- // Create lightdash/models directory and a test model
72
- const modelsDir = path.join(tempDir, 'lightdash', 'models');
73
- await promises_1.default.mkdir(modelsDir, { recursive: true });
74
- await promises_1.default.writeFile(path.join(modelsDir, 'test_model.yml'), `
75
- version: 1
76
- type: model
77
- name: test_model
78
- description: Test model
79
- sql_from: "SELECT * FROM test_table"
80
- dimensions:
81
- - name: id
82
- description: ID column
83
- type: number
84
- sql: id
85
- `);
86
- });
87
- afterEach(async () => {
88
- vi.restoreAllMocks();
89
- await promises_1.default.rm(tempDir, { recursive: true, force: true });
90
- });
91
- test('should compile Lightdash YAML project without dbt installed', async () => {
92
- // Mock analytics to prevent actual tracking
93
- const { LightdashAnalytics } = await Promise.resolve().then(() => tslib_1.__importStar(require('../analytics/analytics')));
94
- LightdashAnalytics.track.mockResolvedValue(undefined);
95
- const result = await (0, compile_1.compile)(getCompileOptions(tempDir));
96
- // Should succeed even though dbt is not installed
97
- expect(result).toBeDefined();
98
- expect(result.length).toBeGreaterThan(0);
99
- // Analytics should be called even though dbt is not found
100
- const trackCalls = LightdashAnalytics.track.mock.calls;
101
- expect(LightdashAnalytics.track).toHaveBeenCalledWith(expect.objectContaining({
102
- event: 'compile.started',
103
- properties: expect.objectContaining({
104
- dbtVersion: undefined,
105
- }),
106
- }));
107
- expect(LightdashAnalytics.track).toHaveBeenCalledWith(expect.objectContaining({
108
- event: 'compile.completed',
109
- }));
110
- // Verify compile.started was called with undefined dbtVersion
111
- const startedCall = trackCalls.find((call) => call[0].event === 'compile.started');
112
- if (startedCall === undefined) {
113
- throw new Error('Expected compile.started analytics event');
114
- }
115
- expect(startedCall[0].properties.dbtVersion).toBeUndefined();
116
- });
117
- test('should allow partial compilation by default and allow it to be disabled explicitly', async () => {
118
- vi.spyOn(lightdashLoader, 'loadLightdashModels').mockResolvedValue([
119
- { ...modelWithBrokenMetric, sourcePath: 'models/test_model.yml' },
120
- ]);
121
- const errorOutput = [];
122
- vi.spyOn(console, 'error').mockImplementation((...args) => {
123
- errorOutput.push(args.map(String).join(' '));
124
- });
125
- const partialResult = await (0, compile_1.compile)(getCompileOptions(tempDir));
126
- expect(partialResult.filter(common_1.isExploreError)).toHaveLength(0);
127
- expect(partialResult.flatMap((explore) => (0, common_1.isExploreError)(explore) ? [] : (explore.warnings ?? []))).toEqual(expect.arrayContaining([
128
- expect.objectContaining({
129
- message: expect.stringContaining('broken_metric'),
130
- }),
131
- ]));
132
- expect(errorOutput).toEqual(expect.arrayContaining([
133
- expect.stringContaining('- PARTIAL_SUCCESS> test_model'),
134
- expect.stringContaining('Compiled 1 explores, SUCCESS=0 PARTIAL_SUCCESS=1 ERRORS=0'),
135
- ]));
136
- errorOutput.length = 0;
137
- const strictResult = await (0, compile_1.compile)({
138
- ...getCompileOptions(tempDir),
139
- partialCompilation: false,
140
- });
141
- expect(strictResult.filter(common_1.isExploreError)).toHaveLength(1);
142
- expect(errorOutput).toEqual(expect.arrayContaining([
143
- expect.stringContaining('- ERROR> test_model'),
144
- expect.stringContaining('Compiled 1 explores, SUCCESS=0 ERRORS=1'),
145
- ]));
146
- });
147
- test('rejects malformed native YAML instead of compiling only the valid models', async () => {
148
- await promises_1.default.writeFile(path.join(tempDir, 'lightdash/models/broken.yml'), 'type: model\ndimensions: [');
149
- await expect((0, compile_1.compile)(getCompileOptions(tempDir))).rejects.toThrow(/broken.yml/);
150
- });
151
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=compileProject.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compileProject.test.d.ts","sourceRoot":"","sources":["../../src/handlers/compileProject.test.ts"],"names":[],"mappings":""}