@boltic/cli 1.0.44-dev1.2 → 1.0.44-dev1.4
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/helper/secure-storage.js +127 -54
- package/package.json +1 -1
package/helper/secure-storage.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
2
4
|
|
|
3
5
|
const SERVICE_NAME = "boltic-cli";
|
|
4
6
|
|
|
5
7
|
// Mapping from credential keys to environment variable names.
|
|
6
|
-
//
|
|
7
|
-
// these env vars are used as a fallback so commands like `boltic serverless publish`
|
|
8
|
-
// work without an interactive keychain daemon.
|
|
8
|
+
// Checked last (lowest priority) so explicit login always wins.
|
|
9
9
|
const ENV_VAR_MAP = {
|
|
10
10
|
token: "BOLTIC_TOKEN",
|
|
11
11
|
account_id: "BOLTIC_ACCOUNT_ID",
|
|
@@ -13,73 +13,133 @@ const ENV_VAR_MAP = {
|
|
|
13
13
|
environment: "BOLTIC_ENVIRONMENT",
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
// File-based credential store used when keytar (OS keychain) is unavailable.
|
|
17
|
+
// Credentials are stored as plain JSON, readable only by the current user.
|
|
18
|
+
const CRED_FILE = path.join(os.homedir(), ".boltic", "credentials.json");
|
|
19
|
+
|
|
20
|
+
const readCredFile = () => {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(fs.readFileSync(CRED_FILE, "utf-8"));
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const writeCredFile = (data) => {
|
|
29
|
+
const dir = path.dirname(CRED_FILE);
|
|
30
|
+
if (!fs.existsSync(dir)) {
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(CRED_FILE, JSON.stringify(data, null, 2), {
|
|
34
|
+
mode: 0o600,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Lazy-load keytar via dynamic import so that a missing native dependency
|
|
39
|
+
// (e.g. libsecret on Linux CI runners) is caught at call time rather than
|
|
40
|
+
// crashing the process at startup with ERR_DLOPEN_FAILED.
|
|
41
|
+
let _keytar;
|
|
42
|
+
let _keytarAttempted = false;
|
|
43
|
+
|
|
44
|
+
const getKeytar = async () => {
|
|
45
|
+
if (_keytarAttempted) return _keytar;
|
|
46
|
+
_keytarAttempted = true;
|
|
47
|
+
try {
|
|
48
|
+
_keytar = (await import("keytar")).default;
|
|
49
|
+
} catch {
|
|
50
|
+
// Native library not available (e.g. libsecret missing on CI runners).
|
|
51
|
+
_keytar = null;
|
|
52
|
+
}
|
|
53
|
+
return _keytar;
|
|
54
|
+
};
|
|
55
|
+
|
|
16
56
|
/**
|
|
17
|
-
* Store a secret value
|
|
18
|
-
*
|
|
19
|
-
* instead of throwing so that env-var-based auth can still be used.
|
|
20
|
-
* @param {string} key - The key under which to store the secret
|
|
21
|
-
* @param {string} value - The secret value to store
|
|
22
|
-
* @returns {Promise<void>}
|
|
57
|
+
* Store a secret value.
|
|
58
|
+
* Priority: keytar (OS keychain) → file store (~/.boltic/credentials.json)
|
|
23
59
|
*/
|
|
24
60
|
export const storeSecret = async (key, value) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.warn(
|
|
34
|
-
`In CI environments, set credentials via environment variables (e.g. BOLTIC_TOKEN, BOLTIC_ACCOUNT_ID).`
|
|
35
|
-
);
|
|
61
|
+
const keytar = await getKeytar();
|
|
62
|
+
if (keytar) {
|
|
63
|
+
try {
|
|
64
|
+
await keytar.setPassword(SERVICE_NAME, key, value);
|
|
65
|
+
return;
|
|
66
|
+
} catch {
|
|
67
|
+
// fall through to file store
|
|
68
|
+
}
|
|
36
69
|
}
|
|
70
|
+
// File-based fallback (CI / headless environments)
|
|
71
|
+
const data = readCredFile();
|
|
72
|
+
data[key] = value;
|
|
73
|
+
writeCredFile(data);
|
|
37
74
|
};
|
|
38
75
|
|
|
39
76
|
/**
|
|
40
|
-
* Retrieve a secret value.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} key - The key of the secret to retrieve
|
|
43
|
-
* @returns {Promise<string|null>} The secret value or null if not found
|
|
77
|
+
* Retrieve a secret value.
|
|
78
|
+
* Priority: keytar → file store → environment variables
|
|
44
79
|
*/
|
|
45
80
|
export const getSecret = async (key) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
81
|
+
const keytar = await getKeytar();
|
|
82
|
+
if (keytar) {
|
|
83
|
+
try {
|
|
84
|
+
const val = await keytar.getPassword(SERVICE_NAME, key);
|
|
85
|
+
if (val !== null) return val;
|
|
86
|
+
} catch {
|
|
87
|
+
// fall through
|
|
88
|
+
}
|
|
51
89
|
}
|
|
90
|
+
// File store fallback
|
|
91
|
+
const val = readCredFile()[key];
|
|
92
|
+
if (val != null) return val;
|
|
93
|
+
// Env var fallback
|
|
52
94
|
return process.env[ENV_VAR_MAP[key]] || null;
|
|
53
95
|
};
|
|
54
96
|
|
|
55
97
|
/**
|
|
56
|
-
* Delete a secret value
|
|
57
|
-
*
|
|
58
|
-
* @returns {Promise<boolean>} True if deletion was successful
|
|
98
|
+
* Delete a secret value.
|
|
99
|
+
* Priority: keytar → file store
|
|
59
100
|
*/
|
|
60
101
|
export const deleteSecret = async (key) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
const keytar = await getKeytar();
|
|
103
|
+
if (keytar) {
|
|
104
|
+
try {
|
|
105
|
+
return await keytar.deletePassword(SERVICE_NAME, key);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`Error deleting secret for ${key}:`, error.message);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
66
110
|
}
|
|
111
|
+
// File store fallback
|
|
112
|
+
const data = readCredFile();
|
|
113
|
+
if (key in data) {
|
|
114
|
+
delete data[key];
|
|
115
|
+
writeCredFile(data);
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
67
118
|
};
|
|
68
119
|
|
|
69
120
|
/**
|
|
70
|
-
* Retrieve all secrets.
|
|
71
|
-
* keytar
|
|
72
|
-
* @returns {Promise<Array<{account: string, password: string}>|null>}
|
|
121
|
+
* Retrieve all secrets.
|
|
122
|
+
* Priority: keytar → file store → environment variables
|
|
73
123
|
*/
|
|
74
124
|
export const getAllSecrets = async () => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
125
|
+
const keytar = await getKeytar();
|
|
126
|
+
if (keytar) {
|
|
127
|
+
try {
|
|
128
|
+
const keytarSecrets = await keytar.findCredentials(SERVICE_NAME);
|
|
129
|
+
if (keytarSecrets && keytarSecrets.length > 0) return keytarSecrets;
|
|
130
|
+
} catch {
|
|
131
|
+
// fall through
|
|
132
|
+
}
|
|
80
133
|
}
|
|
81
|
-
|
|
82
|
-
|
|
134
|
+
// File store fallback
|
|
135
|
+
const fileData = readCredFile();
|
|
136
|
+
if (Object.keys(fileData).length > 0) {
|
|
137
|
+
return Object.entries(fileData).map(([account, password]) => ({
|
|
138
|
+
account,
|
|
139
|
+
password,
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
// Env var fallback
|
|
83
143
|
const secrets = [];
|
|
84
144
|
for (const [key, envVar] of Object.entries(ENV_VAR_MAP)) {
|
|
85
145
|
if (process.env[envVar]) {
|
|
@@ -91,12 +151,25 @@ export const getAllSecrets = async () => {
|
|
|
91
151
|
|
|
92
152
|
export const deleteAllSecrets = async () => {
|
|
93
153
|
try {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
const keytar = await getKeytar();
|
|
155
|
+
if (keytar) {
|
|
156
|
+
try {
|
|
157
|
+
const secrets = await keytar.findCredentials(SERVICE_NAME);
|
|
158
|
+
if (secrets && secrets.length > 0) {
|
|
159
|
+
await Promise.all(
|
|
160
|
+
secrets.map(({ account }) =>
|
|
161
|
+
keytar.deletePassword(SERVICE_NAME, account)
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
// fall through to file store
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// File store fallback
|
|
171
|
+
if (fs.existsSync(CRED_FILE)) {
|
|
172
|
+
fs.unlinkSync(CRED_FILE);
|
|
100
173
|
}
|
|
101
174
|
} catch (error) {
|
|
102
175
|
console.error(`Error deleting all secrets:`, error.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boltic/cli",
|
|
3
|
-
"version": "1.0.44-dev1.
|
|
3
|
+
"version": "1.0.44-dev1.4",
|
|
4
4
|
"description": "Professional CLI for interacting with the Boltic platform — create, manage, and publish integrations, serverless functions, workflows, MCPs, and more with enterprise-grade features and a seamless developer experience",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|