@lightdash/cli 2.228.1 → 2.229.1

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,391 +0,0 @@
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 appCodeFiles_1 = require("./appCodeFiles");
8
- const makeCode = (appUuid, projectUuid) => ({
9
- manifest: {
10
- codeVersion: 1,
11
- appUuid,
12
- projectUuid,
13
- version: 3,
14
- name: 'My App',
15
- description: '',
16
- template: null,
17
- downloadedAt: '2026-06-25T00:00:00.000Z',
18
- },
19
- files: [],
20
- });
21
- describe('buildImportBody', () => {
22
- it('sets targetAppUuid from manifest when uploading to the same project', () => {
23
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
24
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', {});
25
- expect(body.targetAppUuid).toBe('app-uuid-1');
26
- });
27
- it('leaves targetAppUuid undefined when uploading to a different project', () => {
28
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
29
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-OTHER', {});
30
- expect(body.targetAppUuid).toBeUndefined();
31
- });
32
- it('uses --app option regardless of project match', () => {
33
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
34
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', {
35
- app: 'explicit-app-uuid',
36
- });
37
- expect(body.targetAppUuid).toBe('explicit-app-uuid');
38
- });
39
- it('uses --app option even when project differs', () => {
40
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
41
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-OTHER', {
42
- app: 'explicit-app-uuid',
43
- });
44
- expect(body.targetAppUuid).toBe('explicit-app-uuid');
45
- });
46
- it('passes spaceUuid when --space is provided', () => {
47
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
48
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', {
49
- space: 'space-uuid-1',
50
- });
51
- expect(body.spaceUuid).toBe('space-uuid-1');
52
- });
53
- it('leaves spaceUuid undefined when --space is not provided', () => {
54
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
55
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', {});
56
- expect(body.spaceUuid).toBeUndefined();
57
- });
58
- it('includes the full code bundle in the body', () => {
59
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
60
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', {});
61
- expect(body.code).toBe(code);
62
- });
63
- it('forces a create when createNew is set, even in the same project', () => {
64
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
65
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', { createNew: true });
66
- expect(body.targetAppUuid).toBeUndefined();
67
- });
68
- it('createNew overrides an explicit app target', () => {
69
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
70
- const body = (0, appCodeFiles_1.buildImportBody)(code, 'proj-uuid-1', {
71
- app: 'explicit-app-uuid',
72
- createNew: true,
73
- });
74
- expect(body.targetAppUuid).toBeUndefined();
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
- });
87
- });
88
- const bundle = {
89
- manifest: {
90
- codeVersion: 1,
91
- appUuid: 'a',
92
- projectUuid: 'p',
93
- version: 1,
94
- name: 'N',
95
- description: '',
96
- template: null,
97
- downloadedAt: '2026-06-30T00:00:00.000Z',
98
- },
99
- files: [
100
- {
101
- path: 'src/assets/app.js',
102
- contentBase64: Buffer.from('console.log(1)').toString('base64'),
103
- },
104
- {
105
- path: 'src/index.html',
106
- contentBase64: Buffer.from('<html>').toString('base64'),
107
- },
108
- ],
109
- };
110
- it('readManifestFromDir returns just the manifest', async () => {
111
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
112
- await (0, appCodeFiles_1.writeBundleToDir)(dir, bundle);
113
- const manifest = await (0, appCodeFiles_1.readManifestFromDir)(dir);
114
- expect(manifest).toEqual(bundle.manifest);
115
- });
116
- it('writes then reads back an identical bundle', async () => {
117
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
118
- await (0, appCodeFiles_1.writeBundleToDir)(dir, bundle);
119
- expect(await fs_1.promises.readFile(path.join(dir, 'src/assets/app.js'), 'utf-8')).toBe('console.log(1)');
120
- const read = await (0, appCodeFiles_1.readBundleFromDir)(dir);
121
- expect(read).toEqual(bundle);
122
- });
123
- it('refuses to write a file whose path escapes the target directory', async () => {
124
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
125
- await expect((0, appCodeFiles_1.writeBundleToDir)(dir, {
126
- ...bundle,
127
- files: [
128
- {
129
- path: '../escape.js',
130
- contentBase64: Buffer.from('x').toString('base64'),
131
- },
132
- ],
133
- })).rejects.toThrow(/outside the target directory/);
134
- });
135
- it('writes a manifest-only bundle with no files', async () => {
136
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
137
- await (0, appCodeFiles_1.writeBundleToDir)(dir, { ...bundle, files: [] });
138
- const read = await (0, appCodeFiles_1.readBundleFromDir)(dir);
139
- expect(read).toEqual({ ...bundle, files: [] });
140
- });
141
- it('upload reads back only src/ files, ignoring scaffolding and context', async () => {
142
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
143
- await (0, appCodeFiles_1.writeBundleToDir)(dir, bundle); // bundle.files are src/*
144
- // simulate Phase 2 extra files on disk:
145
- await fs_1.promises.mkdir(path.join(dir, '.lightdash/context'), { recursive: true });
146
- await fs_1.promises.mkdir(path.join(dir, '.claude/skills/x'), { recursive: true });
147
- await fs_1.promises.writeFile(path.join(dir, 'package.json'), '{"name":"x"}');
148
- await fs_1.promises.writeFile(path.join(dir, '.lightdash/context/semantic-layer.yml'), 'models: []');
149
- await fs_1.promises.writeFile(path.join(dir, '.claude/skills/x/SKILL.md'), '# skill');
150
- await fs_1.promises.writeFile(path.join(dir, '.env.local'), 'VITE_LIGHTDASH_API_KEY=secret');
151
- const read = await (0, appCodeFiles_1.readBundleFromDir)(dir);
152
- expect(read.files.every((f) => f.path.startsWith('src/'))).toBe(true);
153
- expect(read.files.map((f) => f.path)).not.toContain('.env.local');
154
- expect(read.files.map((f) => f.path).sort()).toEqual(bundle.files.map((f) => f.path).sort());
155
- });
156
- it('throws a clear error when the manifest is not valid YAML', async () => {
157
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
158
- await fs_1.promises.writeFile(path.join(dir, 'lightdash-app.yml'), 'foo: [bar', 'utf-8');
159
- await expect((0, appCodeFiles_1.readBundleFromDir)(dir)).rejects.toThrow(/Could not parse lightdash-app\.yml/);
160
- });
161
- it('writes context files under .lightdash/context and skips null parameters', async () => {
162
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-ctx-'));
163
- await (0, appCodeFiles_1.writeContextToDir)(dir, {
164
- semanticLayer: {
165
- path: '.lightdash/context/semantic-layer.yml',
166
- contentBase64: Buffer.from('models: []').toString('base64'),
167
- },
168
- parameters: null,
169
- promptHistory: {
170
- path: '.lightdash/context/prompt-history.md',
171
- contentBase64: Buffer.from('# prompts').toString('base64'),
172
- },
173
- theme: {
174
- instructions: {
175
- path: '.lightdash/context/theme/instructions.md',
176
- contentBase64: Buffer.from('# theme').toString('base64'),
177
- },
178
- assets: [],
179
- skippedAssetCount: 0,
180
- },
181
- });
182
- expect(await fs_1.promises.readFile(path.join(dir, '.lightdash/context/semantic-layer.yml'), 'utf-8')).toBe('models: []');
183
- expect(await fs_1.promises.readFile(path.join(dir, '.lightdash/context/theme/instructions.md'), 'utf-8')).toBe('# theme');
184
- await expect(fs_1.promises.access(path.join(dir, '.lightdash/context/parameters.yml'))).rejects.toThrow();
185
- });
186
- it('writes sharded semantic layer files and clears the previous context snapshot', async () => {
187
- const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-ctx-'));
188
- await fs_1.promises.mkdir(path.join(dir, '.lightdash/context/models'), {
189
- recursive: true,
190
- });
191
- await fs_1.promises.writeFile(path.join(dir, '.lightdash/context/models/deleted_model.yml'), 'models: []');
192
- await fs_1.promises.writeFile(path.join(dir, '.lightdash/context/parameters.yml'), 'parameters: {}');
193
- await fs_1.promises.mkdir(path.join(dir, 'src'), { recursive: true });
194
- await fs_1.promises.writeFile(path.join(dir, 'src/App.tsx'), '// keep');
195
- await (0, appCodeFiles_1.writeContextToDir)(dir, {
196
- semanticLayer: {
197
- path: '.lightdash/context/semantic-layer.yml',
198
- contentBase64: Buffer.from('# see models/').toString('base64'),
199
- },
200
- semanticLayerFiles: [
201
- {
202
- path: '.lightdash/context/models/orders.yml',
203
- contentBase64: Buffer.from('models: []').toString('base64'),
204
- },
205
- ],
206
- parameters: null,
207
- promptHistory: {
208
- path: '.lightdash/context/prompt-history.md',
209
- contentBase64: Buffer.from('# prompts').toString('base64'),
210
- },
211
- theme: { instructions: null, assets: [], skippedAssetCount: 0 },
212
- });
213
- expect(await fs_1.promises.readFile(path.join(dir, '.lightdash/context/models/orders.yml'), 'utf-8')).toBe('models: []');
214
- await expect(fs_1.promises.access(path.join(dir, '.lightdash/context/models/deleted_model.yml'))).rejects.toThrow();
215
- await expect(fs_1.promises.access(path.join(dir, '.lightdash/context/parameters.yml'))).rejects.toThrow();
216
- expect(await fs_1.promises.readFile(path.join(dir, 'src/App.tsx'), 'utf-8')).toBe('// keep');
217
- });
218
- describe('appFolderName', () => {
219
- it('returns the slugified name when no collision', () => {
220
- const taken = new Set();
221
- expect((0, appCodeFiles_1.appFolderName)('My Sales App', 'abcd1234-ef56-7890-ab12-cdef01234567', taken)).toBe('my-sales-app');
222
- });
223
- it('returns uuid-suffixed name on collision (first 8 chars of UUID)', () => {
224
- const taken = new Set(['my-sales-app']);
225
- // UUID first 8 chars: 'abcd1234'
226
- expect((0, appCodeFiles_1.appFolderName)('My Sales App', 'abcd1234-ef56-7890-ab12-cdef01234567', taken)).toBe('my-sales-app-abcd1234');
227
- });
228
- it('produces distinct names for two colliding apps with different UUIDs', () => {
229
- const taken = new Set(['my-sales-app']);
230
- // First UUID: 'aaaa1111-...' → suffix 'aaaa1111'
231
- const first = (0, appCodeFiles_1.appFolderName)('My Sales App', 'aaaa1111-bbbb-cccc-dddd-eeeeeeeeeeee', taken);
232
- taken.add(first);
233
- // Second UUID: 'cccc2222-...' → suffix 'cccc2222'
234
- const second = (0, appCodeFiles_1.appFolderName)('My Sales App', 'cccc2222-dddd-eeee-ffff-000000000000', taken);
235
- expect(first).not.toBe(second);
236
- expect(first).toBe('my-sales-app-aaaa1111');
237
- expect(second).toBe('my-sales-app-cccc2222');
238
- });
239
- // generateSlug returns a RANDOM string for names that sanitize to nothing,
240
- // which would give an unnamed app a different folder on every download.
241
- it('uses a stable untitled-app-<uuid8> folder for an empty name', () => {
242
- const uuid = 'abcd1234-ef56-7890-ab12-cdef01234567';
243
- expect((0, appCodeFiles_1.appFolderName)('', uuid, new Set())).toBe('untitled-app-abcd1234');
244
- // Deterministic: a re-download must land in the same folder.
245
- expect((0, appCodeFiles_1.appFolderName)('', uuid, new Set())).toBe('untitled-app-abcd1234');
246
- });
247
- it('uses the stable fallback for a name with no alphanumeric characters', () => {
248
- expect((0, appCodeFiles_1.appFolderName)('!!!', 'abcd1234-ef56-7890-ab12-cdef01234567', new Set())).toBe('untitled-app-abcd1234');
249
- });
250
- });
251
- describe('resolveAppFolderName', () => {
252
- it('uses the manifest slug when present', () => {
253
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
254
- expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, slug: 'sales-app' }, new Set())).toBe('sales-app');
255
- });
256
- it('falls back to appFolderName when the manifest has no slug', () => {
257
- const uuid = 'abcd1234-ef56-7890-ab12-cdef01234567';
258
- const code = makeCode(uuid, 'proj-uuid-1');
259
- expect((0, appCodeFiles_1.resolveAppFolderName)(code.manifest, new Set())).toBe((0, appCodeFiles_1.appFolderName)(code.manifest.name, uuid, new Set()));
260
- });
261
- it('uses the slug for uuid-free manifests (slug-aware servers)', () => {
262
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
263
- expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, appUuid: undefined, slug: 'sales-app' }, new Set())).toBe('sales-app');
264
- });
265
- // Defense-in-depth: a tampered manifest (or a server of unknown version)
266
- // must not be able to steer the write path via an invalid slug.
267
- it('falls back to appFolderName when the slug is a path-traversal attempt', () => {
268
- const uuid = 'abcd1234-ef56-7890-ab12-cdef01234567';
269
- const code = makeCode(uuid, 'proj-uuid-1');
270
- expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, slug: '../../../../tmp/evil' }, new Set())).toBe((0, appCodeFiles_1.appFolderName)(code.manifest.name, uuid, new Set()));
271
- });
272
- it('still uses a valid manifest slug (unaffected by the new validation)', () => {
273
- const code = makeCode('app-uuid-1', 'proj-uuid-1');
274
- expect((0, appCodeFiles_1.resolveAppFolderName)({ ...code.manifest, slug: 'sales-app-2' }, new Set())).toBe('sales-app-2');
275
- });
276
- });
277
- // ─── readDependenciesFromDir ──────────────────────────────────────────────────
278
- const makePackageJson = (deps = {}) => JSON.stringify({ name: 'test-app', dependencies: deps });
279
- const LOCKFILE_CONTENT = 'lockfileVersion: 9.0\n\npackages:\n react@19.0.0:\n';
280
- describe('readDependenciesFromDir', () => {
281
- let dir;
282
- beforeEach(async () => {
283
- dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-deps-'));
284
- });
285
- afterEach(async () => {
286
- await fs_1.promises.rm(dir, { recursive: true, force: true });
287
- });
288
- it('returns null when neither package.json nor pnpm-lock.yaml exist', async () => {
289
- expect(await (0, appCodeFiles_1.readDependenciesFromDir)(dir)).toBeNull();
290
- });
291
- it('returns the deps pair when both files are present', async () => {
292
- await fs_1.promises.writeFile(path.join(dir, 'package.json'), makePackageJson());
293
- await fs_1.promises.writeFile(path.join(dir, 'pnpm-lock.yaml'), LOCKFILE_CONTENT);
294
- const result = await (0, appCodeFiles_1.readDependenciesFromDir)(dir);
295
- expect(result).not.toBeNull();
296
- expect(result?.packageJson).toBe(makePackageJson());
297
- expect(result?.lockfile).toBe(LOCKFILE_CONTENT);
298
- expect(result?.hasNpmLockfile).toBe(false);
299
- });
300
- // The download scaffold always writes package.json but never a lockfile,
301
- // so this state is every freshly downloaded template-only app — it must
302
- // read back cleanly, not throw.
303
- it('returns the package.json with a null lockfile when pnpm-lock.yaml is absent', async () => {
304
- await fs_1.promises.writeFile(path.join(dir, 'package.json'), makePackageJson());
305
- const result = await (0, appCodeFiles_1.readDependenciesFromDir)(dir);
306
- expect(result).not.toBeNull();
307
- expect(result?.packageJson).toBe(makePackageJson());
308
- expect(result?.lockfile).toBeNull();
309
- });
310
- it('throws when pnpm-lock.yaml exists but package.json is absent', async () => {
311
- await fs_1.promises.writeFile(path.join(dir, 'pnpm-lock.yaml'), LOCKFILE_CONTENT);
312
- await expect((0, appCodeFiles_1.readDependenciesFromDir)(dir)).rejects.toThrow(/package\.json/);
313
- });
314
- it('flags a stray package-lock.json when the pnpm lockfile is absent', async () => {
315
- await fs_1.promises.writeFile(path.join(dir, 'package.json'), makePackageJson());
316
- await fs_1.promises.writeFile(path.join(dir, 'package-lock.json'), '{}');
317
- const result = await (0, appCodeFiles_1.readDependenciesFromDir)(dir);
318
- expect(result?.lockfile).toBeNull();
319
- expect(result?.hasNpmLockfile).toBe(true);
320
- });
321
- });
322
- // ─── buildDepsWarningLines ────────────────────────────────────────────────────
323
- describe('buildDepsWarningLines', () => {
324
- const TEMPLATE = {
325
- react: '19.2.5',
326
- lodash: '4.17.21',
327
- };
328
- it('labels a brand-new package as "not in default template"', () => {
329
- const lines = (0, appCodeFiles_1.buildDepsWarningLines)({ 'my-lib': '^2.0.0' }, TEMPLATE);
330
- expect(lines).toHaveLength(1);
331
- expect(lines[0]).toContain('my-lib@^2.0.0');
332
- expect(lines[0]).toContain('not in default template');
333
- });
334
- it('labels a version override with the template version', () => {
335
- const lines = (0, appCodeFiles_1.buildDepsWarningLines)({ react: '18.3.1' }, TEMPLATE);
336
- expect(lines).toHaveLength(1);
337
- expect(lines[0]).toContain('react@18.3.1');
338
- expect(lines[0]).toContain('overrides template 19.2.5');
339
- });
340
- it('returns one line per custom package', () => {
341
- const custom = { 'pkg-a': '^1.0.0', 'pkg-b': '2.0.0' };
342
- const lines = (0, appCodeFiles_1.buildDepsWarningLines)(custom, TEMPLATE);
343
- expect(lines).toHaveLength(2);
344
- });
345
- it('returns an empty array when there are no custom deps', () => {
346
- expect((0, appCodeFiles_1.buildDepsWarningLines)({}, TEMPLATE)).toEqual([]);
347
- });
348
- });
349
- // ─── attachDependenciesToCode ─────────────────────────────────────────────────
350
- describe('attachDependenciesToCode', () => {
351
- const baseCode = makeCode('app-1', 'proj-1');
352
- const deps = {
353
- packageJson: makePackageJson({ react: '18.3.1' }),
354
- lockfile: LOCKFILE_CONTENT,
355
- };
356
- it('returns the original code object (by reference) when customDeps is empty', () => {
357
- const result = (0, appCodeFiles_1.attachDependenciesToCode)(baseCode, {}, deps);
358
- expect(result).toBe(baseCode);
359
- expect(result.dependencies).toBeUndefined();
360
- });
361
- it('returns a new code object with dependencies attached when customDeps is non-empty', () => {
362
- const result = (0, appCodeFiles_1.attachDependenciesToCode)(baseCode, { react: '18.3.1' }, deps);
363
- expect(result).not.toBe(baseCode);
364
- expect(result.dependencies).toEqual(deps);
365
- });
366
- it('preserves all other code fields when attaching dependencies', () => {
367
- const result = (0, appCodeFiles_1.attachDependenciesToCode)(baseCode, { react: '18.3.1' }, deps);
368
- expect(result.manifest).toBe(baseCode.manifest);
369
- expect(result.files).toBe(baseCode.files);
370
- });
371
- });
372
- describe('applySdkMirrorToTemplateDeps', () => {
373
- const template = {
374
- '@lightdash/query-sdk': '0.3326.1',
375
- react: '19.2.5',
376
- };
377
- it('mirrors the declared SDK spec so an older pin never counts as custom', () => {
378
- const result = (0, appCodeFiles_1.applySdkMirrorToTemplateDeps)(template, JSON.stringify({
379
- dependencies: { '@lightdash/query-sdk': '0.3315.3' },
380
- }));
381
- expect(result['@lightdash/query-sdk']).toBe('0.3315.3');
382
- expect(result.react).toBe('19.2.5');
383
- });
384
- it('leaves the baseline untouched when the SDK is not declared', () => {
385
- const result = (0, appCodeFiles_1.applySdkMirrorToTemplateDeps)(template, JSON.stringify({ dependencies: { react: '19.2.5' } }));
386
- expect(result).toEqual(template);
387
- });
388
- it('leaves the baseline untouched for unparseable packageJson', () => {
389
- expect((0, appCodeFiles_1.applySdkMirrorToTemplateDeps)(template, 'not-json')).toEqual(template);
390
- });
391
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=appsDownload.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appsDownload.test.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/appsDownload.test.ts"],"names":[],"mappings":""}