@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.
@@ -1,11 +1,11 @@
1
- import keytar from "keytar";
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
- // In CI/headless environments where keytar (OS keychain) is unavailable,
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 securely using keytar.
18
- * In CI/headless environments where keytar is unavailable, logs a warning
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
- try {
26
- await keytar.setPassword(SERVICE_NAME, key, value);
27
- } catch (error) {
28
- // In headless/CI environments the OS keychain daemon is not available.
29
- // Warn instead of throwing so callers can still rely on env var fallback.
30
- console.warn(
31
- `Warning: Could not store '${key}' in system keychain: ${error.message}`
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. Tries keytar first; falls back to env vars
41
- * (BOLTIC_TOKEN, BOLTIC_PAT, BOLTIC_ACCOUNT_ID, etc.) when keytar is unavailable.
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
- try {
47
- const val = await keytar.getPassword(SERVICE_NAME, key);
48
- if (val !== null) return val;
49
- } catch {
50
- // keytar unavailable (CI/headless) fall through to env var
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 using keytar
57
- * @param {string} key - The key of the secret to delete
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
- try {
62
- return await keytar.deletePassword(SERVICE_NAME, key);
63
- } catch (error) {
64
- console.error(`Error deleting secret for ${key}:`, error.message);
65
- return false;
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. Tries keytar first; falls back to env vars when
71
- * keytar is unavailable (e.g. GitHub Actions, Docker containers).
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
- try {
76
- const keytarSecrets = await keytar.findCredentials(SERVICE_NAME);
77
- if (keytarSecrets && keytarSecrets.length > 0) return keytarSecrets;
78
- } catch {
79
- // keytar unavailable (CI/headless) fall through to env vars
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
- // Build credential list from env vars
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 secrets = await getAllSecrets();
95
- if (secrets && secrets.length > 0) {
96
- const deletionPromises = secrets.map(
97
- async ({ account }) => await deleteSecret(account)
98
- );
99
- await Promise.all(deletionPromises);
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.2",
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": {