@claryai/cli 0.1.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/LICENSE +25 -0
- package/README.md +197 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/ajv.d.ts +3 -0
- package/dist/ajv.d.ts.map +1 -0
- package/dist/ajv.js +13 -0
- package/dist/analytics/analytics.d.ts +370 -0
- package/dist/analytics/analytics.d.ts.map +1 -0
- package/dist/analytics/analytics.js +143 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +134 -0
- package/dist/dbt/context.d.ts +14 -0
- package/dist/dbt/context.d.ts.map +1 -0
- package/dist/dbt/context.js +76 -0
- package/dist/dbt/context.test.d.ts +2 -0
- package/dist/dbt/context.test.d.ts.map +1 -0
- package/dist/dbt/context.test.js +152 -0
- package/dist/dbt/manifest.d.ts +7 -0
- package/dist/dbt/manifest.d.ts.map +1 -0
- package/dist/dbt/manifest.js +23 -0
- package/dist/dbt/models.d.ts +43 -0
- package/dist/dbt/models.d.ts.map +1 -0
- package/dist/dbt/models.js +256 -0
- package/dist/dbt/models.test.d.ts +2 -0
- package/dist/dbt/models.test.d.ts.map +1 -0
- package/dist/dbt/models.test.js +19 -0
- package/dist/dbt/profile.d.ts +9 -0
- package/dist/dbt/profile.d.ts.map +1 -0
- package/dist/dbt/profile.js +86 -0
- package/dist/dbt/profiles.test.d.ts +2 -0
- package/dist/dbt/profiles.test.d.ts.map +1 -0
- package/dist/dbt/profiles.test.js +50 -0
- package/dist/dbt/schema.d.ts +31 -0
- package/dist/dbt/schema.d.ts.map +1 -0
- package/dist/dbt/schema.js +49 -0
- package/dist/dbt/targets/Bigquery/index.d.ts +18 -0
- package/dist/dbt/targets/Bigquery/index.d.ts.map +1 -0
- package/dist/dbt/targets/Bigquery/index.js +105 -0
- package/dist/dbt/targets/Bigquery/oauth.d.ts +2 -0
- package/dist/dbt/targets/Bigquery/oauth.d.ts.map +1 -0
- package/dist/dbt/targets/Bigquery/oauth.js +43 -0
- package/dist/dbt/targets/Bigquery/serviceAccount.d.ts +35 -0
- package/dist/dbt/targets/Bigquery/serviceAccount.d.ts.map +1 -0
- package/dist/dbt/targets/Bigquery/serviceAccount.js +149 -0
- package/dist/dbt/targets/Databricks/oauth.d.ts +21 -0
- package/dist/dbt/targets/Databricks/oauth.d.ts.map +1 -0
- package/dist/dbt/targets/Databricks/oauth.js +184 -0
- package/dist/dbt/targets/athena.d.ts +21 -0
- package/dist/dbt/targets/athena.d.ts.map +1 -0
- package/dist/dbt/targets/athena.js +91 -0
- package/dist/dbt/targets/athena.test.d.ts +2 -0
- package/dist/dbt/targets/athena.test.d.ts.map +1 -0
- package/dist/dbt/targets/athena.test.js +60 -0
- package/dist/dbt/targets/clickhouse.d.ts +24 -0
- package/dist/dbt/targets/clickhouse.d.ts.map +1 -0
- package/dist/dbt/targets/clickhouse.js +90 -0
- package/dist/dbt/targets/databricks.d.ts +27 -0
- package/dist/dbt/targets/databricks.d.ts.map +1 -0
- package/dist/dbt/targets/databricks.js +138 -0
- package/dist/dbt/targets/duckdb.d.ts +16 -0
- package/dist/dbt/targets/duckdb.d.ts.map +1 -0
- package/dist/dbt/targets/duckdb.js +63 -0
- package/dist/dbt/targets/duckdb.test.d.ts +2 -0
- package/dist/dbt/targets/duckdb.test.d.ts.map +1 -0
- package/dist/dbt/targets/duckdb.test.js +37 -0
- package/dist/dbt/targets/postgres.d.ts +26 -0
- package/dist/dbt/targets/postgres.d.ts.map +1 -0
- package/dist/dbt/targets/postgres.js +142 -0
- package/dist/dbt/targets/redshift.d.ts +23 -0
- package/dist/dbt/targets/redshift.d.ts.map +1 -0
- package/dist/dbt/targets/redshift.js +96 -0
- package/dist/dbt/targets/snowflake.d.ts +4 -0
- package/dist/dbt/targets/snowflake.d.ts.map +1 -0
- package/dist/dbt/targets/snowflake.js +134 -0
- package/dist/dbt/targets/trino.d.ts +16 -0
- package/dist/dbt/targets/trino.d.ts.map +1 -0
- package/dist/dbt/targets/trino.js +65 -0
- package/dist/dbt/templating.d.ts +15 -0
- package/dist/dbt/templating.d.ts.map +1 -0
- package/dist/dbt/templating.js +50 -0
- package/dist/dbt/templating.test.d.ts +2 -0
- package/dist/dbt/templating.test.d.ts.map +1 -0
- package/dist/dbt/templating.test.js +51 -0
- package/dist/dbt/types.d.ts +17 -0
- package/dist/dbt/types.d.ts.map +1 -0
- package/dist/dbt/types.js +2 -0
- package/dist/dbt/validation.d.ts +9 -0
- package/dist/dbt/validation.d.ts.map +1 -0
- package/dist/dbt/validation.js +54 -0
- package/dist/env.d.ts +12 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +40 -0
- package/dist/error.d.ts +2 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +12 -0
- package/dist/globalState.d.ts +29 -0
- package/dist/globalState.d.ts.map +1 -0
- package/dist/globalState.js +67 -0
- package/dist/handlers/asyncQuery.d.ts +7 -0
- package/dist/handlers/asyncQuery.d.ts.map +1 -0
- package/dist/handlers/asyncQuery.js +50 -0
- package/dist/handlers/compile.d.ts +16 -0
- package/dist/handlers/compile.d.ts.map +1 -0
- package/dist/handlers/compile.js +277 -0
- package/dist/handlers/compile.test.d.ts +2 -0
- package/dist/handlers/compile.test.d.ts.map +1 -0
- package/dist/handlers/compile.test.js +201 -0
- package/dist/handlers/createProject.d.ts +37 -0
- package/dist/handlers/createProject.d.ts.map +1 -0
- package/dist/handlers/createProject.js +272 -0
- package/dist/handlers/dbt/apiClient.d.ts +14 -0
- package/dist/handlers/dbt/apiClient.d.ts.map +1 -0
- package/dist/handlers/dbt/apiClient.js +167 -0
- package/dist/handlers/dbt/compile.d.ts +35 -0
- package/dist/handlers/dbt/compile.d.ts.map +1 -0
- package/dist/handlers/dbt/compile.js +220 -0
- package/dist/handlers/dbt/getDbtProfileTargetName.d.ts +9 -0
- package/dist/handlers/dbt/getDbtProfileTargetName.d.ts.map +1 -0
- package/dist/handlers/dbt/getDbtProfileTargetName.js +44 -0
- package/dist/handlers/dbt/getDbtVersion.d.ts +16 -0
- package/dist/handlers/dbt/getDbtVersion.d.ts.map +1 -0
- package/dist/handlers/dbt/getDbtVersion.js +141 -0
- package/dist/handlers/dbt/getDbtVersion.mocks.d.ts +11 -0
- package/dist/handlers/dbt/getDbtVersion.mocks.d.ts.map +1 -0
- package/dist/handlers/dbt/getDbtVersion.mocks.js +70 -0
- package/dist/handlers/dbt/getDbtVersion.test.d.ts +2 -0
- package/dist/handlers/dbt/getDbtVersion.test.d.ts.map +1 -0
- package/dist/handlers/dbt/getDbtVersion.test.js +97 -0
- package/dist/handlers/dbt/getWarehouseClient.d.ts +24 -0
- package/dist/handlers/dbt/getWarehouseClient.d.ts.map +1 -0
- package/dist/handlers/dbt/getWarehouseClient.js +312 -0
- package/dist/handlers/dbt/refresh.d.ts +11 -0
- package/dist/handlers/dbt/refresh.d.ts.map +1 -0
- package/dist/handlers/dbt/refresh.js +114 -0
- package/dist/handlers/dbt/run.d.ts +14 -0
- package/dist/handlers/dbt/run.d.ts.map +1 -0
- package/dist/handlers/dbt/run.js +67 -0
- package/dist/handlers/deploy.d.ts +26 -0
- package/dist/handlers/deploy.d.ts.map +1 -0
- package/dist/handlers/deploy.js +377 -0
- package/dist/handlers/diagnostics.d.ts +11 -0
- package/dist/handlers/diagnostics.d.ts.map +1 -0
- package/dist/handlers/diagnostics.js +194 -0
- package/dist/handlers/download.d.ts +29 -0
- package/dist/handlers/download.d.ts.map +1 -0
- package/dist/handlers/download.js +955 -0
- package/dist/handlers/exportChartImage.d.ts +7 -0
- package/dist/handlers/exportChartImage.d.ts.map +1 -0
- package/dist/handlers/exportChartImage.js +33 -0
- package/dist/handlers/generate.d.ts +13 -0
- package/dist/handlers/generate.d.ts.map +1 -0
- package/dist/handlers/generate.js +159 -0
- package/dist/handlers/generateExposures.d.ts +8 -0
- package/dist/handlers/generateExposures.d.ts.map +1 -0
- package/dist/handlers/generateExposures.js +100 -0
- package/dist/handlers/getProject.d.ts +6 -0
- package/dist/handlers/getProject.d.ts.map +1 -0
- package/dist/handlers/getProject.js +43 -0
- package/dist/handlers/installSkills.d.ts +12 -0
- package/dist/handlers/installSkills.d.ts.map +1 -0
- package/dist/handlers/installSkills.js +321 -0
- package/dist/handlers/lint/ajvToSarif.d.ts +66 -0
- package/dist/handlers/lint/ajvToSarif.d.ts.map +1 -0
- package/dist/handlers/lint/ajvToSarif.js +222 -0
- package/dist/handlers/lint/sarifFormatter.d.ts +14 -0
- package/dist/handlers/lint/sarifFormatter.d.ts.map +1 -0
- package/dist/handlers/lint/sarifFormatter.js +111 -0
- package/dist/handlers/lint.d.ts +8 -0
- package/dist/handlers/lint.d.ts.map +1 -0
- package/dist/handlers/lint.js +308 -0
- package/dist/handlers/listProjects.d.ts +6 -0
- package/dist/handlers/listProjects.d.ts.map +1 -0
- package/dist/handlers/listProjects.js +53 -0
- package/dist/handlers/login/oauth.d.ts +2 -0
- package/dist/handlers/login/oauth.d.ts.map +1 -0
- package/dist/handlers/login/oauth.js +27 -0
- package/dist/handlers/login/pat.d.ts +2 -0
- package/dist/handlers/login/pat.d.ts.map +1 -0
- package/dist/handlers/login/pat.js +31 -0
- package/dist/handlers/login.d.ts +15 -0
- package/dist/handlers/login.d.ts.map +1 -0
- package/dist/handlers/login.js +239 -0
- package/dist/handlers/metadataFile.d.ts +9 -0
- package/dist/handlers/metadataFile.d.ts.map +1 -0
- package/dist/handlers/metadataFile.js +34 -0
- package/dist/handlers/oauthLogin.d.ts +6 -0
- package/dist/handlers/oauthLogin.d.ts.map +1 -0
- package/dist/handlers/oauthLogin.js +191 -0
- package/dist/handlers/preview.d.ts +29 -0
- package/dist/handlers/preview.d.ts.map +1 -0
- package/dist/handlers/preview.js +415 -0
- package/dist/handlers/renameHandler.d.ts +16 -0
- package/dist/handlers/renameHandler.d.ts.map +1 -0
- package/dist/handlers/renameHandler.js +160 -0
- package/dist/handlers/runChart.d.ts +10 -0
- package/dist/handlers/runChart.d.ts.map +1 -0
- package/dist/handlers/runChart.js +105 -0
- package/dist/handlers/selectProject.d.ts +20 -0
- package/dist/handlers/selectProject.d.ts.map +1 -0
- package/dist/handlers/selectProject.js +91 -0
- package/dist/handlers/setProject.d.ts +14 -0
- package/dist/handlers/setProject.d.ts.map +1 -0
- package/dist/handlers/setProject.js +131 -0
- package/dist/handlers/setWarehouse.d.ts +14 -0
- package/dist/handlers/setWarehouse.d.ts.map +1 -0
- package/dist/handlers/setWarehouse.js +94 -0
- package/dist/handlers/sql.d.ts +9 -0
- package/dist/handlers/sql.d.ts.map +1 -0
- package/dist/handlers/sql.js +89 -0
- package/dist/handlers/utils.d.ts +11 -0
- package/dist/handlers/utils.d.ts.map +1 -0
- package/dist/handlers/utils.js +36 -0
- package/dist/handlers/validate.d.ts +22 -0
- package/dist/handlers/validate.d.ts.map +1 -0
- package/dist/handlers/validate.js +201 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +581 -0
- package/dist/lightdash/loader.d.ts +21 -0
- package/dist/lightdash/loader.d.ts.map +1 -0
- package/dist/lightdash/loader.js +122 -0
- package/dist/lightdash/projectType.d.ts +84 -0
- package/dist/lightdash/projectType.d.ts.map +1 -0
- package/dist/lightdash/projectType.js +75 -0
- package/dist/lightdash-config/index.d.ts +2 -0
- package/dist/lightdash-config/index.d.ts.map +1 -0
- package/dist/lightdash-config/index.js +41 -0
- package/dist/lightdash-config/lightdash-config.test.d.ts +2 -0
- package/dist/lightdash-config/lightdash-config.test.d.ts.map +1 -0
- package/dist/lightdash-config/lightdash-config.test.js +70 -0
- package/dist/styles.d.ts +10 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +14 -0
- package/entitlements.plist +33 -0
- package/package.json +71 -0
- package/track.sh +116 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.login = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@lightdash/common");
|
|
6
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
7
|
+
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
8
|
+
const url_1 = require("url");
|
|
9
|
+
const analytics_1 = require("../analytics/analytics");
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
12
|
+
const styles = tslib_1.__importStar(require("../styles"));
|
|
13
|
+
const apiClient_1 = require("./dbt/apiClient");
|
|
14
|
+
const oauthLogin_1 = require("./oauthLogin");
|
|
15
|
+
const setProject_1 = require("./setProject");
|
|
16
|
+
const utils_1 = require("./utils");
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes a URL input to make it more user-friendly:
|
|
19
|
+
* - Single words (e.g., "app") become subdomains of getclary.com (e.g., "https://app.getclary.com")
|
|
20
|
+
* - Missing protocol defaults to https://
|
|
21
|
+
* - Any path is stripped (e.g., "https://app.getclary.com/projects/123" -> "https://app.getclary.com")
|
|
22
|
+
* - Preserves explicitly provided protocols (http:// or https://)
|
|
23
|
+
*
|
|
24
|
+
* @param input - The URL input from the user
|
|
25
|
+
* @returns Normalized URL with protocol and host only
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* normalizeUrl("app") // "https://app.getclary.com"
|
|
29
|
+
* normalizeUrl("app.getclary.com") // "https://app.getclary.com"
|
|
30
|
+
* normalizeUrl("https://app.getclary.com/projects/123") // "https://app.getclary.com"
|
|
31
|
+
* normalizeUrl("http://localhost:3000") // "http://localhost:3000"
|
|
32
|
+
* normalizeUrl("custom.domain.com") // "https://custom.domain.com"
|
|
33
|
+
*/
|
|
34
|
+
const normalizeUrl = (input) => {
|
|
35
|
+
let url = input.trim();
|
|
36
|
+
// If it's a single word (no dots, slashes, or colons), assume it's a getclary.com subdomain
|
|
37
|
+
if (!url.includes("/") && !url.includes(".") && !url.includes(":")) {
|
|
38
|
+
url = `${url}.getclary.com`;
|
|
39
|
+
}
|
|
40
|
+
// If no protocol is specified, add https://
|
|
41
|
+
if (!url.match(/^https?:\/\//)) {
|
|
42
|
+
url = `https://${url}`;
|
|
43
|
+
}
|
|
44
|
+
// Parse the URL to extract protocol, hostname, and port (strips path)
|
|
45
|
+
const parsedUrl = new url_1.URL(url);
|
|
46
|
+
// Return only protocol + host (host includes hostname and port)
|
|
47
|
+
return `${parsedUrl.protocol}//${parsedUrl.host}`;
|
|
48
|
+
};
|
|
49
|
+
// Helper function to determine login method
|
|
50
|
+
const getLoginMethod = (options) => {
|
|
51
|
+
if (options.token)
|
|
52
|
+
return "token";
|
|
53
|
+
if (options.email)
|
|
54
|
+
return "password";
|
|
55
|
+
return "oauth";
|
|
56
|
+
};
|
|
57
|
+
const loginWithToken = async (url, token) => {
|
|
58
|
+
const userInfoUrl = new url_1.URL(`/api/v1/user`, url).href;
|
|
59
|
+
const response = await (0, node_fetch_1.default)(userInfoUrl, {
|
|
60
|
+
method: "GET",
|
|
61
|
+
headers: (0, utils_1.buildRequestHeaders)(token),
|
|
62
|
+
});
|
|
63
|
+
if (response.status !== 200) {
|
|
64
|
+
throw new common_1.AuthorizationError(`Cannot sign in with token:\n${JSON.stringify(await response.json())}`);
|
|
65
|
+
}
|
|
66
|
+
const userBody = await response.json();
|
|
67
|
+
const { userUuid, organizationUuid } = userBody.results || userBody;
|
|
68
|
+
return {
|
|
69
|
+
userUuid,
|
|
70
|
+
organizationUuid,
|
|
71
|
+
token,
|
|
72
|
+
};
|
|
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: "Clary 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?.message ||
|
|
111
|
+
"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
|
+
};
|
|
131
|
+
const login = async (urlInput, options) => {
|
|
132
|
+
globalState_1.default.setVerbose(options.verbose);
|
|
133
|
+
await (0, apiClient_1.checkLightdashVersion)();
|
|
134
|
+
// If no URL provided, try to use the saved URL from config
|
|
135
|
+
let resolvedUrlInput = urlInput;
|
|
136
|
+
if (!resolvedUrlInput) {
|
|
137
|
+
const config = await (0, config_1.getConfig)();
|
|
138
|
+
if (config.context?.serverUrl) {
|
|
139
|
+
resolvedUrlInput = config.context.serverUrl;
|
|
140
|
+
console.error(`${styles.secondary(`Using saved URL: ${resolvedUrlInput}`)}`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
throw new common_1.AuthorizationError(`No URL provided and no saved URL found. Please provide a URL:\n\n ${styles.bold("⚡️ clary login <url>")}\n\nExamples:\n ${styles.bold("⚡️ clary login app")} ${styles.secondary("(for https://app.getclary.com)")}\n ${styles.bold("⚡️ clary login https://custom.domain.com")}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Normalize the URL input to handle various formats
|
|
147
|
+
const url = normalizeUrl(resolvedUrlInput);
|
|
148
|
+
if (urlInput) {
|
|
149
|
+
globalState_1.default.debug(`> Original URL input: ${urlInput}`);
|
|
150
|
+
}
|
|
151
|
+
globalState_1.default.debug(`> Normalized URL: ${url}`);
|
|
152
|
+
const loginMethod = getLoginMethod(options);
|
|
153
|
+
await analytics_1.LightdashAnalytics.track({
|
|
154
|
+
event: "login.started",
|
|
155
|
+
properties: {
|
|
156
|
+
url,
|
|
157
|
+
method: loginMethod,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
if (url.includes("getclary.com")) {
|
|
161
|
+
const cloudServer = url.replace("getclary.com", "getclary.com");
|
|
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`);
|
|
163
|
+
}
|
|
164
|
+
const email = options.email || process.env.CLARY_CLI_EMAIL;
|
|
165
|
+
const password = process.env.CLARY_CLI_PASSWORD;
|
|
166
|
+
let loginResult;
|
|
167
|
+
if (options.token) {
|
|
168
|
+
loginResult = await loginWithToken(url, options.token);
|
|
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 CLARY_CLI_PASSWORD environment variable:\n\n" +
|
|
176
|
+
` export CLARY_CLI_PASSWORD='your_password'\n` +
|
|
177
|
+
` clary 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 CLARY_CLI_PASSWORD='your_password'\n` +
|
|
181
|
+
` clary 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
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
loginResult = await (0, oauthLogin_1.loginWithOauth)(url, options.oauthPort);
|
|
196
|
+
}
|
|
197
|
+
const { userUuid, token, organizationUuid } = loginResult;
|
|
198
|
+
globalState_1.default.debug(`> Logged in with userUuid: ${userUuid}`);
|
|
199
|
+
await analytics_1.LightdashAnalytics.track({
|
|
200
|
+
event: "login.completed",
|
|
201
|
+
properties: {
|
|
202
|
+
userId: userUuid,
|
|
203
|
+
organizationId: organizationUuid,
|
|
204
|
+
url,
|
|
205
|
+
method: loginMethod,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
await (0, config_1.setContext)({
|
|
209
|
+
serverUrl: url,
|
|
210
|
+
apiKey: token,
|
|
211
|
+
});
|
|
212
|
+
await analytics_1.LightdashAnalytics.identify({ organizationId: organizationUuid });
|
|
213
|
+
globalState_1.default.debug(`> Saved config on: ${config_1.configFilePath}`);
|
|
214
|
+
await (0, config_1.setDefaultUser)(userUuid, organizationUuid);
|
|
215
|
+
console.error(`\n ✅️ Login successful\n`);
|
|
216
|
+
try {
|
|
217
|
+
if (options.project) {
|
|
218
|
+
await (0, setProject_1.setProjectCommand)(undefined, options.project);
|
|
219
|
+
}
|
|
220
|
+
else if (globalState_1.default.isNonInteractive()) {
|
|
221
|
+
await (0, setProject_1.setFirstProject)();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const result = await (0, setProject_1.setProjectCommand)();
|
|
225
|
+
if (result === "empty") {
|
|
226
|
+
console.error("Now you can add your first project to clary by doing: ");
|
|
227
|
+
console.error(`\n ${styles.bold(`⚡️ clary deploy --create`)}\n`);
|
|
228
|
+
}
|
|
229
|
+
else if (result === "skipped") {
|
|
230
|
+
console.error(`\n No project selected — use ${styles.bold("clary config set-project")} or ${styles.bold("--project <uuid>")} when running commands.\n`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
console.error("Unable to select projects, try with: ");
|
|
236
|
+
console.error(`\n ${styles.bold(`⚡️ clary config set-project`)}\n`);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
exports.login = login;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const METADATA_FILENAME = ".clary-metadata.json";
|
|
2
|
+
export type LightdashMetadata = {
|
|
3
|
+
version: 1;
|
|
4
|
+
charts: Record<string, string>;
|
|
5
|
+
dashboards: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
export declare const readMetadataFile: (baseDir: string) => Promise<LightdashMetadata>;
|
|
8
|
+
export declare const writeMetadataFile: (baseDir: string, metadata: LightdashMetadata) => Promise<void>;
|
|
9
|
+
//# sourceMappingURL=metadataFile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadataFile.d.ts","sourceRoot":"","sources":["../../src/handlers/metadataFile.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AAExD,MAAM,MAAM,iBAAiB,GAAG;IAC5B,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC;AAQF,eAAO,MAAM,gBAAgB,GACzB,SAAS,MAAM,KAChB,OAAO,CAAC,iBAAiB,CAQ3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC1B,SAAS,MAAM,EACf,UAAU,iBAAiB,KAC5B,OAAO,CAAC,IAAI,CASd,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeMetadataFile = exports.readMetadataFile = exports.METADATA_FILENAME = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
exports.METADATA_FILENAME = '.clary-metadata.json';
|
|
8
|
+
const emptyMetadata = () => ({
|
|
9
|
+
version: 1,
|
|
10
|
+
charts: {},
|
|
11
|
+
dashboards: {},
|
|
12
|
+
});
|
|
13
|
+
const readMetadataFile = async (baseDir) => {
|
|
14
|
+
const filePath = path.join(baseDir, exports.METADATA_FILENAME);
|
|
15
|
+
try {
|
|
16
|
+
const content = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
17
|
+
return JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return emptyMetadata();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
exports.readMetadataFile = readMetadataFile;
|
|
24
|
+
const writeMetadataFile = async (baseDir, metadata) => {
|
|
25
|
+
const existing = await (0, exports.readMetadataFile)(baseDir);
|
|
26
|
+
const merged = {
|
|
27
|
+
version: 1,
|
|
28
|
+
charts: { ...existing.charts, ...metadata.charts },
|
|
29
|
+
dashboards: { ...existing.dashboards, ...metadata.dashboards },
|
|
30
|
+
};
|
|
31
|
+
const filePath = path.join(baseDir, exports.METADATA_FILENAME);
|
|
32
|
+
await fs_1.promises.writeFile(filePath, JSON.stringify(merged, null, 2));
|
|
33
|
+
};
|
|
34
|
+
exports.writeMetadataFile = writeMetadataFile;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthLogin.d.ts","sourceRoot":"","sources":["../../src/handlers/oauthLogin.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,cAAc,GACvB,KAAK,MAAM,EACX,YAAY,MAAM,KACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAwPvE,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loginWithOauth = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@lightdash/common");
|
|
6
|
+
const http = tslib_1.__importStar(require("http"));
|
|
7
|
+
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
8
|
+
const openid_client_1 = require("openid-client");
|
|
9
|
+
const url_1 = require("url");
|
|
10
|
+
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
11
|
+
const styles = tslib_1.__importStar(require("../styles"));
|
|
12
|
+
const oauth_1 = require("./login/oauth");
|
|
13
|
+
const pat_1 = require("./login/pat");
|
|
14
|
+
const loginWithOauth = async (url, oauthPort) => {
|
|
15
|
+
// Create a promise that will be resolved when we get the authorization code
|
|
16
|
+
let resolveAuth;
|
|
17
|
+
let rejectAuth;
|
|
18
|
+
const authPromise = new Promise((resolve, reject) => {
|
|
19
|
+
resolveAuth = resolve;
|
|
20
|
+
rejectAuth = reject;
|
|
21
|
+
});
|
|
22
|
+
const envPortStr = process.env.CLARY_OAUTH_PORT;
|
|
23
|
+
const envPort = envPortStr ? parseInt(envPortStr, 10) : undefined;
|
|
24
|
+
if (envPort !== undefined &&
|
|
25
|
+
(Number.isNaN(envPort) || envPort < 1 || envPort > 65535)) {
|
|
26
|
+
throw new Error('CLARY_OAUTH_PORT must be a number between 1 and 65535');
|
|
27
|
+
}
|
|
28
|
+
let port = oauthPort ?? envPort ?? 0; // CLI flag > env var > random
|
|
29
|
+
// Generate PKCE values using openid-client generators
|
|
30
|
+
const codeVerifier = openid_client_1.generators.codeVerifier();
|
|
31
|
+
const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
|
|
32
|
+
const state = openid_client_1.generators.state();
|
|
33
|
+
// Create HTTP server to handle the callback
|
|
34
|
+
const server = http.createServer((req, res) => {
|
|
35
|
+
if (req.url?.startsWith('/callback')) {
|
|
36
|
+
const callbackUrl = new url_1.URL(req.url, `http://localhost:${port}`);
|
|
37
|
+
const code = callbackUrl.searchParams.get('code');
|
|
38
|
+
const returnedState = callbackUrl.searchParams.get('state');
|
|
39
|
+
const error = callbackUrl.searchParams.get('error');
|
|
40
|
+
res.setHeader('Content-Type', 'text/html');
|
|
41
|
+
if (error === 'access_denied') {
|
|
42
|
+
rejectAuth(new common_1.AuthorizationError(`OAuth error: ${error}`));
|
|
43
|
+
res.writeHead(400);
|
|
44
|
+
res.end((0, common_1.generateOAuthErrorResponse)('Authentication Failed', [
|
|
45
|
+
`Access denied from the Clary page.`,
|
|
46
|
+
]));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (error) {
|
|
50
|
+
rejectAuth(new common_1.AuthorizationError(`OAuth error: ${error}`));
|
|
51
|
+
res.writeHead(400);
|
|
52
|
+
res.end((0, common_1.generateOAuthErrorResponse)('Authentication Failed', [
|
|
53
|
+
`Error: ${error}`,
|
|
54
|
+
]));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!code || !returnedState) {
|
|
58
|
+
rejectAuth(new common_1.AuthorizationError('Missing authorization code or state'));
|
|
59
|
+
res.writeHead(400);
|
|
60
|
+
res.end((0, common_1.generateOAuthErrorResponse)('Authentication Failed', [
|
|
61
|
+
'Missing authorization code or state parameter.',
|
|
62
|
+
]));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (returnedState !== state) {
|
|
66
|
+
rejectAuth(new common_1.AuthorizationError('Authentication session expired or invalid'));
|
|
67
|
+
res.writeHead(400);
|
|
68
|
+
res.end((0, common_1.generateOAuthErrorResponse)('Authentication Failed', [
|
|
69
|
+
'Your authentication session has expired or is invalid.',
|
|
70
|
+
'This can happen if you used an old link.',
|
|
71
|
+
'Please close this window and try logging in again.',
|
|
72
|
+
], 'sessionExpired'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Success - resolve the promise
|
|
76
|
+
resolveAuth({ code, state: returnedState });
|
|
77
|
+
res.writeHead(200);
|
|
78
|
+
res.end((0, common_1.generateOAuthSuccessResponse)());
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
82
|
+
res.end('Not Found');
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// Start the server
|
|
86
|
+
await new Promise((resolve) => {
|
|
87
|
+
server.listen(port, () => {
|
|
88
|
+
const address = server.address();
|
|
89
|
+
if (address === null)
|
|
90
|
+
throw new Error('Failed to get server address');
|
|
91
|
+
if (typeof address === 'object') {
|
|
92
|
+
port = address.port;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
port = parseInt(address.toString(), 10);
|
|
96
|
+
}
|
|
97
|
+
globalState_1.default.debug(`> OAuth callback server listening on port ${port}`);
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
102
|
+
globalState_1.default.debug(`> Starting CLI callback server on URI: ${redirectUri}`);
|
|
103
|
+
// Create OAuth2 issuer and client using openid-client
|
|
104
|
+
const issuerUrl = new url_1.URL('/api/v1/oauth', url).href;
|
|
105
|
+
const issuer = new openid_client_1.Issuer({
|
|
106
|
+
issuer: issuerUrl,
|
|
107
|
+
authorization_endpoint: new url_1.URL('/api/v1/oauth/authorize', url).href,
|
|
108
|
+
token_endpoint: new url_1.URL('/api/v1/oauth/token', url).href,
|
|
109
|
+
});
|
|
110
|
+
const client = new issuer.Client({
|
|
111
|
+
client_id: 'lightdash-cli',
|
|
112
|
+
redirect_uris: [redirectUri],
|
|
113
|
+
response_types: ['code'],
|
|
114
|
+
token_endpoint_auth_method: 'none', // Public client (no client secret)
|
|
115
|
+
id_token_signed_response_alg: 'none', // Disable ID token validation for OAuth2
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
// Generate the authorization URL using openid-client
|
|
119
|
+
const authUrl = client.authorizationUrl({
|
|
120
|
+
scope: 'read write',
|
|
121
|
+
state,
|
|
122
|
+
code_challenge: codeChallenge,
|
|
123
|
+
code_challenge_method: 'S256',
|
|
124
|
+
});
|
|
125
|
+
console.error(`\n${styles.title('🔐 OAuth Authentication')}`);
|
|
126
|
+
console.error(`Opening browser for authentication...`);
|
|
127
|
+
console.error(`If the browser doesn't open automatically, please visit:`);
|
|
128
|
+
console.error(`${styles.secondary(authUrl)}\n`);
|
|
129
|
+
// Try to open the browser
|
|
130
|
+
await (0, oauth_1.openBrowser)(authUrl);
|
|
131
|
+
// Wait for the authorization code
|
|
132
|
+
const { code } = await authPromise;
|
|
133
|
+
globalState_1.default.debug(`> Got authorization code ${code.substring(0, 10)}...`);
|
|
134
|
+
globalState_1.default.debug(`> Getting token for authorization code`);
|
|
135
|
+
// Exchange the authorization code for an access token manually (pure OAuth2)
|
|
136
|
+
const tokenUrl = new url_1.URL('/api/v1/oauth/token', url);
|
|
137
|
+
const tokenResponse = await (0, node_fetch_1.default)(tokenUrl.href, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
141
|
+
},
|
|
142
|
+
body: new URLSearchParams({
|
|
143
|
+
grant_type: 'authorization_code',
|
|
144
|
+
code,
|
|
145
|
+
client_id: 'lightdash-cli',
|
|
146
|
+
redirect_uri: redirectUri,
|
|
147
|
+
code_verifier: codeVerifier,
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
if (!tokenResponse.ok) {
|
|
151
|
+
const errorText = await tokenResponse.text();
|
|
152
|
+
throw new common_1.AuthorizationError(`Token exchange failed: ${tokenResponse.status} ${errorText}`);
|
|
153
|
+
}
|
|
154
|
+
const tokenData = await tokenResponse.json();
|
|
155
|
+
const { access_token: accessToken } = tokenData;
|
|
156
|
+
if (!accessToken) {
|
|
157
|
+
throw new common_1.AuthorizationError('No access token received from OAuth server');
|
|
158
|
+
}
|
|
159
|
+
globalState_1.default.debug(`> OAuth access token: ${accessToken.substring(0, 10)}...`);
|
|
160
|
+
globalState_1.default.debug(`> Creating PAT for user`);
|
|
161
|
+
// Generate a new PAT from this access token
|
|
162
|
+
const pat = await (0, pat_1.generatePersonalAccessToken)({
|
|
163
|
+
Authorization: `Bearer ${accessToken}`,
|
|
164
|
+
}, url, 8);
|
|
165
|
+
globalState_1.default.debug(`> PAT: ${pat.substring(0, 10)}...`);
|
|
166
|
+
// Get user information using the PAT
|
|
167
|
+
const userInfoUrl = new url_1.URL('/api/v1/user', url);
|
|
168
|
+
const userResponse = await (0, node_fetch_1.default)(userInfoUrl.href, {
|
|
169
|
+
method: 'GET',
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `ApiKey ${pat}`,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
if (!userResponse.ok) {
|
|
175
|
+
throw new common_1.AuthorizationError(`Failed to get user info: ${userResponse.status}`);
|
|
176
|
+
}
|
|
177
|
+
const userData = await userResponse.json();
|
|
178
|
+
const { userUuid, organizationUuid, firstName, lastName, email } = userData.results;
|
|
179
|
+
globalState_1.default.debug(`> Got user info for user ${firstName} ${lastName} (${email}): ${userUuid} in organization ${organizationUuid}`);
|
|
180
|
+
return {
|
|
181
|
+
userUuid,
|
|
182
|
+
organizationUuid,
|
|
183
|
+
token: pat,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
// Clean up the server
|
|
188
|
+
server.close();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
exports.loginWithOauth = loginWithOauth;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CreateProjectTableConfiguration } from '@lightdash/common';
|
|
2
|
+
import { DbtCompileOptions } from './dbt/compile';
|
|
3
|
+
type PreviewHandlerOptions = DbtCompileOptions & {
|
|
4
|
+
projectDir: string;
|
|
5
|
+
profilesDir: string;
|
|
6
|
+
target: string | undefined;
|
|
7
|
+
profile: string | undefined;
|
|
8
|
+
name?: string;
|
|
9
|
+
verbose: boolean;
|
|
10
|
+
startOfWeek?: number;
|
|
11
|
+
ignoreErrors: boolean;
|
|
12
|
+
tableConfiguration: CreateProjectTableConfiguration;
|
|
13
|
+
skipCopyContent?: boolean;
|
|
14
|
+
organizationCredentials?: string;
|
|
15
|
+
assumeYes?: boolean;
|
|
16
|
+
warehouseCredentials?: boolean;
|
|
17
|
+
useBatchedDeploy?: boolean;
|
|
18
|
+
batchSize?: string;
|
|
19
|
+
parallelBatches?: string;
|
|
20
|
+
};
|
|
21
|
+
type StopPreviewHandlerOptions = {
|
|
22
|
+
name: string;
|
|
23
|
+
verbose: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare const previewHandler: (options: PreviewHandlerOptions) => Promise<void>;
|
|
26
|
+
export declare const startPreviewHandler: (options: PreviewHandlerOptions) => Promise<void>;
|
|
27
|
+
export declare const stopPreviewHandler: (options: StopPreviewHandlerOptions) => Promise<void>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/handlers/preview.ts"],"names":[],"mappings":"AACA,OAAO,EACH,+BAA+B,EAIlC,MAAM,mBAAmB,CAAC;AAgB3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,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,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,yBAAyB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AA6EF,eAAO,MAAM,cAAc,GACvB,SAAS,qBAAqB,KAC/B,OAAO,CAAC,IAAI,CAmRd,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC5B,SAAS,qBAAqB,KAC/B,OAAO,CAAC,IAAI,CAgJd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC3B,SAAS,yBAAyB,KACnC,OAAO,CAAC,IAAI,CA0Cd,CAAC"}
|