@lightdash/cli 0.1440.3 → 0.1440.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,19 @@
1
1
  import ora from 'ora';
2
+ type PromptAnswer = {
3
+ useFallbackDbtVersion?: boolean;
4
+ };
2
5
  declare class GlobalState {
3
6
  private verbose;
4
7
  private activeSpinner;
8
+ private savedPromptAnswers;
9
+ constructor();
5
10
  getActiveSpinner(): ora.Ora | undefined;
6
11
  startSpinner(options?: ora.Options | string): ora.Ora;
7
12
  log(message: unknown, ...optionalParams: unknown[]): void;
8
13
  setVerbose(verbose: boolean): void;
14
+ getSavedPromptAnswer<T extends keyof PromptAnswer>(prompt: T): PromptAnswer[T] | undefined;
15
+ savePromptAnswer<T extends keyof PromptAnswer>(prompt: T, value: PromptAnswer[T]): void;
16
+ clearPromptAnswer(): void;
9
17
  debug(message: string): void;
10
18
  }
11
19
  declare const _default: GlobalState;
@@ -6,6 +6,10 @@ const styles = tslib_1.__importStar(require("./styles"));
6
6
  class GlobalState {
7
7
  verbose = false;
8
8
  activeSpinner;
9
+ savedPromptAnswers;
10
+ constructor() {
11
+ this.savedPromptAnswers = {};
12
+ }
9
13
  getActiveSpinner() {
10
14
  return this.activeSpinner;
11
15
  }
@@ -26,6 +30,15 @@ class GlobalState {
26
30
  setVerbose(verbose) {
27
31
  this.verbose = verbose;
28
32
  }
33
+ getSavedPromptAnswer(prompt) {
34
+ return this.savedPromptAnswers[prompt];
35
+ }
36
+ savePromptAnswer(prompt, value) {
37
+ this.savedPromptAnswers[prompt] = value;
38
+ }
39
+ clearPromptAnswer() {
40
+ this.savedPromptAnswers = {};
41
+ }
29
42
  debug(message) {
30
43
  if (this.verbose) {
31
44
  this.log(styles.debug(message));
@@ -4,7 +4,6 @@ exports.compileHandler = exports.compile = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@lightdash/common");
6
6
  const warehouses_1 = require("@lightdash/warehouses");
7
- const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
7
  const path_1 = tslib_1.__importDefault(require("path"));
9
8
  const uuid_1 = require("uuid");
10
9
  const analytics_1 = require("../analytics/analytics");
@@ -25,29 +24,12 @@ const compile = async (options) => {
25
24
  event: 'compile.started',
26
25
  properties: {
27
26
  executionId,
28
- dbtVersion,
27
+ dbtVersion: dbtVersion.verboseVersion,
29
28
  useDbtList: !!options.useDbtList,
30
29
  skipWarehouseCatalog: !!options.skipWarehouseCatalog,
31
30
  skipDbtCompile: !!options.skipDbtCompile,
32
31
  },
33
32
  });
34
- if (!(0, getDbtVersion_1.isSupportedDbtVersion)(dbtVersion)) {
35
- if (process.env.CI === 'true') {
36
- console.error(`Your dbt version ${dbtVersion} does not match our supported versions (1.3.* - 1.9.*), this could cause problems on compile or validation.`);
37
- }
38
- else {
39
- const answers = await inquirer_1.default.prompt([
40
- {
41
- type: 'confirm',
42
- name: 'isConfirm',
43
- message: `${styles.warning(`Your dbt version ${dbtVersion} does not match our supported version (1.3.* - 1.9.*), this could cause problems on compile or validation.`)}\nDo you still want to continue?`,
44
- },
45
- ]);
46
- if (!answers.isConfirm) {
47
- throw new Error(`Unsupported dbt version ${dbtVersion}`);
48
- }
49
- }
50
- }
51
33
  const absoluteProjectPath = path_1.default.resolve(options.projectDir);
52
34
  const absoluteProfilesPath = path_1.default.resolve(options.profilesDir);
53
35
  globalState_1.default.debug(`> Compiling with project dir ${absoluteProjectPath}`);
@@ -95,7 +77,7 @@ ${errors.join('')}`));
95
77
  event: 'compile.error',
96
78
  properties: {
97
79
  executionId,
98
- dbtVersion,
80
+ dbtVersion: dbtVersion.verboseVersion,
99
81
  error: `Dbt adapter ${manifest.metadata.adapter_type} is not supported`,
100
82
  },
101
83
  });
@@ -130,7 +112,7 @@ ${errors.join('')}`));
130
112
  explores: explores.length,
131
113
  errors,
132
114
  dbtMetrics: Object.values(manifest.metrics).length,
133
- dbtVersion,
115
+ dbtVersion: dbtVersion.verboseVersion,
134
116
  },
