@lightdash/cli 2.603.1 → 2.603.3

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 +1 @@
1
- {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/handlers/preview.ts"],"names":[],"mappings":"AACA,OAAO,EACH,+BAA+B,EAQlC,MAAM,mBAAmB,CAAC;AAoB3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAalD,KAAK,qBAAqB,GAAG,iBAAiB,GAAG;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,EAAE,+BAA+B,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,wBAAwB,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAoBF,KAAK,yBAAyB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AA2GF,eAAO,MAAM,cAAc,oBACN,qBAAqB,KACvC,OAAO,CAAC,IAAI,CAoTd,CAAC;AAiHF,eAAO,MAAM,mBAAmB,oBACX,qBAAqB,KACvC,OAAO,CAAC,IAAI,CAiNd,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAClB,yBAAyB,KACnC,OAAO,CAAC,IAAI,CA2Cd,CAAC"}
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/handlers/preview.ts"],"names":[],"mappings":"AACA,OAAO,EACH,+BAA+B,EAUlC,MAAM,mBAAmB,CAAC;AAoB3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAalD,KAAK,qBAAqB,GAAG,iBAAiB,GAAG;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,EAAE,+BAA+B,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,wBAAwB,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAoBF,KAAK,yBAAyB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AA2QF,eAAO,MAAM,cAAc,oBACN,qBAAqB,KACvC,OAAO,CAAC,IAAI,CAwWd,CAAC;AAEF,eAAO,MAAM,mBAAmB,oBACX,qBAAqB,KACvC,OAAO,CAAC,IAAI,CA2Qd,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAClB,yBAAyB,KACnC,OAAO,CAAC,IAAI,CA2Cd,CAAC"}
@@ -104,6 +104,108 @@ const getPreviewProject = async (name) => {
104
104
  });
105
105
  return projects.find((project) => project.type === common_1.ProjectType.PREVIEW && project.name === name);
106
106
  };
