@lightdash/cli 1.242.4 → 1.242.6

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.
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const fs_1 = require("fs");
5
+ const os = tslib_1.__importStar(require("os"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const lint_1 = require("./lint");
8
+ vi.mock('../analytics/analytics', () => ({
9
+ categorizeError: vi.fn(),
10
+ LightdashAnalytics: {
11
+ track: vi.fn().mockResolvedValue(undefined),
12
+ },
13
+ }));
14
+ describe('lintHandler', () => {
15
+ let tempDir;
16
+ let output;
17
+ beforeEach(async () => {
18
+ tempDir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'lightdash-lint-'));
19
+ output = [];
20
+ vi.spyOn(console, 'log').mockImplementation((...args) => {
21
+ output.push(args.map(String).join(' '));
22
+ });
23
+ vi.spyOn(process, 'exit').mockImplementation(() => {
24
+ throw new Error('process.exit');
25
+ });
26
+ });
27
+ afterEach(async () => {
28
+ vi.restoreAllMocks();
29
+ await fs_1.promises.rm(tempDir, { recursive: true, force: true });
30
+ });
31
+ const expectLintFailure = async () => {
32
+ await expect((0, lint_1.lintHandler)({ path: tempDir, format: 'json' })).rejects.toThrow('process.exit');
33
+ expect(process.exit).toHaveBeenCalledWith(2);
34
+ return output.join('\n');
35
+ };
36
+ test('fails for a dashboard upload file missing its required version', async () => {
37
+ const dashboardsDir = path.join(tempDir, 'dashboards');
38
+ await fs_1.promises.mkdir(dashboardsDir);
39
+ await fs_1.promises.writeFile(path.join(dashboardsDir, 'missing-version.yml'), [
40
+ 'name: Missing version',
41
+ 'slug: missing-version',
42
+ 'spaceSlug: shared',
43
+ 'tiles: []',
44
+ 'tabs: []',
45
+ ].join('\n'));
46
+ const lintOutput = await expectLintFailure();
47
+ expect(lintOutput).toContain('missing-version.yml');
48
+ expect(lintOutput).toContain("Missing required property 'version'");
49
+ });
50
+ test('fails for a chart missing its required version alongside a valid sibling', async () => {
51
+ const chartsDir = path.join(tempDir, 'charts');
52
+ const dashboardsDir = path.join(tempDir, 'dashboards');
53
+ await fs_1.promises.mkdir(chartsDir);
54
+ await fs_1.promises.mkdir(dashboardsDir);
55
+ await fs_1.promises.writeFile(path.join(chartsDir, 'missing-version.yml'), [
56
+ 'name: Missing version',
57
+ 'slug: missing-version',
58
+ 'spaceSlug: shared',
59
+ 'tableName: orders',
60
+ 'metricQuery:',
61
+ ' exploreName: orders',
62
+ ' dimensions: []',
63
+ ' metrics: []',
64
+ ' filters: {}',
65
+ ' sorts: []',
66
+ ' limit: 500',
67
+ ' tableCalculations: []',
68
+ ' additionalMetrics: []',
69
+ ' customDimensions: []',
70
+ 'chartConfig:',
71
+ ' type: table',
72
+ ' config: {}',
73
+ ].join('\n'));
74
+ await fs_1.promises.writeFile(path.join(dashboardsDir, 'valid.yml'), [
75
+ 'name: Valid dashboard',
76
+ 'slug: valid-dashboard',
77
+ 'spaceSlug: shared',
78
+ 'tiles: []',
79
+ 'tabs: []',
80
+ 'version: 1',
81
+ ].join('\n'));
82
+ await expect((0, lint_1.lintHandler)({ path: tempDir, format: 'cli' })).rejects.toThrow('process.exit');
83
+ expect(process.exit).toHaveBeenCalledWith(2);
84
+ const lintOutput = output.join('\n');
85
+ expect(lintOutput).toContain('missing-version.yml');
86
+ expect(lintOutput).toContain("Missing required property 'version'");
87
+ expect(lintOutput).toContain('2 Lightdash Code files');
88
+ expect(lintOutput).toContain('1 valid');
89
+ expect(lintOutput).toContain('1 invalid');
90
+ });
91
+ test('fails when a chart upload file cannot be parsed', async () => {
92
+ const chartsDir = path.join(tempDir, 'charts');
93
+ await fs_1.promises.mkdir(chartsDir);
94
+ await fs_1.promises.writeFile(path.join(chartsDir, 'invalid-yaml.yml'), ['name: Invalid YAML', 'slug: invalid-yaml', 'sql: ]'].join('\n'));
95
+ const lintOutput = await expectLintFailure();
96
+ expect(lintOutput).toContain('invalid-yaml.yml');
97
+ expect(lintOutput).toContain('chart/parse');
98
+ expect(lintOutput).toContain('"startLine": 3');
99
+ });
100
+ test('reports empty chart upload files as invalid', async () => {
101
+ const chartsDir = path.join(tempDir, 'charts');
102
+ await fs_1.promises.mkdir(chartsDir);
103
+ await fs_1.promises.writeFile(path.join(chartsDir, 'empty.yml'), '');
104
+ const lintOutput = await expectLintFailure();
105
+ expect(lintOutput).toContain('empty.yml');
106
+ expect(lintOutput).toContain('chart/type');
107
+ });
108
+ test('validates loose chart files by contentType', async () => {
109
+ await fs_1.promises.writeFile(path.join(tempDir, 'loose-chart.yml'), [
110
+ 'contentType: chart',
111
+ 'name: Missing version',
112
+ 'metricQuery: {}',
113
+ ].join('\n'));
114
+ const lintOutput = await expectLintFailure();
115
+ expect(lintOutput).toContain('loose-chart.yml');
116
+ expect(lintOutput).toContain("Missing required property 'version'");
117
+ });
118
+ test('validates SQL chart content as a dashboard when placed in dashboards', async () => {
119
+ const dashboardsDir = path.join(tempDir, 'dashboards');
120
+ await fs_1.promises.mkdir(dashboardsDir);
121
+ await fs_1.promises.writeFile(path.join(dashboardsDir, 'sql-chart.yml'), [
122
+ 'contentType: sql_chart',
123
+ 'name: Misplaced SQL chart',
124
+ 'sql: SELECT 1',
125
+ ].join('\n'));
126
+ const lintOutput = await expectLintFailure();
127
+ expect(lintOutput).toContain('sql-chart.yml');
128
+ expect(lintOutput).toContain("Missing required property 'version'");
129
+ });
130
+ test('accepts structurally recognized SQL charts selected for upload', async () => {
131
+ const chartsDir = path.join(tempDir, 'charts');
132
+ await fs_1.promises.mkdir(chartsDir);
133
+ await fs_1.promises.writeFile(path.join(tempDir, 'sql-chart.yml'), [
134
+ 'contentType: sql_chart',
135
+ 'name: Loose SQL chart',
136
+ 'sql: SELECT 1',
137
+ ].join('\n'));
138
+ await fs_1.promises.writeFile(path.join(chartsDir, 'sql-chart.yml'), ['name: Folder SQL chart', 'sql: SELECT 1'].join('\n'));
139
+ await (0, lint_1.lintHandler)({ path: tempDir, format: 'cli' });
140
+ expect(process.exit).not.toHaveBeenCalled();
141
+ expect(output.join('\n')).toContain('All Lightdash Code files are valid!');
142
+ });
143
+ test('ignores loose YAML files upload would not select', async () => {
144
+ await fs_1.promises.writeFile(path.join(tempDir, 'unrelated.yml'), ['version: 1', 'metricQuery: {}', 'name: Not uploadable'].join('\n'));
145
+ await (0, lint_1.lintHandler)({ path: tempDir, format: 'cli' });
146
+ expect(process.exit).not.toHaveBeenCalled();
147
+ expect(output.join('\n')).toContain('No Lightdash Code files found.');
148
+ });
149
+ test('accepts merge keys that upload resolves with js-yaml', async () => {
150
+ const dashboardsDir = path.join(tempDir, 'dashboards');
151
+ await fs_1.promises.mkdir(dashboardsDir);
152
+ await fs_1.promises.writeFile(path.join(dashboardsDir, 'merged-tabs.yml'), [
153
+ 'contentType: dashboard',
154
+ 'name: Merged tabs',
155
+ 'slug: merged-tabs',
156
+ 'spaceSlug: shared',
157
+ 'version: 1',
158
+ 'tiles: []',
159
+ 'tabs:',
160
+ ' - &tab',
161
+ ' name: Overview',
162
+ ' order: 0',
163
+ ' - <<: *tab',
164
+ ' name: Details',
165
+ ' order: 1',
166
+ ].join('\n'));
167
+ await (0, lint_1.lintHandler)({ path: tempDir, format: 'cli' });
168
+ expect(process.exit).not.toHaveBeenCalled();
169
+ expect(output.join('\n')).toContain('All Lightdash Code files are valid!');
170
+ });
171
+ test('reports parse errors for loose content files and models', async () => {
172
+ const modelsDir = path.join(tempDir, 'models');
173
+ await fs_1.promises.mkdir(modelsDir);
174
+ await fs_1.promises.writeFile(path.join(tempDir, 'loose-chart.yml'), [
175
+ 'contentType: chart',
176
+ 'name: Broken chart',
177
+ 'bad: [unterminated',
178
+ ].join('\n'));
179
+ await fs_1.promises.writeFile(path.join(modelsDir, 'broken-model.yml'), ['type: model', 'name: Broken model', 'bad: [unterminated'].join('\n'));
180
+ const lintOutput = await expectLintFailure();
181
+ expect(lintOutput).toContain('loose-chart.yml');
182
+ expect(lintOutput).toContain('chart/parse');
183
+ expect(lintOutput).toContain('broken-model.yml');
184
+ expect(lintOutput).toContain('model/parse');
185
+ });
186
+ test('reports .yaml content files as unsupported', async () => {
187
+ const chartsDir = path.join(tempDir, 'charts');
188
+ await fs_1.promises.mkdir(chartsDir);
189
+ await fs_1.promises.writeFile(path.join(chartsDir, 'unsupported.yaml'), 'name: Unsupported extension');
190
+ const lintOutput = await expectLintFailure();
191
+ expect(lintOutput).toContain('unsupported.yaml');
192
+ expect(lintOutput).toContain('chart/extension');
193
+ expect(lintOutput).toContain("Content files must use the '.yml' extension");
194
+ });
195
+ test('defaults strict content discovery to the upload root', async () => {
196
+ const thirdPartyChartsDir = path.join(tempDir, 'vendor', 'charts');
197
+ await fs_1.promises.mkdir(thirdPartyChartsDir, { recursive: true });
198
+ await fs_1.promises.writeFile(path.join(thirdPartyChartsDir, 'unrelated.yml'), 'name: Not Lightdash content');
199
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
200
+ await (0, lint_1.lintHandler)({ format: 'cli' });
201
+ expect(process.exit).not.toHaveBeenCalled();
202
+ expect(output.join('\n')).toContain('No Lightdash Code files found.');
203
+ });
204
+ test('discovers content when run from inside the download root', async () => {
205
+ const chartsDir = path.join(tempDir, 'charts');
206
+ await fs_1.promises.mkdir(chartsDir);
207
+ await fs_1.promises.writeFile(path.join(chartsDir, 'missing-version.yml'), 'name: Missing version');
208
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
209
+ await expect((0, lint_1.lintHandler)({ format: 'cli' })).rejects.toThrow('process.exit');
210
+ expect(output.join('\n')).toContain('missing-version.yml');
211
+ expect(output.join('\n')).toContain("Missing required property 'version'");
212
+ });
213
+ test('shows the filename when linting a single file', async () => {
214
+ const dashboardsDir = path.join(tempDir, 'dashboards');
215
+ await fs_1.promises.mkdir(dashboardsDir);
216
+ const dashboardPath = path.join(dashboardsDir, 'single-file.yml');
217
+ await fs_1.promises.writeFile(dashboardPath, 'name: Missing version');
218
+ await expect((0, lint_1.lintHandler)({ path: dashboardPath, format: 'cli' })).rejects.toThrow('process.exit');
219
+ expect(output.join('\n')).toContain('✗ single-file.yml');
220
+ });
221
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "1.242.4",
3
+ "version": "1.242.6",
4
4
  "description": "Lightdash CLI tool",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,8 +45,8 @@
45
45
  "unique-names-generator": "4.7.1",
46
46
  "uuid": "11.1.1",
47
47
  "yaml": "2.8.3",
48
- "@lightdash/common": "1.242.4",
49
- "@lightdash/warehouses": "1.242.4"
48
+ "@lightdash/common": "1.242.6",
49
+ "@lightdash/warehouses": "1.242.6"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/inquirer": "8.2.4",