135
117
  });
136
118
  return explores;
@@ -55,7 +55,7 @@ const askPermissionToStoreWarehouseCredentials = async () => {
55
55
  return savedAnswer;
56
56
  };
57
57
  const createProject = async (options) => {
58
- const dbtVersion = await (0, getDbtVersion_1.getSupportedDbtVersion)();
58
+ const dbtVersion = await (0, getDbtVersion_1.getDbtVersion)();
59
59
  const absoluteProjectPath = path_1.default.resolve(options.projectDir);
60
60
  const absoluteProfilesPath = path_1.default.resolve(options.profilesDir);
61
61
  const context = await (0, context_1.getDbtContext)({ projectDir: absoluteProjectPath });
@@ -102,7 +102,7 @@ const createProject = async (options) => {
102
102
  target: targetName,
103
103
  },
104
104
  upstreamProjectUuid: options.upstreamProjectUuid,
105
- dbtVersion,
105
+ dbtVersion: dbtVersion.versionOption,
106
106
  };
107
107
  return (0, apiClient_1.lightdashApi)({
108
108
  method: 'POST',
@@ -90,8 +90,8 @@ async function dbtList(options) {
90
90
  'unique_id',
91
91
  ];
92
92
  const version = await (0, getDbtVersion_1.getDbtVersion)();
93
- // older dbt versions don't support --quiet flag
94
- if (!version.startsWith('1.3.') && !version.startsWith('1.4.')) {
93
+ // only dbt 1.5 and above support --quiet flag
94
+ if (version.versionOption !== common_1.SupportedDbtVersions.V1_4) {
95
95
  args.push('--quiet');
96
96
  }
97
97
  globalState_1.default.debug(`> Running: dbt ls ${args.join(' ')}`);
@@ -1,4 +1,7 @@
1
- import { SupportedDbtVersions } from '@lightdash/common';
2
- export declare const getDbtVersion: () => Promise<string>;
3
- export declare const getSupportedDbtVersion: () => Promise<SupportedDbtVersions | import("@lightdash/common").DbtVersionOptionLatest.LATEST>;
4
- export declare const isSupportedDbtVersion: (version: string) => boolean;
1
+ import { DbtVersionOption } from '@lightdash/common';
2
+ type DbtVersion = {
3
+ verboseVersion: string;
4
+ versionOption: DbtVersionOption;
5
+ };
6
+ export declare const getDbtVersion: () => Promise<DbtVersion>;
7
+ export {};
@@ -1,19 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isSupportedDbtVersion = exports.getSupportedDbtVersion = exports.getDbtVersion = void 0;
3
+ exports.getDbtVersion = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@lightdash/common");
6
6
  const execa_1 = tslib_1.__importDefault(require("execa"));
7
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
+ const globalState_1 = tslib_1.__importDefault(require("../../globalState"));
7
9
  const styles = tslib_1.__importStar(require("../../styles"));
8
- const getDbtVersion = async () => {
10
+ const DBT_CORE_VERSION_REGEX = /installed:.*/;
11
+ const getDbtCLIVersion = async () => {
9
12
  try {
10
13
  const { all } = await (0, execa_1.default)('dbt', ['--version'], {
11
14
  all: true,
12
15
  stdio: ['pipe', 'pipe', 'pipe'],
13
16
  });
14
17
  const logs = all || '';
15
- const coreVersionRegex = /installed:.*/;
16
- const version = await logs.match(coreVersionRegex);
18
+ const version = logs.match(DBT_CORE_VERSION_REGEX);
17
19
  if (version === null || version.length === 0)
18
20
  throw new common_1.ParseError(`Can't locate dbt --version: ${logs}`);
19
21
  return version[0].split(':')[1].trim();
@@ -23,9 +25,7 @@ const getDbtVersion = async () => {
23
25
  throw new common_1.ParseError(`Failed to get dbt --version:\n ${msg}`);
24
26
  }
25
27
  };
26
- exports.getDbtVersion = getDbtVersion;
27
- const getSupportedDbtVersion = async () => {
28
- const version = await (0, exports.getDbtVersion)();
28
+ const getSupportedDbtVersionOption = (version) => {
29
29
  if (version.startsWith('1.4.'))
30
30
  return common_1.SupportedDbtVersions.V1_4;
31
31
  if (version.startsWith('1.5.'))
@@ -38,20 +38,47 @@ const getSupportedDbtVersion = async () => {
38
38
  return common_1.SupportedDbtVersions.V1_8;
39
39
  if (version.startsWith('1.9.'))
40
40
  return common_1.SupportedDbtVersions.V1_9;
41
- console.error(styles.warning(`We don't currently support version ${version} on Lightdash, we'll be using ${common_1.DefaultSupportedDbtVersion} instead when dbt is refresh from the UI.`));
42
- return common_1.DefaultSupportedDbtVersion;
41
+ // No supported version found
42
+ return null;
43
+ };
44
+ const getFallbackDbtVersionOption = (version) => {
45
+ if (version.startsWith('1.3.'))
46
+ return common_1.SupportedDbtVersions.V1_4; // legacy|deprecated support for dbt 1.3
47
+ return (0, common_1.getLatestSupportDbtVersion)();
43
48
  };
44
- exports.getSupportedDbtVersion = getSupportedDbtVersion;
45
- const isSupportedDbtVersion = (version) => {
46
- const supportedVersions = [
47
- '1.3.',
48
- '1.4.',
49
- '1.5.',
50
- '1.6.',
51
- '1.7',
52
- '1.8',
53
- '1.9',
54
- ];
55
- return supportedVersions.some((supportedVersion) => version.startsWith(supportedVersion));
49
+ const getDbtVersion = async () => {
50
+ const verboseVersion = await getDbtCLIVersion();
51
+ const supportedVersionOption = getSupportedDbtVersionOption(verboseVersion);
52
+ const fallbackVersionOption = getFallbackDbtVersionOption(verboseVersion);
53
+ const isSupported = !!supportedVersionOption;
54
+ if (!isSupported &&
55
+ !globalState_1.default.getSavedPromptAnswer('useFallbackDbtVersion')) {
56
+ const versions = Object.values(common_1.SupportedDbtVersions);
57
+ const supportedVersionsRangeMessage = `${versions[0]}.* - ${versions[versions.length - 1]}.*`;
58
+ const message = `We don't currently support version ${verboseVersion} on Lightdash. We'll interpret it as version ${fallbackVersionOption} instead, which might cause unexpected errors or behavior. For the best experience, please use a supported version (${supportedVersionsRangeMessage}).`;
59
+ const spinner = globalState_1.default.getActiveSpinner();
60
+ spinner?.stop();
61
+ if (process.env.CI === 'true') {
62
+ console.error(styles.warning(message));
63
+ }
64
+ else {
65
+ const answers = await inquirer_1.default.prompt([
66
+ {
67
+ type: 'confirm',
68
+ name: 'isConfirm',
69
+ message: `${styles.warning(message)}\nDo you still want to continue?`,
70
+ },
71
+ ]);
72
+ if (!answers.isConfirm) {
73
+ throw new Error(`Unsupported dbt version ${verboseVersion}. Please consider using a supported version (${supportedVersionsRangeMessage}).`);
74
+ }
75
+ }
76
+ spinner?.start();
77
+ globalState_1.default.savePromptAnswer('useFallbackDbtVersion', true);
78
+ }
79
+ return {
80
+ verboseVersion,
81
+ versionOption: supportedVersionOption ?? fallbackVersionOption,
82
+ };
56
83
  };
57
- exports.isSupportedDbtVersion = isSupportedDbtVersion;
84
+ exports.getDbtVersion = getDbtVersion;
@@ -0,0 +1,8 @@
1
+ import { ExecaError, ExecaReturnValue } from 'execa';
2
+ export declare const cliMocks: {
3
+ dbt1_3: Partial<ExecaReturnValue<string>>;
4
+ dbt1_4: Partial<ExecaReturnValue<string>>;
5
+ dbt1_9: Partial<ExecaReturnValue<string>>;
6
+ dbt20_1: Partial<ExecaReturnValue<string>>;
7
+ error: Partial<ExecaError<string>>;
8
+ };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cliMocks = void 0;
4
+ exports.cliMocks = {
5
+ dbt1_3: {
6
+ all: 'Core:\n' +
7
+ ' - installed: 1.3.0\n' +
8
+ ' - latest: 1.9.1 - Update available!\n',
9
+ },
10
+ dbt1_4: {
11
+ all: 'Core:\n' +
12
+ ' - installed: 1.4.9\n' +
13
+ ' - latest: 1.9.1 - Update available!\n' +
14
+ '\n' +
15
+ ' Your version of dbt-core is out of date!\n' +
16
+ ' You can find instructions for upgrading here:\n' +
17
+ ' https://docs.getdbt.com/docs/installation\n' +
18
+ '\n' +
19
+ 'Plugins:\n' +
20
+ ' - trino: 1.4.2 - Update available!\n' +
21
+ ' - databricks: 1.4.3 - Update available!\n' +
22
+ ' - redshift: 1.4.1 - Update available!\n' +
23
+ ' - spark: 1.4.3 - Update available!\n' +
24
+ ' - bigquery: 1.4.5 - Update available!\n' +
25
+ ' - snowflake: 1.4.5 - Update available!\n' +
26
+ ' - postgres: 1.4.9 - Update available!\n' +
27
+ '\n' +
28
+ ' At least one plugin is out of date or incompatible with dbt-core.\n' +
29
+ ' You can find instructions for upgrading here:\n' +
30
+ ' https://docs.getdbt.com/docs/installation\n',
31
+ },
32
+ dbt1_9: {
33
+ all: 'Core:\n' +
34
+ ' - installed: 1.9.1\n' +
35
+ ' - latest: 1.9.1 - Up to date!\n' +
36
+ '\n' +
37
+ 'Plugins:\n' +
38
+ ' - databricks: 1.9.1 - Up to date!\n' +
39
+ ' - redshift: 1.9.0 - Up to date!\n' +
40
+ ' - spark: 1.9.0 - Up to date!\n' +
41
+ ' - bigquery: 1.9.0 - Up to date!\n' +
42
+ ' - snowflake: 1.9.0 - Up to date!\n' +
43
+ ' - postgres: 1.9.0 - Up to date!\n',
44
+ },
45
+ dbt20_1: {
46
+ all: 'Core:\n' +
47
+ ' - installed: 20.1.0\n' +
48
+ ' - latest: 20.2.0 - Update available!\n',
49
+ },
50
+ error: {
51
+ shortMessage: 'error message',
52
+ all: 'all error messages',
53
+ },
54
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,83 @@
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 execa_1 = tslib_1.__importDefault(require("execa"));
6
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
7
+ const globalState_1 = tslib_1.__importDefault(require("../../globalState"));
8
+ const getDbtVersion_1 = require("./getDbtVersion");
9
+ const getDbtVersion_mocks_1 = require("./getDbtVersion.mocks");
10
+ jest.mock('execa');
11
+ const execaMock = execa_1.default;
12
+ jest.mock('inquirer', () => ({
13
+ prompt: jest.fn(),
14
+ }));
15
+ const promptMock = inquirer_1.default.prompt;
16
+ const consoleError = jest.spyOn(console, 'error').mockImplementation(() => { });
17
+ describe('Get dbt version', () => {
18
+ const { env } = process;
19
+ beforeEach(() => {
20
+ jest.resetAllMocks();
21
+ jest.resetModules();
22
+ process.env = { ...env };
23
+ execaMock.mockImplementation(async () => getDbtVersion_mocks_1.cliMocks.dbt1_4);
24
+ promptMock.mockImplementation(async () => ({ isConfirm: true }));
25
+ globalState_1.default.clearPromptAnswer();
26
+ });
27
+ afterEach(() => {
28
+ process.env = env;
29
+ });
30
+ describe('getDbtVersion', () => {
31
+ test('should return error if dbt cli is not installed', async () => {
32
+ execaMock.mockImplementation(async () => getDbtVersion_mocks_1.cliMocks.error);
33
+ await expect((0, getDbtVersion_1.getDbtVersion)()).rejects.toThrowError();
34
+ });
35
+ test('should return supported dbt versions', async () => {
36
+ // Test for 1.4
37
+ const version = await (0, getDbtVersion_1.getDbtVersion)();
38
+ expect(version.verboseVersion).toEqual('1.4.9');
39
+ expect(version.versionOption).toEqual(common_1.SupportedDbtVersions.V1_4);
40
+ // Test for 1.9
41
+ execaMock.mockImplementation(async () => getDbtVersion_mocks_1.cliMocks.dbt1_9);
42
+ const version2 = await (0, getDbtVersion_1.getDbtVersion)();
43
+ expect(version2.verboseVersion).toEqual('1.9.1');
44
+ expect(version2.versionOption).toEqual(common_1.SupportedDbtVersions.V1_9);
45
+ });
46
+ test('when CI=true, should warn user about unsupported version and return fallback', async () => {
47
+ process.env.CI = 'true';
48
+ // Test for 1.3
49
+ execaMock.mockImplementation(async () => getDbtVersion_mocks_1.cliMocks.dbt1_3);
50
+ const version = await (0, getDbtVersion_1.getDbtVersion)();
51
+ expect(version.verboseVersion).toEqual('1.3.0');
52
+ expect(version.versionOption).toEqual(common_1.SupportedDbtVersions.V1_4);
53
+ expect(consoleError).toHaveBeenCalledTimes(1);
54
+ expect(consoleError).nthCalledWith(1, expect.stringContaining("We don't currently support version 1.3.0"));
55
+ // Clear saved prompt answer
56
+ globalState_1.default.clearPromptAnswer();
57
+ // Test for future version
58
+ execaMock.mockImplementation(async () => getDbtVersion_mocks_1.cliMocks.dbt20_1);
59
+ const version2 = await (0, getDbtVersion_1.getDbtVersion)();
60
+ expect(version2.verboseVersion).toEqual('20.1.0');
61
+ expect(version2.versionOption).toEqual((0, common_1.getLatestSupportDbtVersion)());
62
+ expect(consoleError).toHaveBeenCalledTimes(2);
63
+ expect(consoleError).nthCalledWith(2, expect.stringContaining("We don't currently support version 20.1.0"));
64
+ });
65
+ test('when CI=false, should return fallback version if user confirms', async () => {
66
+ process.env.CI = 'false';
67
+ execaMock.mockImplementation(async () => getDbtVersion_mocks_1.cliMocks.dbt1_3);
68
+ const version = await (0, getDbtVersion_1.getDbtVersion)();
69
+ expect(version.verboseVersion).toEqual('1.3.0');
70
+ expect(version.versionOption).toEqual(common_1.SupportedDbtVersions.V1_4);
71
+ expect(promptMock).toHaveBeenCalledTimes(1);
72
+ expect(consoleError).toHaveBeenCalledTimes(0);
73
+ });
74
+ test('when CI=false, should return error if user declines fallback', async () => {
75
+ process.env.CI = 'false';
76
+ execaMock.mockImplementation(async () => getDbtVersion_mocks_1.cliMocks.dbt1_3);
77
+ promptMock.mockImplementation(async () => ({ isConfirm: false }));
78
+ await expect((0, getDbtVersion_1.getDbtVersion)()).rejects.toThrowError();
79
+ expect(promptMock).toHaveBeenCalledTimes(1);
80
+ expect(consoleError).toHaveBeenCalledTimes(0);
81
+ });
82
+ });
83
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "0.1440.3",
3
+ "version": "0.1440.5",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "lightdash": "dist/index.js"
@@ -11,8 +11,8 @@
11
11
  ],
12
12
  "dependencies": {
13
13
  "@actions/core": "^1.11.1",
14
- "@lightdash/common": "^0.1440.3",
15
- "@lightdash/warehouses": "^0.1440.3",
14
+ "@lightdash/common": "^0.1440.5",
15
+ "@lightdash/warehouses": "^0.1440.5",
16
16
  "@types/columnify": "^1.5.1",
17
17
  "ajv": "^8.11.0",
18
18
  "ajv-formats": "^2.1.1",