107
+ const withCurrentSettings = (credentials, current) => {
108
+ if (!current || current.type !== credentials.type)
109
+ return credentials;
110
+ const defined = Object.fromEntries(Object.entries(credentials).filter(([, value]) => value !== undefined));
111
+ return { ...current, ...defined };
112
+ };
113
+ const resolveSourceConnection = async (project, sourceOption, upstreamProjectUuid) => {
114
+ if (project.connectionRoute !== 'multi') {
115
+ return { sourceUuid: undefined, connection: { kind: 'primary' } };
116
+ }
117
+ const sourceUuid = await (0, sourceSelection_1.resolveProjectSourceUuid)(project.projectUuid, sourceOption, upstreamProjectUuid);
118
+ if (!sourceUuid)
119
+ return { sourceUuid, connection: { kind: 'primary' } };
120
+ let bindings;
121
+ try {
122
+ bindings = await (0, apiClient_1.lightdashApi)({
123
+ method: 'GET',
124
+ url: `/api/v1/projects/${project.projectUuid}/warehouse-connections/dbt-sources/bindings`,
125
+ body: undefined,
126
+ });
127
+ }
128
+ catch {
129
+ return { sourceUuid, connection: { kind: 'unknown', sourceUuid } };
130
+ }
131
+ const boundConnectionUuid = bindings.sources.find((item) => item.projectDbtSourceUuid === sourceUuid)?.warehouseConnectionUuid ?? null;
132
+ if (boundConnectionUuid === null) {
133
+ return { sourceUuid, connection: { kind: 'primary' } };
134
+ }
135
+ const boundConnection = bindings.connections.find((item) => item.warehouseConnectionUuid === boundConnectionUuid);
136
+ if (!boundConnection) {
137
+ return { sourceUuid, connection: { kind: 'unknown', sourceUuid } };
138
+ }
139
+ return {
140
+ sourceUuid,
141
+ connection: boundConnection.isOriginal
142
+ ? { kind: 'primary' }
143
+ : { kind: 'extra', connection: boundConnection },
144
+ };
145
+ };
146
+ // Credentials resolved locally (AWS SSO, Snowflake SSO) expire, so a re-run
147
+ // with the same name pushes freshly resolved ones into the existing preview.
148
+ const refreshPreviewWarehouseCredentials = async (projectUuid, options, upstreamProjectUuid) => {
149
+ const source = (0, warehouseConnection_1.getWarehouseCredentialsSource)(options);
150
+ if (source.source === 'organization') {
151
+ globalState_1.default.debug(`> Preview uses organization warehouse credentials "${source.name}", nothing to refresh`);
152
+ return;
153
+ }
154
+ if (source.source === 'none') {
155
+ globalState_1.default.debug('> Skipping warehouse credentials refresh (--no-warehouse-credentials)');
156
+ return;
157
+ }
158
+ const loaded = await (0, createProject_1.loadWarehouseCredentialsFromProfiles)({
159
+ projectDir: options.projectDir,
160
+ profilesDir: options.profilesDir,
161
+ target: options.target,
162
+ profile: options.profile,
163
+ startOfWeek: options.startOfWeek,
164
+ assumeYes: options.assumeYes,
165
+ targetPath: options.targetPath,
166
+ });
167
+ if (!loaded) {
168
+ console.error(styles.warning('Keeping the warehouse credentials already stored on the preview.'));
169
+ return;
170
+ }
171
+ const project = await (0, refresh_1.getProject)(projectUuid);
172
+ const { connection: sourceConnection } = await resolveSourceConnection(project, options.source, upstreamProjectUuid);
173
+ if (sourceConnection.kind === 'unknown') {
174
+ console.error(styles.warning(`Could not read which connection source ${options.source ?? sourceConnection.sourceUuid} uses; keeping the warehouse credentials already stored on the preview.`));
175
+ return;
176
+ }
177
+ const connection = sourceConnection.kind === 'extra'
178
+ ? sourceConnection.connection
179
+ : undefined;
180
+ const spinner = globalState_1.default.startSpinner(connection
181
+ ? ` Refreshing warehouse credentials for "${connection.name}"...`
182
+ : ' Refreshing warehouse credentials...');
183
+ try {
184
+ if (connection) {
185
+ const current = await (0, apiClient_1.lightdashApi)({
186
+ method: 'GET',
187
+ url: `/api/v1/projects/${projectUuid}/warehouse-connections/${connection.warehouseConnectionUuid}`,
188
+ body: undefined,
189
+ });
190
+ const body = {
191
+ warehouseConnection: withCurrentSettings(loaded.credentials, current.warehouseConnection),
192
+ };
193
+ await (0, apiClient_1.lightdashApi)({
194
+ method: 'PATCH',
195
+ url: `/api/v1/projects/${projectUuid}/warehouse-connections/${connection.warehouseConnectionUuid}`,
196
+ body: JSON.stringify(body),
197
+ });
198
+ }
199
+ else {
200
+ await (0, warehouseConnection_1.updateProjectWarehouseConnection)(project, loaded.credentials, 'Refreshing warehouse credentials');
201
+ }
202
+ spinner.succeed(' Warehouse credentials refreshed');
203
+ }
204
+ catch (e) {
205
+ spinner.fail();
206
+ throw e;
207
+ }
208
+ };
107
209
  const previewHandler = async (originalOptions) => {
108
210
  (0, credentials_1.refuseAgentConnection)();
109
211
  const options = { ...originalOptions };
@@ -144,10 +246,11 @@ const previewHandler = async (originalOptions) => {
144
246
  options.combineManifestProjectUuid = config.context?.project;
145
247
  // Validate upstream project before attempting to copy permissions or content
146
248
  let upstreamProjectValid = false;
249
+ let upstreamProject;
147
250
  if (config.context?.project) {
148
251
  try {
149
252
  globalState_1.default.debug(`> Validating upstream project: ${config.context.project}`);
150
- const upstreamProject = await (0, apiClient_1.lightdashApi)({
253
+ upstreamProject = await (0, apiClient_1.lightdashApi)({
151
254
  method: 'GET',
152
255
  url: `/api/v1/projects/${config.context.project}`,
153
256
  body: undefined,
@@ -179,16 +282,29 @@ const previewHandler = async (originalOptions) => {
179
282
  else if (upstreamProjectValid) {
180
283
  console.error(`\n${styles.success('✔')} Copying charts and dashboards from "${config.context?.projectName || 'source project'}"`);
181
284
  }
285
+ const upstreamProjectUuid = config.context?.project && upstreamProjectValid
286
+ ? config.context.project
287
+ : undefined;
288
+ const upstreamSourceConnection = upstreamProject &&
289
+ upstreamProjectUuid &&
290
+ projectTypeConfig.warehouseCredentials !== false &&
291
+ (0, warehouseConnection_1.getWarehouseCredentialsSource)({
292
+ ...options,
293
+ warehouseCredentials: projectTypeConfig.warehouseCredentials,
294
+ }).source === 'profiles'
295
+ ? (await resolveSourceConnection(upstreamProject, options.source, upstreamProjectUuid)).connection
296
+ : { kind: 'primary' };
297
+ const keepsUpstreamPrimary = upstreamSourceConnection.kind !== 'primary';
182
298
  try {
183
299
  const results = await (0, createProject_1.createProject)({
184
300
  ...options,
185
301
  name,
186
302
  type: common_1.ProjectType.PREVIEW,
187
- upstreamProjectUuid: config.context?.project && upstreamProjectValid
188
- ? config.context.project
189
- : undefined,
303
+ upstreamProjectUuid,
190
304
  copyContent: !options.skipCopyContent && upstreamProjectValid,
191
- warehouseCredentials: projectTypeConfig.warehouseCredentials,
305
+ warehouseCredentials: keepsUpstreamPrimary
306
+ ? false
307
+ : projectTypeConfig.warehouseCredentials,
192
308
  expiresIn: options.expiresIn
193
309
  ? parseInt(options.expiresIn, 10)
194
310
  : undefined,
@@ -212,11 +328,6 @@ const previewHandler = async (originalOptions) => {
212
328
  }
213
329
  return;
214
330
  }
215
- if (options.disableTimestampConversion === undefined) {
216
- options.disableTimestampConversion =
217
- (0, timestampConversion_1.getDisableTimestampConversionFromProject)(project.warehouseConnection);
218
- globalState_1.default.debug(`> Using disable-timestamp-conversion=${String(options.disableTimestampConversion ?? false)} from preview project settings`);
219
- }
220
331
  const previewStartTime = Date.now();
221
332
  await analytics_1.LightdashAnalytics.track({
222
333
  event: 'preview.started',
@@ -226,6 +337,24 @@ const previewHandler = async (originalOptions) => {
226
337
  },
227
338
  });
228
339
  try {
340
+ if (keepsUpstreamPrimary) {
341
+ await refreshPreviewWarehouseCredentials(project.projectUuid, {
342
+ ...options,
343
+ warehouseCredentials: projectTypeConfig.warehouseCredentials,
344
+ }, upstreamProjectUuid);
345
+ }
346
+ const previewSource = upstreamProjectUuid
347
+ ? await resolveSourceConnection(await (0, refresh_1.getProject)(project.projectUuid), options.source, upstreamProjectUuid)
348
+ : undefined;
349
+ if (options.disableTimestampConversion === undefined &&
350
+ previewSource?.connection.kind === 'extra') {
351
+ options.disableTimestampConversion = (await (0, timestampConversion_1.getProjectDeploySettings)(undefined, project.projectUuid, previewSource.sourceUuid)).disableTimestampConversion;
352
+ }
353
+ else if (options.disableTimestampConversion === undefined) {
354
+ options.disableTimestampConversion =
355
+ (0, timestampConversion_1.getDisableTimestampConversionFromProject)(project.warehouseConnection);
356
+ globalState_1.default.debug(`> Using disable-timestamp-conversion=${String(options.disableTimestampConversion ?? false)} from preview project settings`);
357
+ }
229
358
  const { explores, isProjectComplete } = await (0, compile_1.compileProject)(options);
230
359
  await (0, deploy_1.deploy)(explores, {
231
360
  ...options,
@@ -320,86 +449,6 @@ const previewHandler = async (originalOptions) => {
320
449
  await cleanupProject(executionId, project.projectUuid, previewStartTime);
321
450
  };
322
451
  exports.previewHandler = previewHandler;
323
- // Credentials resolved locally (AWS SSO, Snowflake SSO) expire, so a re-run
324
- // with the same name pushes freshly resolved ones into the existing preview.
325
- const refreshPreviewWarehouseCredentials = async (projectUuid, options, upstreamProjectUuid) => {
326
- const source = (0, warehouseConnection_1.getWarehouseCredentialsSource)(options);
327
- if (source.source === 'organization') {
328
- globalState_1.default.debug(`> Preview uses organization warehouse credentials "${source.name}", nothing to refresh`);
329
- return;
330
- }
331
- if (source.source === 'none') {
332
- globalState_1.default.debug('> Skipping warehouse credentials refresh (--no-warehouse-credentials)');
333
- return;
334
- }
335
- const loaded = await (0, createProject_1.loadWarehouseCredentialsFromProfiles)({
336
- projectDir: options.projectDir,
337
- profilesDir: options.profilesDir,
338
- target: options.target,
339
- profile: options.profile,
340
- startOfWeek: options.startOfWeek,
341
- assumeYes: options.assumeYes,
342
- targetPath: options.targetPath,
343
- });
344
- if (!loaded) {
345
- console.error(styles.warning('Keeping the warehouse credentials already stored on the preview.'));
346
- return;
347
- }
348
- const project = await (0, refresh_1.getProject)(projectUuid);
349
- let connection;
350
- if (project.connectionRoute === 'multi') {
351
- const sourceUuid = await (0, sourceSelection_1.resolveProjectSourceUuid)(projectUuid, options.source, upstreamProjectUuid);
352
- if (sourceUuid) {
353
- const keepStoredCredentials = () => console.error(styles.warning(`Could not read which connection source ${options.source ?? sourceUuid} uses; keeping the warehouse credentials already stored on the preview.`));
354
- let bindings;
355
- try {
356
- bindings = await (0, apiClient_1.lightdashApi)({
357
- method: 'GET',
358
- url: `/api/v1/projects/${projectUuid}/warehouse-connections/dbt-sources/bindings`,
359
- body: undefined,
360
- });
361
- }
362
- catch {
363
- keepStoredCredentials();
364
- return;
365
- }
366
- const boundConnectionUuid = bindings.sources.find((item) => item.projectDbtSourceUuid === sourceUuid)?.warehouseConnectionUuid ?? null;
367
- if (boundConnectionUuid !== null) {
368
- const boundConnection = bindings.connections.find((item) => item.warehouseConnectionUuid === boundConnectionUuid);
369
- if (!boundConnection) {
370
- keepStoredCredentials();
371
- return;
372
- }
373
- connection = boundConnection.isOriginal
374
- ? undefined
375
- : boundConnection;
376
- }
377
- }
378
- }
379
- const spinner = globalState_1.default.startSpinner(connection
380
- ? ` Refreshing warehouse credentials for "${connection.name}"...`
381
- : ' Refreshing warehouse credentials...');
382
- try {
383
- if (connection) {
384
- const body = {
385
- warehouseConnection: loaded.credentials,
386
- };
387
- await (0, apiClient_1.lightdashApi)({
388
- method: 'PATCH',
389
- url: `/api/v1/projects/${projectUuid}/warehouse-connections/${connection.warehouseConnectionUuid}`,
390
- body: JSON.stringify(body),
391
- });
392
- }
393
- else {
394
- await (0, warehouseConnection_1.updateProjectWarehouseConnection)(project, loaded.credentials, 'Refreshing warehouse credentials');
395
- }
396
- spinner.succeed(' Warehouse credentials refreshed');
397
- }
398
- catch (e) {
399
- spinner.fail();
400
- throw e;
401
- }
402
- };
403
452
  const startPreviewHandler = async (originalOptions) => {
404
453
  (0, credentials_1.refuseAgentConnection)();
405
454
  const options = { ...originalOptions };
@@ -447,7 +496,10 @@ const startPreviewHandler = async (originalOptions) => {
447
496
  warehouseCredentials: projectTypeConfig.warehouseCredentials,
448
497
  }, config.context?.project);
449
498
  // Update
450
- const previewSettings = await (0, timestampConversion_1.getProjectDeploySettings)(options.disableTimestampConversion, previewProject.projectUuid, undefined);
499
+ const previewSource = await resolveSourceConnection(await (0, refresh_1.getProject)(previewProject.projectUuid), options.source, config.context?.project);
500
+ const previewSettings = await (0, timestampConversion_1.getProjectDeploySettings)(options.disableTimestampConversion, previewProject.projectUuid, previewSource.connection.kind === 'unknown'
501
+ ? undefined
502
+ : previewSource.sourceUuid);
451
503
  options.disableTimestampConversion =
452
504
  previewSettings.disableTimestampConversion;
453
505
  const { explores, isProjectComplete } = await (0, compile_1.compileProject)(options);
@@ -478,14 +530,26 @@ const startPreviewHandler = async (originalOptions) => {
478
530
  else {
479
531
  console.error(`\n${styles.success('✔')} Copying charts and dashboards from "${config.context?.projectName || 'source project'}"`);
480
532
  }
533
+ const upstreamProjectUuid = config.context?.project;
534
+ const upstreamSourceConnection = upstreamProjectUuid &&
535
+ projectTypeConfig.warehouseCredentials !== false &&
536
+ (0, warehouseConnection_1.getWarehouseCredentialsSource)({
537
+ ...options,
538
+ warehouseCredentials: projectTypeConfig.warehouseCredentials,
539
+ }).source === 'profiles'
540
+ ? (await resolveSourceConnection(await (0, refresh_1.getProject)(upstreamProjectUuid), options.source, upstreamProjectUuid)).connection
541
+ : { kind: 'primary' };
542
+ const keepsUpstreamPrimary = upstreamSourceConnection.kind !== 'primary';
481
543
  // Create
482
544
  const results = await (0, createProject_1.createProject)({
483
545
  ...options,
484
546
  name: projectName,
485
547
  type: common_1.ProjectType.PREVIEW,
486
- upstreamProjectUuid: config.context?.project,
548
+ upstreamProjectUuid,
487
549
  copyContent: !options.skipCopyContent,
488
- warehouseCredentials: projectTypeConfig.warehouseCredentials,
550
+ warehouseCredentials: keepsUpstreamPrimary
551
+ ? false
552
+ : projectTypeConfig.warehouseCredentials,
489
553
  expiresIn: options.expiresIn
490
554
  ? parseInt(options.expiresIn, 10)
491
555
  : undefined,
@@ -510,7 +574,20 @@ const startPreviewHandler = async (originalOptions) => {
510
574
  name: options.name,
511
575
  },
512
576
  });
513
- if (options.disableTimestampConversion === undefined) {
577
+ if (keepsUpstreamPrimary) {
578
+ await refreshPreviewWarehouseCredentials(project.projectUuid, {
579
+ ...options,
580
+ warehouseCredentials: projectTypeConfig.warehouseCredentials,
581
+ }, upstreamProjectUuid);
582
+ }
583
+ const previewSource = upstreamProjectUuid
584
+ ? await resolveSourceConnection(await (0, refresh_1.getProject)(project.projectUuid), options.source, upstreamProjectUuid)
585
+ : undefined;
586
+ if (options.disableTimestampConversion === undefined &&
587
+ previewSource?.connection.kind === 'extra') {
588
+ options.disableTimestampConversion = (await (0, timestampConversion_1.getProjectDeploySettings)(undefined, project.projectUuid, previewSource.sourceUuid)).disableTimestampConversion;
589
+ }
590
+ else if (options.disableTimestampConversion === undefined) {
514
591
  options.disableTimestampConversion =
515
592
  (0, timestampConversion_1.getDisableTimestampConversionFromProject)(project.warehouseConnection);
516
593
  globalState_1.default.debug(`> Using disable-timestamp-conversion=${String(options.disableTimestampConversion ?? false)} from preview project settings`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "2.603.1",
3
+ "version": "2.603.3",
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": "2.603.1",
26
- "@lightdash/warehouses": "2.603.1",
25
+ "@lightdash/common": "2.603.3",
26
+ "@lightdash/warehouses": "2.603.3",
27
27
  "@sidvind/better-ajv-errors": "7.0.0",
28
28
  "@types/columnify": "1.5.1",
29
29
  "ajv": "8.18.0",