@lightdash/cli 0.2181.0 → 0.2182.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/handlers/login.d.ts +1 -3
- package/dist/handlers/login.d.ts.map +1 -1
- package/dist/handlers/login.js +54 -58
- package/dist/index.js +9 -8
- package/package.json +4 -4
package/dist/handlers/login.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
type LoginOptions = {
|
|
2
2
|
/** Associated with a Personal Access Token or Service Account Token */
|
|
3
3
|
token?: string;
|
|
4
|
-
/** Use OAuth2 flow instead of password/token */
|
|
5
|
-
oauth?: boolean;
|
|
6
4
|
/** Project UUID to select after login */
|
|
7
5
|
project?: string;
|
|
8
6
|
interactive?: boolean;
|
|
9
7
|
verbose: boolean;
|
|
10
8
|
};
|
|
11
|
-
export declare const login: (
|
|
9
|
+
export declare const login: (urlInput: string | undefined, options: LoginOptions) => Promise<void>;
|
|
12
10
|
export {};
|
|
13
11
|
//# sourceMappingURL=login.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/handlers/login.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/handlers/login.js
CHANGED
|
@@ -3,7 +3,6 @@ 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"));
|
|
7
6
|
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
8
7
|
const url_1 = require("url");
|
|
9
8
|
const analytics_1 = require("../analytics/analytics");
|
|
@@ -11,17 +10,46 @@ const config_1 = require("../config");
|
|
|
11
10
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
12
11
|
const styles = tslib_1.__importStar(require("../styles"));
|
|
13
12
|
const apiClient_1 = require("./dbt/apiClient");
|
|
14
|
-
const pat_1 = require("./login/pat");
|
|
15
13
|
const oauthLogin_1 = require("./oauthLogin");
|
|
16
14
|
const setProject_1 = require("./setProject");
|
|
17
15
|
const utils_1 = require("./utils");
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes a URL input to make it more user-friendly:
|
|
18
|
+
* - Single words (e.g., "app") become subdomains of lightdash.cloud (e.g., "https://app.lightdash.cloud")
|
|
19
|
+
* - Missing protocol defaults to https://
|
|
20
|
+
* - Any path is stripped (e.g., "https://app.lightdash.cloud/projects/123" -> "https://app.lightdash.cloud")
|
|
21
|
+
* - Preserves explicitly provided protocols (http:// or https://)
|
|
22
|
+
*
|
|
23
|
+
* @param input - The URL input from the user
|
|
24
|
+
* @returns Normalized URL with protocol and host only
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* normalizeUrl("app") // "https://app.lightdash.cloud"
|
|
28
|
+
* normalizeUrl("app.lightdash.cloud") // "https://app.lightdash.cloud"
|
|
29
|
+
* normalizeUrl("https://app.lightdash.cloud/projects/123") // "https://app.lightdash.cloud"
|
|
30
|
+
* normalizeUrl("http://localhost:3000") // "http://localhost:3000"
|
|
31
|
+
* normalizeUrl("custom.domain.com") // "https://custom.domain.com"
|
|
32
|
+
*/
|
|
33
|
+
const normalizeUrl = (input) => {
|
|
34
|
+
let url = input.trim();
|
|
35
|
+
// If it's a single word (no dots, slashes, or colons), assume it's a lightdash.cloud subdomain
|
|
36
|
+
if (!url.includes('/') && !url.includes('.') && !url.includes(':')) {
|
|
37
|
+
url = `${url}.lightdash.cloud`;
|
|
38
|
+
}
|
|
39
|
+
// If no protocol is specified, add https://
|
|
40
|
+
if (!url.match(/^https?:\/\//)) {
|
|
41
|
+
url = `https://${url}`;
|
|
42
|
+
}
|
|
43
|
+
// Parse the URL to extract protocol, hostname, and port (strips path)
|
|
44
|
+
const parsedUrl = new url_1.URL(url);
|
|
45
|
+
// Return only protocol + host (host includes hostname and port)
|
|
46
|
+
return `${parsedUrl.protocol}//${parsedUrl.host}`;
|
|
47
|
+
};
|
|
18
48
|
// Helper function to determine login method
|
|
19
49
|
const getLoginMethod = (options) => {
|
|
20
|
-
if (options.oauth)
|
|
21
|
-
return 'oauth';
|
|
22
50
|
if (options.token)
|
|
23
51
|
return 'token';
|
|
24
|
-
return '
|
|
52
|
+
return 'oauth';
|
|
25
53
|
};
|
|
26
54
|
const loginWithToken = async (url, token) => {
|
|
27
55
|
const userInfoUrl = new url_1.URL(`/api/v1/user`, url).href;
|
|
@@ -40,56 +68,27 @@ const loginWithToken = async (url, token) => {
|
|
|
40
68
|
token,
|
|
41
69
|
};
|
|
42
70
|
};
|
|
43
|
-
const
|
|
44
|
-
const answers = await inquirer_1.default.prompt([
|
|
45
|
-
{
|
|
46
|
-
type: 'input',
|
|
47
|
-
name: 'email',
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
type: 'password',
|
|
51
|
-
name: 'password',
|
|
52
|
-
},
|
|
53
|
-
]);
|
|
54
|
-
const { email, password } = answers;
|
|
55
|
-
const loginUrl = new url_1.URL(`/api/v1/login`, url).href;
|
|
56
|
-
const response = await (0, node_fetch_1.default)(loginUrl, {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
body: JSON.stringify({ email, password }),
|
|
59
|
-
headers: {
|
|
60
|
-
'Content-Type': 'application/json',
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
globalState_1.default.debug(`> Login response status: ${response.status}`);
|
|
64
|
-
switch (response.status) {
|
|
65
|
-
case 200:
|
|
66
|
-
break;
|
|
67
|
-
case 401:
|
|
68
|
-
throw new common_1.AuthorizationError(`Unable to authenticate: invalid email or password`);
|
|
69
|
-
default:
|
|
70
|
-
// This error doesn't return a valid JSON, so we use .text instead
|
|
71
|
-
throw new common_1.AuthorizationError(`Unable to authenticate: (${response.status}) ${await response.text()}\nIf you use single sign-on (SSO) in the browser, login with a personal access token.`);
|
|
72
|
-
}
|
|
73
|
-
const loginBody = await response.json();
|
|
74
|
-
const header = response.headers.get('set-cookie');
|
|
75
|
-
if (header === null) {
|
|
76
|
-
throw new common_1.AuthorizationError(`Cannot sign in:\n${JSON.stringify(loginBody)}`);
|
|
77
|
-
}
|
|
78
|
-
const { userUuid, organizationUuid } = loginBody.results;
|
|
79
|
-
const cookie = header.split(';')[0].split('=')[1];
|
|
80
|
-
const patToken = await (0, pat_1.generatePersonalAccessToken)({
|
|
81
|
-
Cookie: `connect.sid=${cookie}`,
|
|
82
|
-
}, url);
|
|
83
|
-
return {
|
|
84
|
-
userUuid,
|
|
85
|
-
organizationUuid,
|
|
86
|
-
token: patToken,
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
const login = async (url, options) => {
|
|
71
|
+
const login = async (urlInput, options) => {
|
|
90
72
|
globalState_1.default.setVerbose(options.verbose);
|
|
91
73
|
await (0, apiClient_1.checkLightdashVersion)();
|
|
92
|
-
|
|
74
|
+
// If no URL provided, try to use the saved URL from config
|
|
75
|
+
let resolvedUrlInput = urlInput;
|
|
76
|
+
if (!resolvedUrlInput) {
|
|
77
|
+
const config = await (0, config_1.getConfig)();
|
|
78
|
+
if (config.context?.serverUrl) {
|
|
79
|
+
resolvedUrlInput = config.context.serverUrl;
|
|
80
|
+
console.error(`${styles.secondary(`Using saved URL: ${resolvedUrlInput}`)}`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new common_1.AuthorizationError(`No URL provided and no saved URL found. Please provide a URL:\n\n ${styles.bold('⚡️ lightdash login <url>')}\n\nExamples:\n ${styles.bold('⚡️ lightdash login app')} ${styles.secondary('(for https://app.lightdash.cloud)')}\n ${styles.bold('⚡️ lightdash login https://custom.domain.com')}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Normalize the URL input to handle various formats
|
|
87
|
+
const url = normalizeUrl(resolvedUrlInput);
|
|
88
|
+
if (urlInput) {
|
|
89
|
+
globalState_1.default.debug(`> Original URL input: ${urlInput}`);
|
|
90
|
+
}
|
|
91
|
+
globalState_1.default.debug(`> Normalized URL: ${url}`);
|
|
93
92
|
const loginMethod = getLoginMethod(options);
|
|
94
93
|
await analytics_1.LightdashAnalytics.track({
|
|
95
94
|
event: 'login.started',
|
|
@@ -103,14 +102,11 @@ const login = async (url, options) => {
|
|
|
103
102
|
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`);
|
|
104
103
|
}
|
|
105
104
|
let loginResult;
|
|
106
|
-
if (options.
|
|
107
|
-
loginResult = await (0, oauthLogin_1.loginWithOauth)(url);
|
|
108
|
-
}
|
|
109
|
-
else if (options.token) {
|
|
105
|
+
if (options.token) {
|
|
110
106
|
loginResult = await loginWithToken(url, options.token);
|
|
111
107
|
}
|
|
112
108
|
else {
|
|
113
|
-
loginResult = await
|
|
109
|
+
loginResult = await (0, oauthLogin_1.loginWithOauth)(url);
|
|
114
110
|
}
|
|
115
111
|
const { userUuid, token, organizationUuid } = loginResult;
|
|
116
112
|
globalState_1.default.debug(`> Logged in with userUuid: ${userUuid}`);
|
package/dist/index.js
CHANGED
|
@@ -75,19 +75,20 @@ ${styles.bold('Examples:')}
|
|
|
75
75
|
`);
|
|
76
76
|
// LOGIN
|
|
77
77
|
commander_1.program
|
|
78
|
-
.command('login
|
|
78
|
+
.command('login [url]')
|
|
79
79
|
.description('Logs in to a Lightdash instance')
|
|
80
|
-
.description('Logs in to a Lightdash instance.\n\n👀 See https://docs.lightdash.com/guides/cli/cli-authentication for more help and examples')
|
|
80
|
+
.description('Logs in to a Lightdash instance using OAuth2 (opens browser). Use --token to bypass OAuth.\n\nURL is optional - if not provided, uses the last URL you logged into. URL can be flexible: single words like "app" become "https://app.lightdash.cloud", protocol defaults to https, and paths are ignored.\n\n👀 See https://docs.lightdash.com/guides/cli/cli-authentication for more help and examples')
|
|
81
81
|
.addHelpText('after', `
|
|
82
82
|
${styles.bold('Examples:')}
|
|
83
|
-
${styles.title('⚡')}️lightdash ${styles.bold('login')}
|
|
84
|
-
${styles.title('⚡')}️lightdash ${styles.bold('login')}
|
|
85
|
-
${styles.title('⚡')}️lightdash ${styles.bold('login')}
|
|
86
|
-
${styles.title('⚡')}️lightdash ${styles.bold('login')}
|
|
87
|
-
${styles.title('⚡')}️lightdash ${styles.bold('login')} https://custom.lightdash.domain
|
|
83
|
+
${styles.title('⚡')}️lightdash ${styles.bold('login')} ${styles.secondary('-- Uses previously saved URL (opens browser for OAuth)')}
|
|
84
|
+
${styles.title('⚡')}️lightdash ${styles.bold('login')} app ${styles.secondary('-- Short form for https://app.lightdash.cloud (opens browser for OAuth)')}
|
|
85
|
+
${styles.title('⚡')}️lightdash ${styles.bold('login')} eu1 ${styles.secondary('-- Short form for https://eu1.lightdash.cloud (opens browser for OAuth)')}
|
|
86
|
+
${styles.title('⚡')}️lightdash ${styles.bold('login')} app.lightdash.cloud ${styles.secondary('-- Adds https:// automatically (opens browser for OAuth)')}
|
|
87
|
+
${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)')}
|
|
88
|
+
${styles.title('⚡')}️lightdash ${styles.bold('login')} http://localhost:3000 ${styles.secondary('-- Preserves http protocol for local development')}
|
|
89
|
+
${styles.title('⚡')}️lightdash ${styles.bold('login')} --token 12345 ${styles.secondary('-- Logs in with API token using saved URL (bypasses OAuth)')}
|
|
88
90
|
`)
|
|
89
91
|
.option('--token <token>', 'Login with an API access token', undefined)
|
|
90
|
-
.option('--oauth', 'Login using OAuth2 flow (opens browser for authentication)', false)
|
|
91
92
|
.option('--project <project uuid>', 'Select a project by UUID after login', parseProjectArgument, undefined)
|
|
92
93
|
.option('--verbose', undefined, false)
|
|
93
94
|
.action(login_1.login);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2182.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lightdash": "dist/index.js"
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"execa": "^5",
|
|
25
25
|
"google-auth-library": "^8.5.1",
|
|
26
26
|
"inquirer": "^8.2.4",
|
|
27
|
-
"js-yaml": "^4.1.
|
|
27
|
+
"js-yaml": "^4.1.1",
|
|
28
28
|
"lodash": "^4.17.21",
|
|
29
29
|
"node-fetch": "^2.7.0",
|
|
30
30
|
"nunjucks": "^3.2.3",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"parse-node-version": "^2.0.0",
|
|
34
34
|
"unique-names-generator": "^4.7.1",
|
|
35
35
|
"uuid": "^11.0.3",
|
|
36
|
-
"@lightdash/common": "0.
|
|
37
|
-
"@lightdash/warehouses": "0.
|
|
36
|
+
"@lightdash/common": "0.2182.0",
|
|
37
|
+
"@lightdash/warehouses": "0.2182.0"
|
|
38
38
|
},
|
|
39
39
|
"description": "Lightdash CLI tool",
|
|
40
40
|
"devDependencies": {
|