@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.
@@ -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
- // In CI/headless environments where keytar (OS keychain) is unavailable,
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 securely using keytar.
35
- * In CI/headless environments where keytar is unavailable, logs a warning
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 (!keytar) {
41
- console.warn(
42
- `Warning: System keychain is unavailable. Could not store '${key}'.`
43
- );
44
- console.warn(
45
- `In CI environments, set credentials via environment variables (e.g. BOLTIC_TOKEN, BOLTIC_ACCOUNT_ID).`
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. Tries keytar first; falls back to env vars
63
- * (BOLTIC_TOKEN, BOLTIC_ACCOUNT_ID, etc.) when keytar is unavailable.
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
- // keytar failed — fall through to env var
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 using keytar.
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 (!keytar) return false;
84
- try {
85
- return await keytar.deletePassword(SERVICE_NAME, key);
86
- } catch (error) {
87
- console.error(`Error deleting secret for ${key}:`, error.message);
88
- return false;
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. Tries keytar first; falls back to env vars when
94
- * keytar is unavailable (e.g. GitHub Actions, Docker containers).
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
- // keytar failed — fall through to env vars
131
+ // fall through
104
132
  }
105
133
  }
106
-
107
- // 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
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 secrets = await getAllSecrets();
120
- if (secrets && secrets.length > 0) {
121
- const deletionPromises = secrets.map(
122
- async ({ account }) => await deleteSecret(account)
123
- );
124
- 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);
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",
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": {