@boltic/cli 0.0.1
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/README.md +149 -0
- package/api/integration.js +469 -0
- package/api/login.js +50 -0
- package/cli.js +128 -0
- package/commands/integration.js +952 -0
- package/commands/login.js +170 -0
- package/config/environments.js +13 -0
- package/helper/command-suggestions.js +54 -0
- package/helper/env.js +27 -0
- package/helper/error.js +123 -0
- package/helper/folder.js +204 -0
- package/helper/secure-storage.js +74 -0
- package/helper/verbose.js +20 -0
- package/index.js +14 -0
- package/package.json +57 -0
- package/templates/schemas.js +506 -0
- package/utils/integration.js +47 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import open from "open";
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
import { getCliBearerToken, getCliSession } from "../api/login.js";
|
|
5
|
+
import { getCurrentEnv } from "../helper/env.js";
|
|
6
|
+
import { deleteAllSecrets, storeSecret } from "../helper/secure-storage.js";
|
|
7
|
+
|
|
8
|
+
// Define login commands and their actions
|
|
9
|
+
const commands = {
|
|
10
|
+
login: {
|
|
11
|
+
description: "Login to the platform and save access token",
|
|
12
|
+
action: handleLogin,
|
|
13
|
+
},
|
|
14
|
+
logout: {
|
|
15
|
+
description: "Logout and clear access token",
|
|
16
|
+
action: handleLogout,
|
|
17
|
+
},
|
|
18
|
+
help: { description: "Show help for login commands", action: showHelp },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Execute a command
|
|
22
|
+
const execute = async (args) => {
|
|
23
|
+
const subCommand = args[0];
|
|
24
|
+
|
|
25
|
+
if (!subCommand || !commands[subCommand]) {
|
|
26
|
+
console.log(chalk.red("❌ Unknown or missing login sub-command.\n"));
|
|
27
|
+
showHelp();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await commands[subCommand].action(args.slice(1));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Show available login commands
|
|
35
|
+
function showHelp() {
|
|
36
|
+
console.log(chalk.cyan("\nLogin Commands:\n"));
|
|
37
|
+
Object.entries(commands).forEach(([cmd, details]) =>
|
|
38
|
+
console.log(chalk.bold(`${cmd}`) + ` - ${details.description}`)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle login command
|
|
43
|
+
async function handleLogin(args) {
|
|
44
|
+
const { apiUrl, loginUrl, clientId, frontendUrl } = await getCurrentEnv();
|
|
45
|
+
|
|
46
|
+
const requestCode = uuidv4();
|
|
47
|
+
const state = {
|
|
48
|
+
source: "boltic_cli",
|
|
49
|
+
request_code: requestCode,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const loginPage = new URL(`${loginUrl}/auth/sign-in`);
|
|
53
|
+
loginPage.searchParams.append("client_id", clientId);
|
|
54
|
+
loginPage.searchParams.append("redirect_uri", frontendUrl);
|
|
55
|
+
loginPage.searchParams.append("state", JSON.stringify(state));
|
|
56
|
+
|
|
57
|
+
console.log(chalk.cyan("\n🌐 Opening browser for login..."));
|
|
58
|
+
try {
|
|
59
|
+
await open(loginPage.toString());
|
|
60
|
+
console.log(chalk.cyan("✅ Browser launched successfully"));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(
|
|
63
|
+
chalk.red(
|
|
64
|
+
`\n❌ Failed to open browser automatically: ${error.message}`
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
console.log(
|
|
68
|
+
chalk.yellow("\n📋 Please copy and paste this URL in your browser:")
|
|
69
|
+
);
|
|
70
|
+
console.log(chalk.cyan("\n" + loginPage.toString() + "\n"));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const timeout = 300000; // 5 minutes in milliseconds
|
|
75
|
+
const pollInterval = 5000; // 5 seconds
|
|
76
|
+
|
|
77
|
+
let lastProgressUpdate = 0;
|
|
78
|
+
console.log(chalk.cyan("\n⏳ Waiting for authentication..."));
|
|
79
|
+
|
|
80
|
+
while (Date.now() - startTime < timeout) {
|
|
81
|
+
try {
|
|
82
|
+
const sessionResponse = await getCliSession(apiUrl, requestCode);
|
|
83
|
+
|
|
84
|
+
if (!sessionResponse?.data?.data) {
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
if (now - lastProgressUpdate >= pollInterval) {
|
|
87
|
+
process.stdout.write(chalk.yellow("."));
|
|
88
|
+
lastProgressUpdate = now;
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { account_id: accountId, session } =
|
|
94
|
+
sessionResponse.data.data;
|
|
95
|
+
|
|
96
|
+
if (!accountId || !session) {
|
|
97
|
+
console.log(
|
|
98
|
+
chalk.yellow(
|
|
99
|
+
"\n⚠️ Invalid session data received, retrying..."
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await storeSecret(
|
|
107
|
+
"session",
|
|
108
|
+
`bolt.session=${encodeURIComponent(session)}`
|
|
109
|
+
);
|
|
110
|
+
await storeSecret("account_id", accountId);
|
|
111
|
+
|
|
112
|
+
const token = await getCliBearerToken(
|
|
113
|
+
apiUrl,
|
|
114
|
+
accountId,
|
|
115
|
+
session
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (!token?.data?.data?.token) {
|
|
119
|
+
throw new Error("Invalid token response");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await storeSecret("token", token.data.data.token);
|
|
123
|
+
console.log(chalk.green("\n✅ Login successful!"));
|
|
124
|
+
return;
|
|
125
|
+
} catch (storageError) {
|
|
126
|
+
console.error(
|
|
127
|
+
chalk.red(
|
|
128
|
+
`\n❌ Failed to store authentication data: ${storageError.message}`
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error?.response?.status === 401) {
|
|
135
|
+
console.error(
|
|
136
|
+
chalk.red("\n\n❌ Authentication failed. Please try again.")
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
} else if (error?.code === "ECONNREFUSED") {
|
|
140
|
+
console.error(
|
|
141
|
+
chalk.red("\n\n❌ Cannot connect to authentication server.")
|
|
142
|
+
);
|
|
143
|
+
return;
|
|
144
|
+
} else if (error?.response?.status !== 404) {
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
if (now - lastProgressUpdate >= pollInterval) {
|
|
147
|
+
process.stdout.write(chalk.yellow("x"));
|
|
148
|
+
lastProgressUpdate = now;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.error(
|
|
157
|
+
chalk.red("\n❌ Login timeout after 5 minutes. Please try again.")
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Handle logout command
|
|
162
|
+
async function handleLogout() {
|
|
163
|
+
await deleteAllSecrets();
|
|
164
|
+
console.log(
|
|
165
|
+
chalk.bgGreen.black("\n ✅ Success! ") +
|
|
166
|
+
chalk.green(" Logout successful! All user data cleared.\n")
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default { execute, handleLogin, handleLogout };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const environments = {
|
|
2
|
+
bolt: {
|
|
3
|
+
name: "boltic",
|
|
4
|
+
consoleUrl: "https://api.console.fynd.com",
|
|
5
|
+
apiUrl: "https://asia-south1.api.boltic.io",
|
|
6
|
+
loginUrl: "https://console.fynd.com",
|
|
7
|
+
clientId: "40ec7873-ce38-4f0a-923b-1ebe96887d78",
|
|
8
|
+
allowedOrigin: "*.console.boltic.io",
|
|
9
|
+
frontendUrl: "https://asia-south1.console.boltic.io",
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { environments };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Helper function to calculate Levenshtein distance between two strings
|
|
2
|
+
function levenshteinDistance(str1, str2) {
|
|
3
|
+
const m = str1.length;
|
|
4
|
+
const n = str2.length;
|
|
5
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
8
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
9
|
+
|
|
10
|
+
for (let i = 1; i <= m; i++) {
|
|
11
|
+
for (let j = 1; j <= n; j++) {
|
|
12
|
+
if (str1[i - 1] === str2[j - 1]) {
|
|
13
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
14
|
+
} else {
|
|
15
|
+
dp[i][j] =
|
|
16
|
+
Math.min(
|
|
17
|
+
dp[i - 1][j - 1], // substitution
|
|
18
|
+
dp[i - 1][j], // deletion
|
|
19
|
+
dp[i][j - 1] // insertion
|
|
20
|
+
) + 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return dp[m][n];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Find similar commands based on Levenshtein distance and prefix matching
|
|
29
|
+
export function findSimilarCommands(invalidCommand, availableCommands) {
|
|
30
|
+
const threshold = 3; // Maximum distance to consider as similar
|
|
31
|
+
const suggestions = new Set(); // Use Set to avoid duplicates
|
|
32
|
+
const lowerInvalidCmd = invalidCommand.toLowerCase();
|
|
33
|
+
|
|
34
|
+
for (const cmd of Object.keys(availableCommands)) {
|
|
35
|
+
const lowerCmd = cmd.toLowerCase();
|
|
36
|
+
|
|
37
|
+
// Check for prefix match first
|
|
38
|
+
if (
|
|
39
|
+
lowerCmd.startsWith(lowerInvalidCmd) ||
|
|
40
|
+
lowerInvalidCmd.startsWith(lowerCmd)
|
|
41
|
+
) {
|
|
42
|
+
suggestions.add(cmd);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If no prefix match, check Levenshtein distance
|
|
47
|
+
const distance = levenshteinDistance(lowerInvalidCmd, lowerCmd);
|
|
48
|
+
if (distance <= threshold) {
|
|
49
|
+
suggestions.add(cmd);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Array.from(suggestions);
|
|
54
|
+
}
|
package/helper/env.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { environments } from "../config/environments.js";
|
|
2
|
+
import { getAllSecrets } from "./secure-storage.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get current environment and tokens from config
|
|
6
|
+
* @returns {Object} Environment configuration including apiUrl, token, session, and accountId
|
|
7
|
+
*/
|
|
8
|
+
export const getCurrentEnv = async () => {
|
|
9
|
+
const secrets = await getAllSecrets();
|
|
10
|
+
let config;
|
|
11
|
+
if (secrets && secrets.length > 0) {
|
|
12
|
+
config = secrets.reduce((acc, { account, password }) => {
|
|
13
|
+
acc[account] = password;
|
|
14
|
+
return acc;
|
|
15
|
+
}, {});
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
apiUrl: environments.bolt.apiUrl,
|
|
19
|
+
loginUrl: environments.bolt.loginUrl,
|
|
20
|
+
consoleUrl: environments.bolt.consoleUrl,
|
|
21
|
+
clientId: environments.bolt.clientId,
|
|
22
|
+
token: config?.token || null,
|
|
23
|
+
session: config?.session || null,
|
|
24
|
+
accountId: config?.account_id || null,
|
|
25
|
+
frontendUrl: environments.bolt.frontendUrl,
|
|
26
|
+
};
|
|
27
|
+
};
|
package/helper/error.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
// Error types for different scenarios
|
|
4
|
+
const ErrorType = {
|
|
5
|
+
API_ERROR: "API_ERROR",
|
|
6
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
7
|
+
AUTH_ERROR: "AUTH_ERROR",
|
|
8
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
9
|
+
CONFIG_ERROR: "CONFIG_ERROR",
|
|
10
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Format error message based on error type and response
|
|
14
|
+
const formatErrorMessage = (error) => {
|
|
15
|
+
if (!error)
|
|
16
|
+
return {
|
|
17
|
+
type: ErrorType.UNKNOWN_ERROR,
|
|
18
|
+
message: "An unknown error occurred",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Handle API response errors
|
|
22
|
+
if (error.response) {
|
|
23
|
+
const { status, data } = error.response;
|
|
24
|
+
|
|
25
|
+
// Authentication errors
|
|
26
|
+
if (status === 401 || status === 403) {
|
|
27
|
+
return {
|
|
28
|
+
type: ErrorType.AUTH_ERROR,
|
|
29
|
+
message:
|
|
30
|
+
data.message ||
|
|
31
|
+
"Authentication failed. Please login again.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Validation errors
|
|
36
|
+
if (status === 400) {
|
|
37
|
+
return {
|
|
38
|
+
type: ErrorType.VALIDATION_ERROR,
|
|
39
|
+
message:
|
|
40
|
+
data.message || "Invalid request. Please check your input.",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Server errors
|
|
45
|
+
if (status >= 500) {
|
|
46
|
+
return {
|
|
47
|
+
type: ErrorType.API_ERROR,
|
|
48
|
+
message:
|
|
49
|
+
error.response?.data?.error?.message ||
|
|
50
|
+
"Server error occurred. Please try again later.",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Default API error
|
|
55
|
+
return {
|
|
56
|
+
type: ErrorType.API_ERROR,
|
|
57
|
+
message: data.message || `API Error: ${status}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Network errors
|
|
62
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") {
|
|
63
|
+
return {
|
|
64
|
+
type: ErrorType.NETWORK_ERROR,
|
|
65
|
+
message:
|
|
66
|
+
"Unable to connect to the server. Please check your internet connection.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Configuration errors
|
|
71
|
+
if (error.code === "ENOENT") {
|
|
72
|
+
return {
|
|
73
|
+
type: ErrorType.CONFIG_ERROR,
|
|
74
|
+
message: "Configuration file not found. Please run setup again.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Default unknown error
|
|
79
|
+
return {
|
|
80
|
+
type: ErrorType.UNKNOWN_ERROR,
|
|
81
|
+
message: error.message || "An unexpected error occurred",
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Display formatted error message to user
|
|
86
|
+
const handleError = (error) => {
|
|
87
|
+
const formattedError = formatErrorMessage(error);
|
|
88
|
+
|
|
89
|
+
switch (formattedError.type) {
|
|
90
|
+
case ErrorType.AUTH_ERROR:
|
|
91
|
+
console.error(
|
|
92
|
+
chalk.red("\n❌ Authentication Error:"),
|
|
93
|
+
formattedError.message
|
|
94
|
+
);
|
|
95
|
+
break;
|
|
96
|
+
case ErrorType.API_ERROR:
|
|
97
|
+
console.error(chalk.red("\n❌ API Error:"), formattedError.message);
|
|
98
|
+
break;
|
|
99
|
+
case ErrorType.NETWORK_ERROR:
|
|
100
|
+
console.error(
|
|
101
|
+
chalk.red("\n❌ Network Error:"),
|
|
102
|
+
formattedError.message
|
|
103
|
+
);
|
|
104
|
+
break;
|
|
105
|
+
case ErrorType.VALIDATION_ERROR:
|
|
106
|
+
console.error(
|
|
107
|
+
chalk.red("\n❌ Validation Error:"),
|
|
108
|
+
formattedError.message
|
|
109
|
+
);
|
|
110
|
+
break;
|
|
111
|
+
case ErrorType.CONFIG_ERROR:
|
|
112
|
+
console.error(
|
|
113
|
+
chalk.red("\n❌ Configuration Error:"),
|
|
114
|
+
formattedError.message
|
|
115
|
+
);
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
console.error(chalk.red("\n❌ Error:"), formattedError.message);
|
|
119
|
+
}
|
|
120
|
+
process.exit(1);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export { ErrorType, handleError };
|
package/helper/folder.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import {
|
|
5
|
+
authentication,
|
|
6
|
+
base,
|
|
7
|
+
resource1,
|
|
8
|
+
webhook,
|
|
9
|
+
} from "../templates/schemas.js";
|
|
10
|
+
|
|
11
|
+
export const createIntegrationFolderStructure = async (integration) => {
|
|
12
|
+
const { id, name, description, icon, activity_type, trigger_type, meta } =
|
|
13
|
+
integration;
|
|
14
|
+
|
|
15
|
+
const spec = {
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
description,
|
|
19
|
+
icon,
|
|
20
|
+
activity_type,
|
|
21
|
+
trigger_type,
|
|
22
|
+
meta,
|
|
23
|
+
};
|
|
24
|
+
// Create integration folder structure
|
|
25
|
+
const integrationName = name.toLowerCase().replace(/\s+/g, "-");
|
|
26
|
+
const integrationDir = path.join(process.cwd(), integrationName);
|
|
27
|
+
|
|
28
|
+
// Ensure the integration directory doesn't exist
|
|
29
|
+
if (fs.existsSync(integrationDir)) {
|
|
30
|
+
console.log(
|
|
31
|
+
chalk.yellow(
|
|
32
|
+
`\nWarning: Directory ${integrationDir} already exists!`
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
return integrationDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create main directory
|
|
39
|
+
fs.mkdirSync(integrationDir, { recursive: true });
|
|
40
|
+
|
|
41
|
+
// Create schemas directory and its subdirectories
|
|
42
|
+
const schemasDir = path.join(integrationDir, "schemas");
|
|
43
|
+
const resourcesDir = path.join(schemasDir, "resources");
|
|
44
|
+
fs.mkdirSync(resourcesDir, { recursive: true });
|
|
45
|
+
|
|
46
|
+
// Create template files
|
|
47
|
+
const files = {
|
|
48
|
+
"schemas/resources/resource1.json": JSON.stringify(resource1, null, 4),
|
|
49
|
+
"schemas/authentication.json": JSON.stringify(authentication, null, 4),
|
|
50
|
+
"schemas/base.json": JSON.stringify(base(name), null, 4),
|
|
51
|
+
"schemas/webhook.json": JSON.stringify(webhook(name), null, 4),
|
|
52
|
+
"spec.json": JSON.stringify(spec, null, 4),
|
|
53
|
+
"Authentication.mdx": `# ${name} Authentication
|
|
54
|
+
|
|
55
|
+
Describe the authentication process for ${name} integration here.`,
|
|
56
|
+
"Documentation.mdx": `# ${name} Documentation
|
|
57
|
+
|
|
58
|
+
${description}
|
|
59
|
+
|
|
60
|
+
## Overview
|
|
61
|
+
|
|
62
|
+
Add integration overview here.
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- Feature 1
|
|
67
|
+
- Feature 2
|
|
68
|
+
- Feature 3
|
|
69
|
+
|
|
70
|
+
## Setup Guide
|
|
71
|
+
|
|
72
|
+
1. Step 1
|
|
73
|
+
2. Step 2
|
|
74
|
+
3. Step 3`,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Create all files
|
|
78
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
79
|
+
const fullPath = path.join(integrationDir, filePath);
|
|
80
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
81
|
+
fs.writeFileSync(fullPath, content);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(chalk.cyan(`To navigate to your integration folder, run:\n`));
|
|
85
|
+
console.log(chalk.white(` cd ${integrationName}\n`));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const createExistingIntegrationsFolder = async (payload) => {
|
|
89
|
+
const {
|
|
90
|
+
integration,
|
|
91
|
+
authentication,
|
|
92
|
+
webhook,
|
|
93
|
+
configuration,
|
|
94
|
+
resources,
|
|
95
|
+
operations,
|
|
96
|
+
} = payload;
|
|
97
|
+
|
|
98
|
+
const {
|
|
99
|
+
id,
|
|
100
|
+
name,
|
|
101
|
+
description,
|
|
102
|
+
icon,
|
|
103
|
+
activity_type,
|
|
104
|
+
trigger_type,
|
|
105
|
+
documentation,
|
|
106
|
+
meta,
|
|
107
|
+
} = integration;
|
|
108
|
+
|
|
109
|
+
const spec = {
|
|
110
|
+
id,
|
|
111
|
+
name,
|
|
112
|
+
description,
|
|
113
|
+
icon,
|
|
114
|
+
activity_type,
|
|
115
|
+
trigger_type,
|
|
116
|
+
meta,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const integrationName = name.toLowerCase().replace(/\s+/g, "-");
|
|
120
|
+
const integrationDir = path.join(process.cwd(), integrationName);
|
|
121
|
+
|
|
122
|
+
// Log if the directory already exists
|
|
123
|
+
if (fs.existsSync(integrationDir)) {
|
|
124
|
+
console.log(
|
|
125
|
+
chalk.yellow(
|
|
126
|
+
`\nNotice: Directory ${integrationDir} already exists. Updating contents...\n`
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
} else {
|
|
130
|
+
console.log(
|
|
131
|
+
chalk.green(`\nCreating integration directory: ${integrationDir}\n`)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Ensure all necessary folders exist
|
|
136
|
+
fs.mkdirSync(integrationDir, { recursive: true });
|
|
137
|
+
|
|
138
|
+
const schemasDir = path.join(integrationDir, "schemas");
|
|
139
|
+
const resourcesDir = path.join(schemasDir, "resources");
|
|
140
|
+
fs.mkdirSync(resourcesDir, { recursive: true });
|
|
141
|
+
|
|
142
|
+
const authentication_documentation = authentication.documentation;
|
|
143
|
+
|
|
144
|
+
// Define files and content
|
|
145
|
+
const files = {
|
|
146
|
+
"schemas/authentication.json": JSON.stringify(
|
|
147
|
+
authentication.content || {},
|
|
148
|
+
null,
|
|
149
|
+
4
|
|
150
|
+
),
|
|
151
|
+
"schemas/base.json": JSON.stringify(
|
|
152
|
+
configuration?.content || {},
|
|
153
|
+
null,
|
|
154
|
+
4
|
|
155
|
+
),
|
|
156
|
+
"schemas/webhook.json": JSON.stringify(webhook?.content || {}, null, 4),
|
|
157
|
+
"spec.json": JSON.stringify(spec, null, 4),
|
|
158
|
+
"Authentication.mdx": authentication_documentation || "",
|
|
159
|
+
"Documentation.mdx": documentation || "",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Write resource files
|
|
163
|
+
resources.forEach((resource) => {
|
|
164
|
+
const resource_id = resource.id;
|
|
165
|
+
const resourceName = resource.name.toLowerCase().replace(/\s+/g, "-");
|
|
166
|
+
const resourcePath = path.join(resourcesDir, `${resourceName}.json`);
|
|
167
|
+
|
|
168
|
+
const resourceOperations = operations.filter(
|
|
169
|
+
(operation) => operation.resource_id === resource_id
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const operationsContent = resourceOperations.reduce(
|
|
173
|
+
(acc, operation) => {
|
|
174
|
+
const operationName = operation.name
|
|
175
|
+
.toLowerCase()
|
|
176
|
+
.replace(/\s+/g, "-");
|
|
177
|
+
acc[operationName] = operation.content;
|
|
178
|
+
return acc;
|
|
179
|
+
},
|
|
180
|
+
{}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const resourceFileContent = {
|
|
184
|
+
...resource.content,
|
|
185
|
+
...operationsContent,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
fs.writeFileSync(
|
|
189
|
+
resourcePath,
|
|
190
|
+
JSON.stringify(resourceFileContent, null, 4)
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Write or overwrite all defined files
|
|
195
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
196
|
+
const fullPath = path.join(integrationDir, filePath);
|
|
197
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
198
|
+
fs.writeFileSync(fullPath, content);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(chalk.cyan(`\nIntegration folder is ready at:`));
|
|
202
|
+
console.log(chalk.white(` ${integrationDir}\n`));
|
|
203
|
+
return true;
|
|
204
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import keytar from "keytar";
|
|
2
|
+
|
|
3
|
+
const SERVICE_NAME = "boltic-cli";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Store a secret value securely using keytar
|
|
7
|
+
* @param {string} key - The key under which to store the secret
|
|
8
|
+
* @param {string} value - The secret value to store
|
|
9
|
+
* @returns {Promise<void>}
|
|
10
|
+
*/
|
|
11
|
+
export const storeSecret = async (key, value) => {
|
|
12
|
+
try {
|
|
13
|
+
await keytar.setPassword(SERVICE_NAME, key, value);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`Error storing secret for ${key}:`, error.message);
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Retrieve a secret value using keytar
|
|
22
|
+
* @param {string} key - The key of the secret to retrieve
|
|
23
|
+
* @returns {Promise<string|null>} The secret value or null if not found
|
|
24
|
+
*/
|
|
25
|
+
export const getSecret = async (key) => {
|
|
26
|
+
try {
|
|
27
|
+
return await keytar.getPassword(SERVICE_NAME, key);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`Error retrieving secret for ${key}:`, error.message);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Delete a secret value using keytar
|
|
36
|
+
* @param {string} key - The key of the secret to delete
|
|
37
|
+
* @returns {Promise<boolean>} True if deletion was successful
|
|
38
|
+
*/
|
|
39
|
+
export const deleteSecret = async (key) => {
|
|
40
|
+
try {
|
|
41
|
+
return await keytar.deletePassword(SERVICE_NAME, key);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`Error deleting secret for ${key}:`, error.message);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retrieve all secrets stored using keytar
|
|
50
|
+
* @returns {Promise<Array<{account: string, password: string}>|null>} An array of secret objects or null if an error occurs
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
export const getAllSecrets = async () => {
|
|
54
|
+
try {
|
|
55
|
+
return await keytar.findCredentials(SERVICE_NAME);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Error retrieving all secrets:`, error.message);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const deleteAllSecrets = async () => {
|
|
63
|
+
try {
|
|
64
|
+
const secrets = await getAllSecrets();
|
|
65
|
+
if (secrets && secrets.length > 0) {
|
|
66
|
+
const deletionPromises = secrets.map(
|
|
67
|
+
async ({ account }) => await deleteSecret(account)
|
|
68
|
+
);
|
|
69
|
+
await Promise.all(deletionPromises);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Error deleting all secrets:`, error.message);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
let isVerbose = false;
|
|
4
|
+
|
|
5
|
+
export const setVerboseMode = (verbose) => {
|
|
6
|
+
isVerbose = verbose;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const getVerboseMode = () => {
|
|
10
|
+
return isVerbose;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const logApi = (method, url, status) => {
|
|
14
|
+
if (!isVerbose) return;
|
|
15
|
+
console.log(
|
|
16
|
+
chalk(
|
|
17
|
+
`https fetch ${chalk.cyan(method.toUpperCase())} ${chalk.green(status)} ${chalk.yellow(url)}`
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
};
|
package/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import createCLI from "./cli.js";
|
|
4
|
+
import { environments } from "./config/environments.js";
|
|
5
|
+
|
|
6
|
+
// Get environment-specific URLs
|
|
7
|
+
const env = environments.bolt;
|
|
8
|
+
const FYND_CONSOLE_URL = env.consoleUrl;
|
|
9
|
+
const BOLTIC_API_URL = env.apiUrl;
|
|
10
|
+
|
|
11
|
+
(async () => {
|
|
12
|
+
const cli = createCLI(FYND_CONSOLE_URL, BOLTIC_API_URL, env);
|
|
13
|
+
await cli.execute(process.argv);
|
|
14
|
+
})();
|