@lightdash/cli 0.2420.0 → 0.2422.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.
@@ -155,7 +155,7 @@ const findAndUpdateModelYaml = async ({ model, table, docs, includeMeta, project
155
155
  });
156
156
  // Delete columns that no longer exist in the warehouse
157
157
  const deletedColumnNames = existingColumnNames.filter((c) => !generatedModel.columns.map((gc) => gc.name).includes(c));
158
- if (deletedColumnNames.length > 0 && process.env.CI !== 'true') {
158
+ if (deletedColumnNames.length > 0 && !globalState_1.default.isNonInteractive()) {
159
159
  let answers = { isConfirm: assumeYes };
160
160
  if (!assumeYes) {
161
161
  const spinner = globalState_1.default.getActiveSpinner();
@@ -6,9 +6,12 @@ type PromptAnswer = {
6
6
  };
7
7
  declare class GlobalState {
8
8
  private verbose;
9
+ private nonInteractive;
9
10
  private activeSpinner;
10
11
  private savedPromptAnswers;
11
12
  constructor();
13
+ setNonInteractive(value: boolean): void;
14
+ isNonInteractive(): boolean;
12
15
  getActiveSpinner(): ora.Ora | undefined;
13
16
  startSpinner(options?: ora.Options | string): ora.Ora;
14
17
  log(message: unknown, ...optionalParams: unknown[]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"globalState.d.ts","sourceRoot":"","sources":["../src/globalState.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,KAAK,YAAY,GAAG;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC;AAEF,cAAM,WAAW;IACb,OAAO,CAAC,OAAO,CAAkB;IAEjC,OAAO,CAAC,aAAa,CAAsB;IAE3C,OAAO,CAAC,kBAAkB,CAAe;;IAMzC,gBAAgB;IAIhB,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG;IAMrD,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE;IAUlD,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,oBAAoB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC7C,MAAM,EAAE,CAAC,GACV,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;IAI9B,gBAAgB,CAAC,CAAC,SAAS,MAAM,YAAY,EACzC,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAK1B,iBAAiB;IAIjB,KAAK,CAAC,OAAO,EAAE,MAAM;IAMrB,cAAc,CAAC,MAAM,EAAE,MAAM;CAKhC;;AAED,wBAAiC"}
1
+ {"version":3,"file":"globalState.d.ts","sourceRoot":"","sources":["../src/globalState.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,KAAK,YAAY,GAAG;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACxC,CAAC;AAEF,cAAM,WAAW;IACb,OAAO,CAAC,OAAO,CAAkB;IAEjC,OAAO,CAAC,cAAc,CAAkB;IAExC,OAAO,CAAC,aAAa,CAAsB;IAE3C,OAAO,CAAC,kBAAkB,CAAe;;IAMzC,iBAAiB,CAAC,KAAK,EAAE,OAAO;IAIhC,gBAAgB,IAAI,OAAO;IAI3B,gBAAgB;IAIhB,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG;IAMrD,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE;IAUlD,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,oBAAoB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC7C,MAAM,EAAE,CAAC,GACV,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;IAI9B,gBAAgB,CAAC,CAAC,SAAS,MAAM,YAAY,EACzC,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAK1B,iBAAiB;IAIjB,KAAK,CAAC,OAAO,EAAE,MAAM;IAMrB,cAAc,CAAC,MAAM,EAAE,MAAM;CAKhC;;AAED,wBAAiC"}
@@ -5,11 +5,18 @@ const ora_1 = tslib_1.__importDefault(require("ora"));
5
5
  const styles = tslib_1.__importStar(require("./styles"));
6
6
  class GlobalState {
7
7
  verbose = false;
8
+ nonInteractive = false;
8
9
  activeSpinner;
9
10
  savedPromptAnswers;
10
11
  constructor() {
11
12
  this.savedPromptAnswers = {};
12
13
  }
14
+ setNonInteractive(value) {
15
+ this.nonInteractive = value;
16
+ }
17
+ isNonInteractive() {
18
+ return this.nonInteractive || process.env.CI === 'true';
19
+ }
13
20
  getActiveSpinner() {
14
21
  return this.activeSpinner;
15
22
  }
@@ -14,6 +14,7 @@ type CreateProjectOptions = {
14
14
  warehouseCredentials?: boolean;
15
15
  organizationCredentials?: string;
16
16
  targetPath?: string;
17
+ assumeYes?: boolean;
17
18
  };
18
19
  export declare const createProject: (options: CreateProjectOptions) => Promise<ApiCreateProjectResults | undefined>;
19
20
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"createProject.d.ts","sourceRoot":"","sources":["../../src/handlers/createProject.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,+BAA+B,EAI/B,WAAW,EAGX,KAAK,uBAAuB,EAI/B,MAAM,mBAAmB,CAAC;AA6D3B,eAAO,MAAM,kCAAkC,SACrC,MAAM,KACb,OAAO,CAAC,MAAM,CAyChB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,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,EAAE,WAAW,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,+BAA+B,CAAC;IACrD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAYF,eAAO,MAAM,aAAa,YACb,oBAAoB,KAC9B,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAqL7C,CAAC"}
1
+ {"version":3,"file":"createProject.d.ts","sourceRoot":"","sources":["../../src/handlers/createProject.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,+BAA+B,EAI/B,WAAW,EAGX,KAAK,uBAAuB,EAI/B,MAAM,mBAAmB,CAAC;AAgE3B,eAAO,MAAM,kCAAkC,SACrC,MAAM,KACb,OAAO,CAAC,MAAM,CAyChB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,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,EAAE,WAAW,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,+BAA+B,CAAC;IACrD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAYF,eAAO,MAAM,aAAa,YACb,oBAAoB,KAC9B,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAmM7C,CAAC"}
@@ -28,8 +28,9 @@ const askToRememberAnswer = async () => {
28
28
  });
29
29
  }
30
30
  };
31
- const askPermissionToStoreWarehouseCredentials = async () => {
32
- if (process.env.CI === 'true') {
31
+ const askPermissionToStoreWarehouseCredentials = async (assumeYes = false) => {
32
+ if (globalState_1.default.isNonInteractive() || assumeYes) {
33
+ globalState_1.default.debug('> Auto-accepting warehouse credentials storage');
33
34
  return true;
34
35
  }
35
36
  const config = await (0, config_1.getConfig)();
@@ -151,7 +152,7 @@ const createProject = async (options) => {
151
152
  target: options.target,
152
153
  });
153
154
  globalState_1.default.debug(`> Using target name ${targetName}`);
154
- const canStoreWarehouseCredentials = await askPermissionToStoreWarehouseCredentials();
155
+ const canStoreWarehouseCredentials = await askPermissionToStoreWarehouseCredentials(options.assumeYes);
155
156
  if (!canStoreWarehouseCredentials) {
156
157
  globalState_1.default.debug('> User declined to store warehouse credentials use --no-warehouse-credentials to create a project without warehouse credentials');
157
158
  return undefined;
@@ -168,19 +169,29 @@ const createProject = async (options) => {
168
169
  if (credentials?.type === common_1.WarehouseTypes.BIGQUERY &&
169
170
  credentials.keyfileContents.project_id &&
170
171
  credentials.keyfileContents.project_id !== credentials.project) {
171
- const spinner = globalState_1.default.getActiveSpinner();
172
- spinner?.stop();
173
- const answers = await inquirer_1.default.prompt([
174
- {
175
- type: 'confirm',
176
- name: 'isConfirm',
177
- message: `${styles.title('Warning')}: Your project on your credentials file ${styles.title(credentials.keyfileContents.project_id)} does not match your project on your profiles.yml ${styles.title(credentials.project)}, this might cause permission issues when accessing data on the warehouse. Are you sure you want to continue?`,
178
- },
179
- ]);
180
- if (!answers.isConfirm) {
181
- process.exit(1);
172
+ if (globalState_1.default.isNonInteractive() && !options.assumeYes) {
173
+ throw new Error(`BigQuery project mismatch: credentials file uses "${credentials.keyfileContents.project_id}" ` +
174
+ `but profiles.yml specifies "${credentials.project}". ` +
175
+ 'This may cause permission issues. Use --assume-yes to bypass this warning.');
176
+ }
177
+ if (options.assumeYes) {
178
+ globalState_1.default.debug(`> Auto-accepting BigQuery project mismatch (credentials: ${credentials.keyfileContents.project_id}, profiles: ${credentials.project})`);
179
+ }
180
+ else {
181
+ const spinner = globalState_1.default.getActiveSpinner();
182
+ spinner?.stop();
183
+ const answers = await inquirer_1.default.prompt([
184
+ {
185
+ type: 'confirm',
186
+ name: 'isConfirm',
187
+ message: `${styles.title('Warning')}: Your project on your credentials file ${styles.title(credentials.keyfileContents.project_id)} does not match your project on your profiles.yml ${styles.title(credentials.project)}, this might cause permission issues when accessing data on the warehouse. Are you sure you want to continue?`,
188
+ },
189
+ ]);
190
+ if (!answers.isConfirm) {
191
+ process.exit(1);
192
+ }
193
+ spinner?.start();
182
194
  }
183
- spinner?.start();
184
195
  }
185
196
  if (credentials?.type === common_1.WarehouseTypes.SNOWFLAKE &&
186
197
  credentials?.authenticationType ===
@@ -77,7 +77,7 @@ const getDbtVersion = async () => {
77
77
  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}).`;
78
78
  const spinner = globalState_1.default.getActiveSpinner();
79
79
  spinner?.stop();
80
- if (process.env.CI === 'true') {
80
+ if (globalState_1.default.isNonInteractive()) {
81
81
  console.error(styles.warning(message));
82
82
  }
83
83
  else {
@@ -101,7 +101,7 @@ const getDbtVersion = async () => {
101
101
  const message = `Support for dbt Cloud CLI is still experimental and might not work as expected.`;
102
102
  const spinner = globalState_1.default.getActiveSpinner();
103
103
  spinner?.stop();
104
- if (process.env.CI === 'true') {
104
+ if (globalState_1.default.isNonInteractive()) {
105
105
  console.error(styles.warning(message));
106
106
  }
107
107
  else {
@@ -11,6 +11,7 @@ type DeployHandlerOptions = DbtCompileOptions & {
11
11
  startOfWeek?: number;
12
12
  warehouseCredentials?: boolean;
13
13
  organizationCredentials?: string;
14
+ assumeYes?: boolean;
14
15
  };
15
16
  type DeployArgs = DeployHandlerOptions & {
16
17
  projectUuid: string;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/handlers/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,OAAO,EACP,YAAY,EASf,MAAM,mBAAmB,CAAC;AAiB3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,KAAK,oBAAoB,GAAG,iBAAiB,GAAG;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,KAAK,UAAU,GAAG,oBAAoB,GAAG;IACrC,WAAW,EAAE,MAAM,CAAC;CACvB,CAAC;AAkCF,eAAO,MAAM,MAAM,aACL,CAAC,OAAO,GAAG,YAAY,CAAC,EAAE,WAC3B,UAAU,KACpB,OAAO,CAAC,IAAI,CAmEd,CAAC;AA8FF,eAAO,MAAM,aAAa,oBAA2B,oBAAoB,kBAwFxE,CAAC"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/handlers/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,OAAO,EACP,YAAY,EASf,MAAM,mBAAmB,CAAC;AAiB3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,KAAK,oBAAoB,GAAG,iBAAiB,GAAG;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,KAAK,UAAU,GAAG,oBAAoB,GAAG;IACrC,WAAW,EAAE,MAAM,CAAC;CACvB,CAAC;AAkCF,eAAO,MAAM,MAAM,aACL,CAAC,OAAO,GAAG,YAAY,CAAC,EAAE,WAC3B,UAAU,KACpB,OAAO,CAAC,IAAI,CAmEd,CAAC;AA+FF,eAAO,MAAM,aAAa,oBAA2B,oBAAoB,kBAwFxE,CAAC"}
@@ -96,7 +96,7 @@ const createNewProject = async (executionId, options) => {
96
96
  }
97
97
  // If interactive and no name provided, prompt for project name
98
98
  let projectName = defaultProjectName;
99
- if (options.create === true && process.env.CI !== 'true') {
99
+ if (options.create === true && !globalState_1.default.isNonInteractive()) {
100
100
  const answers = await inquirer_1.default.prompt([
101
101
  {
102
102
  type: 'input',
@@ -128,6 +128,7 @@ const createNewProject = async (executionId, options) => {
128
128
  name: projectName,
129
129
  type: common_1.ProjectType.DEFAULT,
130
130
  warehouseCredentials: options.warehouseCredentials,
131
+ assumeYes: options.assumeYes,
131
132
  });
132
133
  const project = results?.project;
133
134
  if (!project) {
@@ -1,6 +1,8 @@
1
1
  type LoginOptions = {
2
2
  /** Associated with a Personal Access Token or Service Account Token */
3
3
  token?: string;
4
+ /** Email for password-based login */
5
+ email?: string;
4
6
  /** Project UUID to select after login */
5
7
  project?: string;
6
8
  interactive?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/handlers/login.ts"],"names":[],"mappings":"AAiBA,KAAK,YAAY,GAAG;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AAqEF,eAAO,MAAM,KAAK,aACJ,MAAM,GAAG,SAAS,WACnB,YAAY,kBAgHxB,CAAC"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/handlers/login.ts"],"names":[],"mappings":"AAkBA,KAAK,YAAY,GAAG;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AA4JF,eAAO,MAAM,KAAK,aACJ,MAAM,GAAG,SAAS,WACnB,YAAY,kBAkJxB,CAAC"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.login = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@lightdash/common");
6
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
6
7
  const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
7
8
  const url_1 = require("url");
8
9
  const analytics_1 = require("../analytics/analytics");
@@ -49,6 +50,8 @@ const normalizeUrl = (input) => {
49
50
  const getLoginMethod = (options) => {
50
51
  if (options.token)
51
52
  return 'token';
53
+ if (options.email)
54
+ return 'password';
52
55
  return 'oauth';
53
56
  };
54
57
  const loginWithToken = async (url, token) => {
@@ -68,6 +71,63 @@ const loginWithToken = async (url, token) => {
68
71
  token,
69
72
  };
70
73
  };
74
+ const loginWithEmailPassword = async (url, email, password) => {
75
+ const loginUrl = new url_1.URL('/api/v1/login', url).href;
76
+ const loginResponse = await (0, node_fetch_1.default)(loginUrl, {
77
+ method: 'POST',
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ body: JSON.stringify({ email, password }),
82
+ });
83
+ if (loginResponse.status !== 200) {
84
+ const errorBody = await loginResponse.json().catch(() => ({}));
85
+ const errorMessage = errorBody.error?.message ||
86
+ 'Invalid credentials';
87
+ throw new common_1.AuthorizationError(`Cannot sign in with email/password: ${errorMessage}\n\n` +
88
+ `🤖 Coding agents: If your password has special characters (like !), read:\n` +
89
+ ` packages/cli/README.md#claude-code--agentic-tools-passwords-with-special-characters`);
90
+ }
91
+ const cookies = loginResponse.headers.get('set-cookie');
92
+ if (!cookies) {
93
+ throw new common_1.AuthorizationError('Login succeeded but no session cookie received');
94
+ }
95
+ const patUrl = new url_1.URL('/api/v1/user/me/personal-access-tokens', url).href;
96
+ const patResponse = await (0, node_fetch_1.default)(patUrl, {
97
+ method: 'POST',
98
+ headers: {
99
+ 'Content-Type': 'application/json',
100
+ Cookie: cookies,
101
+ },
102
+ body: JSON.stringify({
103
+ description: 'Lightdash CLI',
104
+ expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
105
+ autoGenerated: false,
106
+ }),
107
+ });
108
+ if (patResponse.status !== 200) {
109
+ const errorBody = await patResponse.json().catch(() => ({}));
110
+ throw new common_1.AuthorizationError(`Failed to create personal access token: ${errorBody.error
111
+ ?.message || 'Unknown error'}`);
112
+ }
113
+ const patBody = (await patResponse.json());
114
+ const { token } = patBody.results;
115
+ const userInfoUrl = new url_1.URL('/api/v1/user', url).href;
116
+ const userResponse = await (0, node_fetch_1.default)(userInfoUrl, {
117
+ method: 'GET',
118
+ headers: (0, utils_1.buildRequestHeaders)(token),
119
+ });
120
+ if (userResponse.status !== 200) {
121
+ throw new common_1.AuthorizationError('Failed to get user info after login');
122
+ }
123
+ const userBody = (await userResponse.json());
124
+ const { userUuid, organizationUuid } = userBody.results;
125
+ return {
126
+ userUuid,
127
+ organizationUuid,
128
+ token,
129
+ };
130
+ };
71
131
  const login = async (urlInput, options) => {
72
132
  globalState_1.default.setVerbose(options.verbose);
73
133
  await (0, apiClient_1.checkLightdashVersion)();
@@ -101,10 +161,36 @@ const login = async (urlInput, options) => {
101
161
  const cloudServer = url.replace('lightdash.com', 'lightdash.cloud');
102
162
  console.error(`\n${styles.title('Warning')}: Login URL ${styles.secondary(url)} does not match a valid cloud server, perhaps you meant ${styles.secondary(cloudServer)} ?\n`);
103
163
  }
164
+ const email = options.email || process.env.LIGHTDASH_CLI_EMAIL;
165
+ const password = process.env.LIGHTDASH_CLI_PASSWORD;
104
166
  let loginResult;
105
167
  if (options.token) {
106
168
  loginResult = await loginWithToken(url, options.token);
107
169
  }
170
+ else if (email) {
171
+ let finalPassword = password;
172
+ if (!finalPassword) {
173
+ if (globalState_1.default.isNonInteractive()) {
174
+ throw new common_1.AuthorizationError('Password is required when using --email in non-interactive mode.\n' +
175
+ 'Set the LIGHTDASH_CLI_PASSWORD environment variable:\n\n' +
176
+ ` export LIGHTDASH_CLI_PASSWORD='your_password'\n` +
177
+ ` lightdash login ${url} --email ${email} --non-interactive\n`);
178
+ }
179
+ console.error(`\n${styles.secondary('💡 Tip for coding agents:')} To avoid interactive prompts, exit and run:\n` +
180
+ ` export LIGHTDASH_CLI_PASSWORD='your_password'\n` +
181
+ ` lightdash login ${url} --email ${email}\n`);
182
+ const answers = await inquirer_1.default.prompt([
183
+ {
184
+ type: 'password',
185
+ name: 'password',
186
+ message: 'Enter your password:',
187
+ mask: '*',
188
+ },
189
+ ]);
190
+ finalPassword = answers.password;
191
+ }
192
+ loginResult = await loginWithEmailPassword(url, email, finalPassword);
193
+ }
108
194
  else {
109
195
  loginResult = await (0, oauthLogin_1.loginWithOauth)(url);
110
196
  }
@@ -130,7 +216,7 @@ const login = async (urlInput, options) => {
130
216
  if (options.project) {
131
217
  await (0, setProject_1.setProjectCommand)(undefined, options.project);
132
218
  }
133
- else if (process.env.CI === 'true') {
219
+ else if (globalState_1.default.isNonInteractive()) {
134
220
  await (0, setProject_1.setFirstProject)();
135
221
  }
136
222
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"setProject.d.ts","sourceRoot":"","sources":["../../src/handlers/setProject.ts"],"names":[],"mappings":"AAOA,KAAK,iBAAiB,GAAG;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAiB,MAAM,SAAS,MAAM,kBAsDnE,CAAC;AAEF,eAAO,MAAM,eAAe,qBAmB3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,YAAmB,iBAAiB,kBAGjE,CAAC"}
1
+ {"version":3,"file":"setProject.d.ts","sourceRoot":"","sources":["../../src/handlers/setProject.ts"],"names":[],"mappings":"AAOA,KAAK,iBAAiB,GAAG;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAiB,MAAM,SAAS,MAAM,kBAuDnE,CAAC;AAEF,eAAO,MAAM,eAAe,qBAmB3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,YAAmB,iBAAiB,kBAGjE,CAAC"}
@@ -20,7 +20,10 @@ const setProjectCommand = async (name, uuid) => {
20
20
  // --uuid or --name options
21
21
  if (uuid !== undefined || name !== undefined) {
22
22
  selectedProject = projects.find((project) => project.name === name || project.projectUuid === uuid);
23
- // Select project interactively
23
+ }
24
+ else if (globalState_1.default.isNonInteractive()) {
25
+ globalState_1.default.debug('> Non-interactive mode: selecting first project');
26
+ [selectedProject] = projects;
24
27
  }
25
28
  else {
26
29
  const answers = await inquirer_1.default.prompt([
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ const common_1 = require("@lightdash/common");
6
6
  const commander_1 = require("commander");
7
7
  const uuid_1 = require("uuid");
8
8
  const env_1 = require("./env");
9
+ const globalState_1 = tslib_1.__importDefault(require("./globalState"));
9
10
  const compile_1 = require("./handlers/compile");
10
11
  const refresh_1 = require("./handlers/dbt/refresh");
11
12
  const run_1 = require("./handlers/dbt/run");
@@ -68,6 +69,13 @@ commander_1.program
68
69
  .version(env_1.CLI_VERSION)
69
70
  .name(styles.title('⚡️lightdash'))
70
71
  .description('Developer tools for dbt and Lightdash.\nSee https://docs.lightdash.com for more help and examples')
72
+ .option('--non-interactive', 'Disable all interactive prompts. Commands fail with helpful error if required input is missing.')
73
+ .hook('preAction', (thisCommand) => {
74
+ const opts = thisCommand.opts();
75
+ if (opts.nonInteractive) {
76
+ globalState_1.default.setNonInteractive(true);
77
+ }
78
+ })
71
79
  .showHelpAfterError(styles.bold('Run ⚡️lightdash help [command] for more information'))
72
80
  .addHelpText('after', `
73
81
  ${styles.bold('Examples:')}
@@ -98,9 +106,11 @@ ${styles.bold('Examples:')}
98
106
  ${styles.title('⚡')}️lightdash ${styles.bold('login')} https://custom.lightdash.domain/projects/123 ${styles.secondary('-- Strips path, uses https://custom.lightdash.domain (opens browser for OAuth)')}
99
107
  ${styles.title('⚡')}️lightdash ${styles.bold('login')} http://localhost:3000 ${styles.secondary('-- Preserves http protocol for local development')}
100
108
  ${styles.title('⚡')}️lightdash ${styles.bold('login')} --token 12345 ${styles.secondary('-- Logs in with API token using saved URL (bypasses OAuth)')}
109
+ ${styles.title('⚡')}️lightdash ${styles.bold('login')} http://localhost:3000 --email demo@lightdash.com ${styles.secondary('-- Local dev only: prompts for password securely')}
101
110
  `)
102
111
  .option('--token <token>', 'Login with an API access token', undefined)
103
112
  .option('--project <project uuid>', 'Select a project by UUID after login', parseProjectArgument, undefined)
113
+ .option('--email <email>', 'Login with email and password', undefined)
104
114
  .option('--verbose', undefined, false)
105
115
  .action(login_1.login);
106
116
  // CONFIG
@@ -306,6 +316,7 @@ commander_1.program
306
316
  .option('--no-warehouse-credentials', 'Create project without warehouse credentials. Skips dbt compile + warehouse catalog')
307
317
  .option('--organization-credentials <name>', 'Use organization warehouse credentials with the specified name (Enterprise Edition feature)')
308
318
  .option('--disable-timestamp-conversion [true|false]', 'Disable timestamp conversion to UTC for Snowflake warehouses. Only use this if your timestamp values are already in UTC.', parseDisableTimestampConversionOption, false)
319
+ .option('-y, --assume-yes', 'assume yes to prompts', false)
309
320
  .action(deploy_1.deployHandler);
310
321
  commander_1.program
311
322
  .command('refresh')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "0.2420.0",
3
+ "version": "0.2422.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,8 +38,8 @@
38
38
  "unique-names-generator": "^4.7.1",
39
39
  "uuid": "^11.0.3",
40
40
  "yaml": "^2.7.0",
41
- "@lightdash/common": "0.2420.0",
42
- "@lightdash/warehouses": "0.2420.0"
41
+ "@lightdash/common": "0.2422.0",
42
+ "@lightdash/warehouses": "0.2422.0"
43
43
  },
44
44
  "description": "Lightdash CLI tool",
45
45
  "devDependencies": {