@boltic/cli 1.0.44-dev1.3 → 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 +94 -46
- package/package.json +1 -1
package/helper/secure-storage.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
1
5
|
const SERVICE_NAME = "boltic-cli";
|
|
2
6
|
|
|
3
7
|
// Mapping from credential keys to environment variable names.
|
|
4
|
-
//
|
|
5
|
-
// these env vars are used as a fallback so commands like `boltic serverless publish`
|
|
6
|
-
// work without an interactive keychain daemon.
|
|
8
|
+
// Checked last (lowest priority) so explicit login always wins.
|
|
7
9
|
const ENV_VAR_MAP = {
|
|
8
10
|
token: "BOLTIC_TOKEN",
|
|
9
11
|
account_id: "BOLTIC_ACCOUNT_ID",
|
|
@@ -11,10 +13,31 @@ const ENV_VAR_MAP = {
|
|
|
11
13
|
environment: "BOLTIC_ENVIRONMENT",
|
|
12
14
|
};
|
|
13
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
|
+
|
|
14
38
|
// Lazy-load keytar via dynamic import so that a missing native dependency
|
|
15
39
|
// (e.g. libsecret on Linux CI runners) is caught at call time rather than
|
|
16
40
|
// crashing the process at startup with ERR_DLOPEN_FAILED.
|
|
17
|
-
// The result is cached after the first attempt.
|
|
18
41
|
let _keytar;
|
|
19
42
|
let _keytarAttempted = false;
|
|
20
43
|
|
|
@@ -31,36 +54,28 @@ const getKeytar = async () => {
|
|
|
31
54
|
};
|
|
32
55
|
|
|
33
56
|
/**
|
|
34
|
-
* Store a secret value
|
|
35
|
-
*
|
|
36
|
-
* instead of throwing so that env-var-based auth can still be used.
|
|
57
|
+
* Store a secret value.
|
|
58
|
+
* Priority: keytar (OS keychain) → file store (~/.boltic/credentials.json)
|
|
37
59
|
*/
|
|
38
60
|
export const storeSecret = async (key, value) => {
|
|
39
61
|
const keytar = await getKeytar();
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
await keytar.setPassword(SERVICE_NAME, key, value);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.warn(
|
|
53
|
-
`Warning: Could not store '${key}' in system keychain: ${error.message}`
|
|
54
|
-
);
|
|
55
|
-
console.warn(
|
|
56
|
-
`In CI environments, set credentials via environment variables (e.g. BOLTIC_TOKEN, BOLTIC_ACCOUNT_ID).`
|
|
57
|
-
);
|
|
62
|
+
if (keytar) {
|
|
63
|
+
try {
|
|
64
|
+
await keytar.setPassword(SERVICE_NAME, key, value);
|
|
65
|
+
return;
|
|
66
|
+
} catch {
|
|
67
|
+
// fall through to file store
|
|
68
|
+
}
|
|
58
69
|
}
|
|
70
|
+
// File-based fallback (CI / headless environments)
|
|
71
|
+
const data = readCredFile();
|
|
72
|
+
data[key] = value;
|
|
73
|
+
writeCredFile(data);
|
|
59
74
|
};
|
|
60
75
|
|
|
61
76
|
/**
|
|
62
|
-
* Retrieve a secret value.
|
|
63
|
-
*
|
|
77
|
+
* Retrieve a secret value.
|
|
78
|
+
* Priority: keytar → file store → environment variables
|
|
64
79
|
*/
|
|
65
80
|
export const getSecret = async (key) => {
|
|
66
81
|
const keytar = await getKeytar();
|
|
@@ -69,29 +84,42 @@ export const getSecret = async (key) => {
|
|
|
69
84
|
const val = await keytar.getPassword(SERVICE_NAME, key);
|
|
70
85
|
if (val !== null) return val;
|
|
71
86
|
} catch {
|
|
72
|
-
//
|
|
87
|
+
// fall through
|
|
73
88
|
}
|
|
74
89
|
}
|
|
90
|
+
// File store fallback
|
|
91
|
+
const val = readCredFile()[key];
|
|
92
|
+
if (val != null) return val;
|
|
93
|
+
// Env var fallback
|
|
75
94
|
return process.env[ENV_VAR_MAP[key]] || null;
|
|
76
95
|
};
|
|
77
96
|
|
|
78
97
|
/**
|
|
79
|
-
* Delete a secret value
|
|
98
|
+
* Delete a secret value.
|
|
99
|
+
* Priority: keytar → file store
|
|
80
100
|
*/
|
|
81
101
|
export const deleteSecret = async (key) => {
|
|
82
102
|
const keytar = await getKeytar();
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// File store fallback
|
|
112
|
+
const data = readCredFile();
|
|
113
|
+
if (key in data) {
|
|
114
|
+
delete data[key];
|
|
115
|
+
writeCredFile(data);
|
|
89
116
|
}
|
|
117
|
+
return true;
|
|
90
118
|
};
|
|
91
119
|
|
|
92
120
|
/**
|
|
93
|
-
* Retrieve all secrets.
|
|
94
|
-
* keytar
|
|
121
|
+
* Retrieve all secrets.
|
|
122
|
+
* Priority: keytar → file store → environment variables
|
|
95
123
|
*/
|
|
96
124
|
export const getAllSecrets = async () => {
|
|
97
125
|
const keytar = await getKeytar();
|
|
@@ -100,11 +128,18 @@ export const getAllSecrets = async () => {
|
|
|
100
128
|
const keytarSecrets = await keytar.findCredentials(SERVICE_NAME);
|
|
101
129
|
if (keytarSecrets && keytarSecrets.length > 0) return keytarSecrets;
|
|
102
130
|
} catch {
|
|
103
|
-
//
|
|
131
|
+
// fall through
|
|
104
132
|
}
|
|
105
133
|
}
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
108
143
|
const secrets = [];
|
|
109
144
|
for (const [key, envVar] of Object.entries(ENV_VAR_MAP)) {
|
|
110
145
|
if (process.env[envVar]) {
|
|
@@ -116,12 +151,25 @@ export const getAllSecrets = async () => {
|
|
|
116
151
|
|
|
117
152
|
export const deleteAllSecrets = async () => {
|
|
118
153
|
try {
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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);
|
|
125
173
|
}
|
|
126
174
|
} catch (error) {
|
|
127
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": {
|