lsh-framework 1.3.0 → 1.3.2
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 +29 -0
- package/dist/cli.js +3 -0
- package/dist/commands/completion.js +381 -0
- package/dist/services/secrets/secrets.js +51 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -317,6 +317,35 @@ lsh --version
|
|
|
317
317
|
lsh self version
|
|
318
318
|
```
|
|
319
319
|
|
|
320
|
+
### Shell Completion (Optional but Recommended)
|
|
321
|
+
|
|
322
|
+
**New in v1.3.2:** Enable intelligent Tab completion for bash/zsh!
|
|
323
|
+
|
|
324
|
+
**Bash:**
|
|
325
|
+
```bash
|
|
326
|
+
# Add to ~/.bashrc
|
|
327
|
+
echo 'source <(lsh completion bash)' >> ~/.bashrc
|
|
328
|
+
source ~/.bashrc
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Zsh:**
|
|
332
|
+
```bash
|
|
333
|
+
# Quick setup
|
|
334
|
+
mkdir -p ~/.zsh/completions
|
|
335
|
+
lsh completion zsh > ~/.zsh/completions/_lsh
|
|
336
|
+
echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc
|
|
337
|
+
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc
|
|
338
|
+
source ~/.zshrc
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Now you can use Tab to:
|
|
342
|
+
- Complete command names: `lsh pu<Tab>` → `lsh push`
|
|
343
|
+
- Discover options: `lsh push <Tab>` → `-f --file -e --env --force -h --help`
|
|
344
|
+
- Complete environments: `lsh push --env <Tab>` → `dev staging production`
|
|
345
|
+
- Complete formats: `lsh list --format <Tab>` → `env json yaml toml export`
|
|
346
|
+
|
|
347
|
+
See [Shell Completion Guide](docs/features/SHELL_COMPLETION.md) for more details.
|
|
348
|
+
|
|
320
349
|
### Initial Setup
|
|
321
350
|
|
|
322
351
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Command } from 'commander';
|
|
|
7
7
|
import selfCommand from './commands/self.js';
|
|
8
8
|
import { registerInitCommands } from './commands/init.js';
|
|
9
9
|
import { registerDoctorCommands } from './commands/doctor.js';
|
|
10
|
+
import { registerCompletionCommands } from './commands/completion.js';
|
|
10
11
|
import { init_daemon } from './services/daemon/daemon.js';
|
|
11
12
|
import { init_supabase } from './services/supabase/supabase.js';
|
|
12
13
|
import { init_cron } from './services/cron/cron.js';
|
|
@@ -141,6 +142,8 @@ function findSimilarCommands(input, validCommands) {
|
|
|
141
142
|
await init_supabase(program);
|
|
142
143
|
await init_daemon(program);
|
|
143
144
|
await init_cron(program);
|
|
145
|
+
// Shell completion
|
|
146
|
+
registerCompletionCommands(program);
|
|
144
147
|
// Self-management commands
|
|
145
148
|
program.addCommand(selfCommand);
|
|
146
149
|
// Pre-parse check for unknown commands
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Completion Commands
|
|
3
|
+
* Generate shell completion scripts for bash and zsh
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate bash completion script
|
|
7
|
+
*/
|
|
8
|
+
function generateBashCompletion() {
|
|
9
|
+
return `# lsh bash completion script
|
|
10
|
+
# Source this file or add to ~/.bashrc:
|
|
11
|
+
# source <(lsh completion bash)
|
|
12
|
+
# Or save to completion directory:
|
|
13
|
+
# lsh completion bash > /etc/bash_completion.d/lsh
|
|
14
|
+
|
|
15
|
+
_lsh_completion() {
|
|
16
|
+
local cur prev words cword
|
|
17
|
+
_init_completion || return
|
|
18
|
+
|
|
19
|
+
local commands="help init doctor push pull list ls env key create sync status info get set delete supabase daemon cron self completion"
|
|
20
|
+
local global_opts="-V --version -v --verbose -d --debug -h --help"
|
|
21
|
+
|
|
22
|
+
# If we're completing the first word (command)
|
|
23
|
+
if [ $` + `{cword} -eq 1 ]; then
|
|
24
|
+
COMPREPLY=( $` + `(compgen -W "$` + `{commands} $` + `{global_opts}" -- "$` + `{cur}") )
|
|
25
|
+
return 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Get the command (first argument)
|
|
29
|
+
local command="$` + `{words[1]}"
|
|
30
|
+
|
|
31
|
+
case "$` + `{command}" in
|
|
32
|
+
get)
|
|
33
|
+
case "$` + `{prev}" in
|
|
34
|
+
-f|--file)
|
|
35
|
+
COMPREPLY=( $` + `(compgen -f -- "$` + `{cur}") )
|
|
36
|
+
return 0
|
|
37
|
+
;;
|
|
38
|
+
--format)
|
|
39
|
+
COMPREPLY=( $(compgen -W "env json yaml toml export" -- "$cur") )
|
|
40
|
+
return 0
|
|
41
|
+
;;
|
|
42
|
+
*)
|
|
43
|
+
local opts="-f --file --all --export --format --exact -h --help"
|
|
44
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
45
|
+
return 0
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
;;
|
|
49
|
+
set)
|
|
50
|
+
case "$prev" in
|
|
51
|
+
-f|--file)
|
|
52
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
53
|
+
return 0
|
|
54
|
+
;;
|
|
55
|
+
*)
|
|
56
|
+
local opts="-f --file --stdin -h --help"
|
|
57
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
58
|
+
return 0
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
;;
|
|
62
|
+
push|pull)
|
|
63
|
+
case "$prev" in
|
|
64
|
+
-f|--file)
|
|
65
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
66
|
+
return 0
|
|
67
|
+
;;
|
|
68
|
+
-e|--env)
|
|
69
|
+
COMPREPLY=( $(compgen -W "dev development staging production prod test" -- "$cur") )
|
|
70
|
+
return 0
|
|
71
|
+
;;
|
|
72
|
+
*)
|
|
73
|
+
local opts="-f --file -e --env --force -h --help"
|
|
74
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
75
|
+
return 0
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
;;
|
|
79
|
+
sync)
|
|
80
|
+
case "$prev" in
|
|
81
|
+
-f|--file)
|
|
82
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
83
|
+
return 0
|
|
84
|
+
;;
|
|
85
|
+
-e|--env)
|
|
86
|
+
COMPREPLY=( $(compgen -W "dev development staging production prod test" -- "$cur") )
|
|
87
|
+
return 0
|
|
88
|
+
;;
|
|
89
|
+
*)
|
|
90
|
+
local opts="-f --file -e --env --dry-run --legacy --load --force -h --help"
|
|
91
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
92
|
+
return 0
|
|
93
|
+
;;
|
|
94
|
+
esac
|
|
95
|
+
;;
|
|
96
|
+
list|ls)
|
|
97
|
+
case "$prev" in
|
|
98
|
+
-f|--file)
|
|
99
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
100
|
+
return 0
|
|
101
|
+
;;
|
|
102
|
+
--format)
|
|
103
|
+
COMPREPLY=( $(compgen -W "env json yaml toml export" -- "$cur") )
|
|
104
|
+
return 0
|
|
105
|
+
;;
|
|
106
|
+
*)
|
|
107
|
+
local opts="-f --file --keys-only --format --no-mask -h --help"
|
|
108
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
109
|
+
return 0
|
|
110
|
+
;;
|
|
111
|
+
esac
|
|
112
|
+
;;
|
|
113
|
+
env)
|
|
114
|
+
case "$prev" in
|
|
115
|
+
--format)
|
|
116
|
+
COMPREPLY=( $(compgen -W "env json yaml toml export" -- "$cur") )
|
|
117
|
+
return 0
|
|
118
|
+
;;
|
|
119
|
+
env)
|
|
120
|
+
COMPREPLY=( $(compgen -W "dev development staging production prod test" -- "$cur") )
|
|
121
|
+
return 0
|
|
122
|
+
;;
|
|
123
|
+
*)
|
|
124
|
+
local opts="--all-files --format -h --help"
|
|
125
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
126
|
+
return 0
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
;;
|
|
130
|
+
key)
|
|
131
|
+
local opts="--export -h --help"
|
|
132
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
133
|
+
return 0
|
|
134
|
+
;;
|
|
135
|
+
create)
|
|
136
|
+
case "$prev" in
|
|
137
|
+
-f|--file)
|
|
138
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
139
|
+
return 0
|
|
140
|
+
;;
|
|
141
|
+
*)
|
|
142
|
+
local opts="-f --file -t --template -h --help"
|
|
143
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
144
|
+
return 0
|
|
145
|
+
;;
|
|
146
|
+
esac
|
|
147
|
+
;;
|
|
148
|
+
delete)
|
|
149
|
+
case "$prev" in
|
|
150
|
+
-f|--file)
|
|
151
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
152
|
+
return 0
|
|
153
|
+
;;
|
|
154
|
+
*)
|
|
155
|
+
local opts="-f --file -y --yes -h --help"
|
|
156
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
157
|
+
return 0
|
|
158
|
+
;;
|
|
159
|
+
esac
|
|
160
|
+
;;
|
|
161
|
+
status|info)
|
|
162
|
+
case "$prev" in
|
|
163
|
+
-f|--file)
|
|
164
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
165
|
+
return 0
|
|
166
|
+
;;
|
|
167
|
+
-e|--env)
|
|
168
|
+
COMPREPLY=( $(compgen -W "dev development staging production prod test" -- "$cur") )
|
|
169
|
+
return 0
|
|
170
|
+
;;
|
|
171
|
+
*)
|
|
172
|
+
local opts="-f --file -e --env -h --help"
|
|
173
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
174
|
+
return 0
|
|
175
|
+
;;
|
|
176
|
+
esac
|
|
177
|
+
;;
|
|
178
|
+
init|doctor)
|
|
179
|
+
local opts="-h --help"
|
|
180
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
181
|
+
return 0
|
|
182
|
+
;;
|
|
183
|
+
completion)
|
|
184
|
+
local opts="bash zsh -h --help"
|
|
185
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
186
|
+
return 0
|
|
187
|
+
;;
|
|
188
|
+
supabase|daemon|cron|self)
|
|
189
|
+
# These have subcommands, just complete help for now
|
|
190
|
+
local opts="-h --help"
|
|
191
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
192
|
+
return 0
|
|
193
|
+
;;
|
|
194
|
+
*)
|
|
195
|
+
# Default: complete global options
|
|
196
|
+
COMPREPLY=( $(compgen -W "$global_opts" -- "$cur") )
|
|
197
|
+
return 0
|
|
198
|
+
;;
|
|
199
|
+
esac
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
complete -F _lsh_completion lsh
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Generate zsh completion script
|
|
207
|
+
*/
|
|
208
|
+
function generateZshCompletion() {
|
|
209
|
+
return `#compdef lsh
|
|
210
|
+
# lsh zsh completion script
|
|
211
|
+
# Install to: ~/.zsh/completions/_lsh
|
|
212
|
+
# Or source directly:
|
|
213
|
+
# source <(lsh completion zsh)
|
|
214
|
+
# Make sure ~/.zsh/completions is in your fpath:
|
|
215
|
+
# fpath=(~/.zsh/completions $fpath)
|
|
216
|
+
# autoload -Uz compinit && compinit
|
|
217
|
+
|
|
218
|
+
_lsh() {
|
|
219
|
+
local -a commands
|
|
220
|
+
local -a global_opts
|
|
221
|
+
local state line
|
|
222
|
+
|
|
223
|
+
global_opts=(
|
|
224
|
+
'(-V --version)'{-V,--version}'[Output version number]'
|
|
225
|
+
'(-v --verbose)'{-v,--verbose}'[Verbose output]'
|
|
226
|
+
'(-d --debug)'{-d,--debug}'[Debug mode]'
|
|
227
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
commands=(
|
|
231
|
+
'help:Show detailed help'
|
|
232
|
+
'init:Interactive setup wizard'
|
|
233
|
+
'doctor:Health check and troubleshooting'
|
|
234
|
+
'push:Push local .env to encrypted cloud storage'
|
|
235
|
+
'pull:Pull .env from encrypted cloud storage'
|
|
236
|
+
'list:List secrets in the current local .env file'
|
|
237
|
+
'ls:List secrets (alias for list)'
|
|
238
|
+
'env:List all stored environments'
|
|
239
|
+
'key:Generate a new encryption key'
|
|
240
|
+
'create:Create a new .env file'
|
|
241
|
+
'sync:Automatically set up and synchronize secrets'
|
|
242
|
+
'status:Get detailed secrets status'
|
|
243
|
+
'info:Show current directory context'
|
|
244
|
+
'get:Get a specific secret value'
|
|
245
|
+
'set:Set a specific secret value'
|
|
246
|
+
'delete:Delete .env file'
|
|
247
|
+
'completion:Generate shell completion scripts'
|
|
248
|
+
'supabase:Supabase database management'
|
|
249
|
+
'daemon:LSH daemon management'
|
|
250
|
+
'cron:Cron job management'
|
|
251
|
+
'self:Manage and update LSH'
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
_arguments -C \
|
|
255
|
+
$global_opts \
|
|
256
|
+
'1: :->command' \
|
|
257
|
+
'*:: :->args'
|
|
258
|
+
|
|
259
|
+
case $state in
|
|
260
|
+
command)
|
|
261
|
+
_describe -t commands 'lsh commands' commands
|
|
262
|
+
;;
|
|
263
|
+
args)
|
|
264
|
+
case $line[1] in
|
|
265
|
+
get)
|
|
266
|
+
_arguments \
|
|
267
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
268
|
+
'--all[Get all secrets]' \
|
|
269
|
+
'--export[Output in export format]' \
|
|
270
|
+
'--format[Output format]:format:(env json yaml toml export)' \
|
|
271
|
+
'--exact[Require exact key match]' \
|
|
272
|
+
'(-h --help)'{-h,--help}'[Display help]' \
|
|
273
|
+
'1:key:'
|
|
274
|
+
;;
|
|
275
|
+
set)
|
|
276
|
+
_arguments \
|
|
277
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
278
|
+
'--stdin[Read from stdin]' \
|
|
279
|
+
'(-h --help)'{-h,--help}'[Display help]' \
|
|
280
|
+
'1:key:' \
|
|
281
|
+
'2:value:'
|
|
282
|
+
;;
|
|
283
|
+
push|pull)
|
|
284
|
+
_arguments \
|
|
285
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
286
|
+
'(-e --env)'{-e,--env}'[Environment name]:environment:(dev development staging production prod test)' \
|
|
287
|
+
'--force[Force operation]' \
|
|
288
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
289
|
+
;;
|
|
290
|
+
sync)
|
|
291
|
+
_arguments \
|
|
292
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
293
|
+
'(-e --env)'{-e,--env}'[Environment name]:environment:(dev development staging production prod test)' \
|
|
294
|
+
'--dry-run[Show what would be done]' \
|
|
295
|
+
'--legacy[Use legacy sync mode]' \
|
|
296
|
+
'--load[Output eval-able export commands]' \
|
|
297
|
+
'--force[Force sync]' \
|
|
298
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
299
|
+
;;
|
|
300
|
+
list|ls)
|
|
301
|
+
_arguments \
|
|
302
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
303
|
+
'--keys-only[Show only keys]' \
|
|
304
|
+
'--format[Output format]:format:(env json yaml toml export)' \
|
|
305
|
+
'--no-mask[Show full values]' \
|
|
306
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
307
|
+
;;
|
|
308
|
+
env)
|
|
309
|
+
_arguments \
|
|
310
|
+
'--all-files[List all tracked files]' \
|
|
311
|
+
'--format[Output format]:format:(env json yaml toml export)' \
|
|
312
|
+
'(-h --help)'{-h,--help}'[Display help]' \
|
|
313
|
+
'1:environment:(dev development staging production prod test)'
|
|
314
|
+
;;
|
|
315
|
+
key)
|
|
316
|
+
_arguments \
|
|
317
|
+
'--export[Output in export format]' \
|
|
318
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
319
|
+
;;
|
|
320
|
+
create)
|
|
321
|
+
_arguments \
|
|
322
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
323
|
+
'(-t --template)'{-t,--template}'[Create with template]' \
|
|
324
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
325
|
+
;;
|
|
326
|
+
delete)
|
|
327
|
+
_arguments \
|
|
328
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
329
|
+
'(-y --yes)'{-y,--yes}'[Skip confirmation]' \
|
|
330
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
331
|
+
;;
|
|
332
|
+
status|info)
|
|
333
|
+
_arguments \
|
|
334
|
+
'(-f --file)'{-f,--file}'[Path to .env file]:file:_files' \
|
|
335
|
+
'(-e --env)'{-e,--env}'[Environment name]:environment:(dev development staging production prod test)' \
|
|
336
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
337
|
+
;;
|
|
338
|
+
completion)
|
|
339
|
+
_arguments \
|
|
340
|
+
'(-h --help)'{-h,--help}'[Display help]' \
|
|
341
|
+
'1:shell:(bash zsh)'
|
|
342
|
+
;;
|
|
343
|
+
init|doctor|help)
|
|
344
|
+
_arguments \
|
|
345
|
+
'(-h --help)'{-h,--help}'[Display help]'
|
|
346
|
+
;;
|
|
347
|
+
esac
|
|
348
|
+
;;
|
|
349
|
+
esac
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
_lsh "$@"
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Register completion commands
|
|
357
|
+
*/
|
|
358
|
+
export function registerCompletionCommands(program) {
|
|
359
|
+
program
|
|
360
|
+
.command('completion <shell>')
|
|
361
|
+
.description('Generate shell completion script (bash or zsh)')
|
|
362
|
+
.action((shell) => {
|
|
363
|
+
const shellLower = shell.toLowerCase();
|
|
364
|
+
if (shellLower === 'bash') {
|
|
365
|
+
console.log(generateBashCompletion());
|
|
366
|
+
}
|
|
367
|
+
else if (shellLower === 'zsh') {
|
|
368
|
+
console.log(generateZshCompletion());
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
console.error(`❌ Unknown shell: ${shell}`);
|
|
372
|
+
console.error('Supported shells: bash, zsh');
|
|
373
|
+
console.error('');
|
|
374
|
+
console.error('Usage:');
|
|
375
|
+
console.error(' lsh completion bash > ~/.lsh-completion.bash');
|
|
376
|
+
console.error(' lsh completion zsh > ~/.zsh/completions/_lsh');
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
export default registerCompletionCommands;
|
|
@@ -543,6 +543,42 @@ API_KEY=
|
|
|
543
543
|
process.exit(1);
|
|
544
544
|
}
|
|
545
545
|
});
|
|
546
|
+
/**
|
|
547
|
+
* Detect if file should use 'export' prefix based on file type
|
|
548
|
+
*/
|
|
549
|
+
function shouldUseExport(filePath) {
|
|
550
|
+
const filename = path.basename(filePath);
|
|
551
|
+
const ext = path.extname(filePath);
|
|
552
|
+
// Shell script files - use export
|
|
553
|
+
if (['.sh', '.bash', '.zsh'].includes(ext)) {
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
// Shell profile/rc files - use export
|
|
557
|
+
if (['.bashrc', '.zshrc', '.profile', '.bash_profile', '.zprofile'].includes(filename)) {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
// .envrc files (direnv) - use export
|
|
561
|
+
if (filename === '.envrc' || filename.endsWith('.envrc')) {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
// .env files - do NOT use export
|
|
565
|
+
if (filename === '.env' || filename.startsWith('.env.')) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
// Default: no export (safest for most env files)
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Format a line based on file type
|
|
573
|
+
*/
|
|
574
|
+
function formatEnvLine(key, value, filePath) {
|
|
575
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
576
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
577
|
+
const useExport = shouldUseExport(filePath);
|
|
578
|
+
return useExport
|
|
579
|
+
? `export ${key}=${quotedValue}`
|
|
580
|
+
: `${key}=${quotedValue}`;
|
|
581
|
+
}
|
|
546
582
|
/**
|
|
547
583
|
* Set a single secret value
|
|
548
584
|
*/
|
|
@@ -563,12 +599,11 @@ API_KEY=
|
|
|
563
599
|
newLines.push(line);
|
|
564
600
|
continue;
|
|
565
601
|
}
|
|
566
|
-
|
|
602
|
+
// Match both with and without export
|
|
603
|
+
const match = line.match(/^(?:export\s+)?([^=]+)=(.*)$/);
|
|
567
604
|
if (match && match[1].trim() === key) {
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
571
|
-
newLines.push(`${key}=${quotedValue}`);
|
|
605
|
+
// Use appropriate format for this file type
|
|
606
|
+
newLines.push(formatEnvLine(key, value, envPath));
|
|
572
607
|
found = true;
|
|
573
608
|
}
|
|
574
609
|
else {
|
|
@@ -579,9 +614,8 @@ API_KEY=
|
|
|
579
614
|
}
|
|
580
615
|
// If key wasn't found, append it
|
|
581
616
|
if (!found) {
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
content = content.trimRight() + `\n${key}=${quotedValue}\n`;
|
|
617
|
+
const formattedLine = formatEnvLine(key, value, envPath);
|
|
618
|
+
content = content.trimRight() + `\n${formattedLine}\n`;
|
|
585
619
|
}
|
|
586
620
|
fs.writeFileSync(envPath, content, 'utf8');
|
|
587
621
|
console.log(`✅ Set ${key}`);
|
|
@@ -638,8 +672,8 @@ API_KEY=
|
|
|
638
672
|
// Skip comments and empty lines
|
|
639
673
|
if (trimmed.startsWith('#') || !trimmed)
|
|
640
674
|
continue;
|
|
641
|
-
// Parse KEY=VALUE format
|
|
642
|
-
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
675
|
+
// Parse KEY=VALUE format (with or without export)
|
|
676
|
+
const match = trimmed.match(/^(?:export\s+)?([^=]+)=(.*)$/);
|
|
643
677
|
if (!match) {
|
|
644
678
|
errors.push(`Invalid format: ${trimmed}`);
|
|
645
679
|
continue;
|
|
@@ -671,15 +705,14 @@ API_KEY=
|
|
|
671
705
|
newLines.push(line);
|
|
672
706
|
continue;
|
|
673
707
|
}
|
|
674
|
-
|
|
708
|
+
// Match both with and without export
|
|
709
|
+
const match = line.match(/^(?:export\s+)?([^=]+)=(.*)$/);
|
|
675
710
|
if (match) {
|
|
676
711
|
const key = match[1].trim();
|
|
677
712
|
if (newKeys.has(key)) {
|
|
678
|
-
// Update existing key
|
|
713
|
+
// Update existing key with appropriate format
|
|
679
714
|
const value = newKeys.get(key);
|
|
680
|
-
|
|
681
|
-
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
682
|
-
newLines.push(`${key}=${quotedValue}`);
|
|
715
|
+
newLines.push(formatEnvLine(key, value, envPath));
|
|
683
716
|
newKeys.delete(key); // Mark as processed
|
|
684
717
|
hasContent = true;
|
|
685
718
|
}
|
|
@@ -696,13 +729,12 @@ API_KEY=
|
|
|
696
729
|
}
|
|
697
730
|
// Add new keys that weren't in the existing file
|
|
698
731
|
for (const [key, value] of newKeys.entries()) {
|
|
699
|
-
const
|
|
700
|
-
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
732
|
+
const formattedLine = formatEnvLine(key, value, envPath);
|
|
701
733
|
if (hasContent) {
|
|
702
|
-
newLines.push(
|
|
734
|
+
newLines.push(formattedLine);
|
|
703
735
|
}
|
|
704
736
|
else {
|
|
705
|
-
newLines.push(
|
|
737
|
+
newLines.push(formattedLine);
|
|
706
738
|
hasContent = true;
|
|
707
739
|
}
|
|
708
740
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Simple, cross-platform encrypted secrets manager with automatic sync and multi-environment support. Just run lsh sync and start managing your secrets.",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
6
|
"bin": {
|