@lkangd/cc-env 1.1.2 → 1.2.1
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 +110 -0
- package/README.zh.md +110 -0
- package/dist/cli.js +68 -6
- package/dist/commands/completion.js +60 -0
- package/dist/commands/doctor.js +73 -0
- package/dist/commands/preset/edit.js +16 -11
- package/dist/commands/preset/rename.js +16 -0
- package/dist/commands/run.js +9 -1
- package/dist/ink/preset-edit-app.js +112 -0
- package/package.json +7 -4
- package/dist/commands/debug.js +0 -17
- package/dist/commands/preset/list.js +0 -16
- package/dist/core/lock.js +0 -25
- package/dist/ink/preset-list-app.js +0 -27
- package/dist/services/config-service.js +0 -26
- package/dist/services/runtime-env-service.js +0 -13
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./statics/logo.png" alt="cc-env" width="320" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-env"><img src="https://img.shields.io/npm/v/@lkangd/cc-env.svg" alt="npm version" /></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-env"><img src="https://img.shields.io/npm/dm/@lkangd/cc-env.svg" alt="npm downloads" /></a>
|
|
8
|
+
<a href="https://github.com/lkangd/cc-env/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@lkangd/cc-env.svg" alt="license" /></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">Manage runtime environment variables for <a href="https://claude.ai/code">Claude Code</a></p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="./README.md">English</a> | <a href="./README.zh.md">简体中文</a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
`cc-env` is a CLI tool that lets you define, switch, and restore environment variable configurations for Claude Code — per project or via reusable presets. No more manually editing `settings.json` or juggling `.env` files across workspaces.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### via npm
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @lkangd/cc-env
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Requires Node.js `>=20.19.2`.
|
|
32
|
+
|
|
33
|
+
### via Homebrew
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
brew tap lkangd/tap
|
|
37
|
+
brew install cc-env
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Initialize cc-env in your project
|
|
44
|
+
cc-env init
|
|
45
|
+
|
|
46
|
+
# 2. Create a preset with your environment variables
|
|
47
|
+
cc-env create
|
|
48
|
+
|
|
49
|
+
# 3. Run Claude Code with the preset applied
|
|
50
|
+
cc-env run
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
| Command | Description |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `cc-env init` | Initialize cc-env for the current project |
|
|
58
|
+
| `cc-env run [args...]` | Run Claude Code with merged environment variables |
|
|
59
|
+
| `cc-env restore` | Restore environment variables from a previous snapshot |
|
|
60
|
+
| `cc-env show` | List and view all saved presets |
|
|
61
|
+
| `cc-env create` | Create a new environment preset |
|
|
62
|
+
| `cc-env edit <name>` | Edit an existing preset |
|
|
63
|
+
| `cc-env rename <from> <to>` | Rename a preset |
|
|
64
|
+
| `cc-env delete` | Delete a saved preset |
|
|
65
|
+
| `cc-env doctor` | Check system health and configuration |
|
|
66
|
+
| `cc-env completion` | Generate shell completion script |
|
|
67
|
+
|
|
68
|
+
## Global Options
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
--verbose Enable verbose output
|
|
72
|
+
--quiet Suppress non-essential output
|
|
73
|
+
--no-interactive Disable interactive prompts (equivalent to -y)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Shell Completion
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# bash
|
|
80
|
+
cc-env completion bash >> ~/.bashrc
|
|
81
|
+
|
|
82
|
+
# zsh
|
|
83
|
+
cc-env completion zsh >> ~/.zshrc
|
|
84
|
+
|
|
85
|
+
# fish
|
|
86
|
+
cc-env completion fish >> ~/.config/fish/completions/cc-env.fish
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Install dependencies
|
|
93
|
+
npm install
|
|
94
|
+
|
|
95
|
+
# Run in dev mode
|
|
96
|
+
npm run dev
|
|
97
|
+
|
|
98
|
+
# Build
|
|
99
|
+
npm run build
|
|
100
|
+
|
|
101
|
+
# Run tests
|
|
102
|
+
npm test
|
|
103
|
+
|
|
104
|
+
# Run tests with coverage
|
|
105
|
+
npm run test:coverage
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
ISC © [lkangd](https://github.com/lkangd)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./statics/logo.png" alt="cc-env" width="320" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-env"><img src="https://img.shields.io/npm/v/@lkangd/cc-env.svg" alt="npm version" /></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-env"><img src="https://img.shields.io/npm/dm/@lkangd/cc-env.svg" alt="npm downloads" /></a>
|
|
8
|
+
<a href="https://github.com/lkangd/cc-env/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@lkangd/cc-env.svg" alt="license" /></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">为 <a href="https://claude.ai/code">Claude Code</a> 管理运行时环境变量</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="./README.md">English</a> | <a href="./README.zh.md">简体中文</a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 概述
|
|
20
|
+
|
|
21
|
+
`cc-env` 是一个 CLI 工具,让你可以为 Claude Code 定义、切换和恢复环境变量配置——支持按项目配置或使用可复用的预设。不再需要手动编辑 `settings.json` 或在不同工作区之间切换 `.env` 文件。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
### 通过 npm
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @lkangd/cc-env
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
需要 Node.js `>=20.19.2`。
|
|
32
|
+
|
|
33
|
+
### 通过 Homebrew
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
brew tap lkangd/tap
|
|
37
|
+
brew install cc-env
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 快速开始
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. 在项目中初始化 cc-env
|
|
44
|
+
cc-env init
|
|
45
|
+
|
|
46
|
+
# 2. 创建一个包含环境变量的预设
|
|
47
|
+
cc-env create
|
|
48
|
+
|
|
49
|
+
# 3. 使用预设运行 Claude Code
|
|
50
|
+
cc-env run
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 命令
|
|
54
|
+
|
|
55
|
+
| 命令 | 说明 |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `cc-env init` | 为当前项目初始化 cc-env |
|
|
58
|
+
| `cc-env run [args...]` | 使用合并后的环境变量运行 Claude Code |
|
|
59
|
+
| `cc-env restore` | 从之前的快照恢复环境变量 |
|
|
60
|
+
| `cc-env show` | 列出并查看所有保存的预设 |
|
|
61
|
+
| `cc-env create` | 创建新的环境变量预设 |
|
|
62
|
+
| `cc-env edit <name>` | 编辑现有预设 |
|
|
63
|
+
| `cc-env rename <from> <to>` | 重命名预设 |
|
|
64
|
+
| `cc-env delete` | 删除保存的预设 |
|
|
65
|
+
| `cc-env doctor` | 检查系统健康状况和配置 |
|
|
66
|
+
| `cc-env completion` | 生成 shell 补全脚本 |
|
|
67
|
+
|
|
68
|
+
## 全局选项
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
--verbose 启用详细输出
|
|
72
|
+
--quiet 抑制非必要输出
|
|
73
|
+
--no-interactive 禁用交互式提示(等同于 -y)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Shell 补全
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# bash
|
|
80
|
+
cc-env completion bash >> ~/.bashrc
|
|
81
|
+
|
|
82
|
+
# zsh
|
|
83
|
+
cc-env completion zsh >> ~/.zshrc
|
|
84
|
+
|
|
85
|
+
# fish
|
|
86
|
+
cc-env completion fish >> ~/.config/fish/completions/cc-env.fish
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 开发
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# 安装依赖
|
|
93
|
+
npm install
|
|
94
|
+
|
|
95
|
+
# 开发模式运行
|
|
96
|
+
npm run dev
|
|
97
|
+
|
|
98
|
+
# 构建
|
|
99
|
+
npm run build
|
|
100
|
+
|
|
101
|
+
# 运行测试
|
|
102
|
+
npm test
|
|
103
|
+
|
|
104
|
+
# 运行测试并生成覆盖率报告
|
|
105
|
+
npm run test:coverage
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 许可证
|
|
109
|
+
|
|
110
|
+
ISC © [lkangd](https://github.com/lkangd)
|
package/dist/cli.js
CHANGED
|
@@ -5,14 +5,19 @@ import { join } from 'node:path';
|
|
|
5
5
|
import figlet from 'figlet';
|
|
6
6
|
import gradient from 'gradient-string';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
|
+
import packageJson from '../package.json' with { type: 'json' };
|
|
8
9
|
const h = React.createElement;
|
|
9
10
|
import { createInitCommand } from './commands/init.js';
|
|
10
11
|
import { createPresetCreateCommand } from './commands/preset/create.js';
|
|
11
12
|
import { createDeletePresetCommand } from './commands/preset/delete.js';
|
|
13
|
+
import { createEditPresetCommand } from './commands/preset/edit.js';
|
|
14
|
+
import { createRenamePresetCommand } from './commands/preset/rename.js';
|
|
12
15
|
import { PresetDeleteApp } from './ink/preset-delete-app.js';
|
|
16
|
+
import { PresetEditApp } from './ink/preset-edit-app.js';
|
|
13
17
|
import { createShowPresetsCommand } from './commands/preset/show.js';
|
|
14
18
|
import { createRestoreCommand } from './commands/restore.js';
|
|
15
19
|
import { createRunCommand } from './commands/run.js';
|
|
20
|
+
import { runDoctorCommand } from './commands/doctor.js';
|
|
16
21
|
import { findClaudeExecutable } from './core/find-claude.js';
|
|
17
22
|
import { InitApp } from './ink/init-app.js';
|
|
18
23
|
import { renderEnvSummary } from './ink/summary.js';
|
|
@@ -32,7 +37,13 @@ import { createProjectStateService } from './services/project-state-service.js';
|
|
|
32
37
|
import { createSettingsEnvService } from './services/settings-env-service.js';
|
|
33
38
|
import { createShellEnvService } from './services/shell-env-service.js';
|
|
34
39
|
const program = new Command();
|
|
35
|
-
program
|
|
40
|
+
program
|
|
41
|
+
.name('cc-env')
|
|
42
|
+
.description('Manage runtime environment variables for Claude Code')
|
|
43
|
+
.version(packageJson.version)
|
|
44
|
+
.option('--verbose', 'Enable verbose output')
|
|
45
|
+
.option('--quiet', 'Suppress non-essential output')
|
|
46
|
+
.option('--no-interactive', 'Disable interactive prompts (equivalent to -y)');
|
|
36
47
|
const homeDir = process.env.HOME ?? process.cwd();
|
|
37
48
|
const cwd = process.cwd();
|
|
38
49
|
const settingsPath = join(cwd, 'settings.json');
|
|
@@ -111,6 +122,7 @@ program
|
|
|
111
122
|
.description('Run claude with merged environment variables')
|
|
112
123
|
.option('--dry-run', 'Preview the merged env without executing')
|
|
113
124
|
.option('-y, --yes', 'Auto-select the default preset without interactive prompts')
|
|
125
|
+
.option('--json', 'Output as JSON (only with --dry-run)')
|
|
114
126
|
.action((args, options) => {
|
|
115
127
|
const rawArgs = args ?? [];
|
|
116
128
|
return createRunCommand({
|
|
@@ -136,6 +148,7 @@ program
|
|
|
136
148
|
args: rawArgs,
|
|
137
149
|
dryRun: options.dryRun ?? false,
|
|
138
150
|
yes: options.yes ?? false,
|
|
151
|
+
json: options.json ?? false,
|
|
139
152
|
cwd
|
|
140
153
|
});
|
|
141
154
|
});
|
|
@@ -186,14 +199,19 @@ program
|
|
|
186
199
|
program
|
|
187
200
|
.command('show')
|
|
188
201
|
.description('List and view all presets')
|
|
189
|
-
.
|
|
202
|
+
.option('--json', 'Output as JSON')
|
|
203
|
+
.action((options) => createShowPresetsCommand({
|
|
190
204
|
presetService,
|
|
191
205
|
projectEnvService,
|
|
192
206
|
renderShow: async (presets) => {
|
|
207
|
+
if (options.json) {
|
|
208
|
+
process.stdout.write(JSON.stringify(presets, null, 2) + '\n');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
193
211
|
const app = render(h(PresetShowApp, { presets }));
|
|
194
212
|
await app.waitUntilExit();
|
|
195
213
|
}
|
|
196
|
-
}));
|
|
214
|
+
})());
|
|
197
215
|
program
|
|
198
216
|
.command('delete')
|
|
199
217
|
.description('Delete a saved preset')
|
|
@@ -235,14 +253,58 @@ program
|
|
|
235
253
|
return result;
|
|
236
254
|
}
|
|
237
255
|
})({ cwd }));
|
|
256
|
+
program
|
|
257
|
+
.command('doctor')
|
|
258
|
+
.description('Check system health and configuration')
|
|
259
|
+
.option('--json', 'Output as JSON')
|
|
260
|
+
.action((options) => runDoctorCommand({ cwd, json: options.json }));
|
|
261
|
+
program
|
|
262
|
+
.command('edit <name>')
|
|
263
|
+
.description('Edit an existing preset')
|
|
264
|
+
.action((name) => createEditPresetCommand({
|
|
265
|
+
presetService,
|
|
266
|
+
renderEdit: async (preset) => {
|
|
267
|
+
let result;
|
|
268
|
+
const app = render(h(PresetEditApp, {
|
|
269
|
+
name: preset.name,
|
|
270
|
+
env: preset.env,
|
|
271
|
+
onSubmit: (value) => {
|
|
272
|
+
result = value;
|
|
273
|
+
}
|
|
274
|
+
}));
|
|
275
|
+
await app.waitUntilExit();
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
})({ name }));
|
|
279
|
+
program
|
|
280
|
+
.command('rename <from> <to>')
|
|
281
|
+
.description('Rename a preset')
|
|
282
|
+
.action((from, to) => createRenamePresetCommand({ presetService })({ from, to }));
|
|
283
|
+
program
|
|
284
|
+
.command('completion')
|
|
285
|
+
.description('Generate shell completion script')
|
|
286
|
+
.option('--shell <shell>', 'Shell type (bash, zsh, fish)', 'bash')
|
|
287
|
+
.action(async (options) => {
|
|
288
|
+
const { generateCompletion } = await import('./commands/completion.js');
|
|
289
|
+
process.stdout.write(generateCompletion(options.shell));
|
|
290
|
+
});
|
|
238
291
|
function printBanner() {
|
|
239
292
|
const banner = figlet.textSync('CC ENV', { font: 'ANSI Shadow' });
|
|
240
293
|
const line = '─'.repeat(48);
|
|
241
294
|
const styled = gradient(['#00d2ff', '#7b2ff7', '#ff0080'])(banner);
|
|
242
295
|
process.stderr.write(`\n${styled}\x1b[2m\n${line}\x1b[0m\n\n`);
|
|
243
296
|
}
|
|
244
|
-
program.hook('preAction', () => {
|
|
245
|
-
|
|
297
|
+
program.hook('preAction', (thisCommand) => {
|
|
298
|
+
const opts = program.opts();
|
|
299
|
+
if (!opts.quiet)
|
|
300
|
+
printBanner();
|
|
301
|
+
// propagate --no-interactive as -y to subcommands
|
|
302
|
+
const globalOpts = program.opts();
|
|
303
|
+
if (globalOpts.interactive === false) {
|
|
304
|
+
const subOpts = thisCommand.opts();
|
|
305
|
+
if ('yes' in subOpts)
|
|
306
|
+
thisCommand.setOptionValue('yes', true);
|
|
307
|
+
}
|
|
246
308
|
});
|
|
247
309
|
program.parseAsync(process.argv).catch((error) => {
|
|
248
310
|
if (error instanceof CliError) {
|
|
@@ -252,7 +314,7 @@ program.parseAsync(process.argv).catch((error) => {
|
|
|
252
314
|
}
|
|
253
315
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
254
316
|
const { code, message } = error;
|
|
255
|
-
if (code === 'commander.helpDisplayed') {
|
|
317
|
+
if (code === 'commander.helpDisplayed' || code === 'commander.version') {
|
|
256
318
|
process.exitCode = 0;
|
|
257
319
|
return;
|
|
258
320
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const COMMANDS = ['run', 'init', 'restore', 'show', 'delete', 'create', 'doctor', 'completion', '--help', '--version'];
|
|
2
|
+
export function generateCompletion(shell) {
|
|
3
|
+
switch (shell) {
|
|
4
|
+
case 'zsh':
|
|
5
|
+
return generateZsh();
|
|
6
|
+
case 'fish':
|
|
7
|
+
return generateFish();
|
|
8
|
+
default:
|
|
9
|
+
return generateBash();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function generateBash() {
|
|
13
|
+
return `# cc-env bash completion
|
|
14
|
+
# Add to ~/.bashrc: eval "$(cc-env completion --shell bash)"
|
|
15
|
+
_cc_env_completions() {
|
|
16
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
17
|
+
local commands="${COMMANDS.join(' ')}"
|
|
18
|
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
19
|
+
}
|
|
20
|
+
complete -F _cc_env_completions cc-env
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
function generateZsh() {
|
|
24
|
+
const cmds = COMMANDS.filter((c) => !c.startsWith('-'));
|
|
25
|
+
const cmdList = cmds.map((c) => ` '${c}'`).join('\n');
|
|
26
|
+
return `# cc-env zsh completion
|
|
27
|
+
# Add to ~/.zshrc: eval "$(cc-env completion --shell zsh)"
|
|
28
|
+
_cc_env() {
|
|
29
|
+
local -a commands
|
|
30
|
+
commands=(
|
|
31
|
+
${cmdList}
|
|
32
|
+
)
|
|
33
|
+
_describe 'command' commands
|
|
34
|
+
}
|
|
35
|
+
compdef _cc_env cc-env
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
function generateFish() {
|
|
39
|
+
const cmds = [
|
|
40
|
+
['run', 'Run claude with merged environment variables'],
|
|
41
|
+
['init', 'Initialize cc-env for the current project'],
|
|
42
|
+
['restore', 'Restore environment variables from a previous snapshot'],
|
|
43
|
+
['show', 'List and view all presets'],
|
|
44
|
+
['delete', 'Delete a saved preset'],
|
|
45
|
+
['create', 'Create a new environment preset'],
|
|
46
|
+
['doctor', 'Check system health and configuration'],
|
|
47
|
+
['completion', 'Generate shell completion script'],
|
|
48
|
+
];
|
|
49
|
+
const lines = cmds.map(([cmd, desc]) => `complete -c cc-env -f -n '__fish_use_subcommand' -a '${cmd}' -d '${desc}'`);
|
|
50
|
+
return `# cc-env fish completion
|
|
51
|
+
# Add to fish config: cc-env completion --shell fish | source
|
|
52
|
+
${lines.join('\n')}
|
|
53
|
+
complete -c cc-env -l help -d 'Show help'
|
|
54
|
+
complete -c cc-env -l version -d 'Show version'
|
|
55
|
+
complete -c cc-env -l json -d 'Output as JSON'
|
|
56
|
+
complete -c cc-env -l quiet -d 'Suppress non-essential output'
|
|
57
|
+
complete -c cc-env -l verbose -d 'Enable verbose output'
|
|
58
|
+
complete -c cc-env -l no-interactive -d 'Disable interactive prompts'
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { access, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findClaudeExecutable } from '../core/find-claude.js';
|
|
4
|
+
import { resolveGlobalRoot } from '../core/paths.js';
|
|
5
|
+
async function exists(p) {
|
|
6
|
+
try {
|
|
7
|
+
await access(p);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function checkGlobalRoot(globalRoot) {
|
|
15
|
+
const ok = await exists(globalRoot);
|
|
16
|
+
return { label: 'Global root (~/.cc-env)', ok, detail: globalRoot };
|
|
17
|
+
}
|
|
18
|
+
async function checkPresetsDir(globalRoot) {
|
|
19
|
+
const dir = join(globalRoot, 'presets');
|
|
20
|
+
const ok = await exists(dir);
|
|
21
|
+
if (!ok)
|
|
22
|
+
return { label: 'Presets directory', ok: false, detail: dir };
|
|
23
|
+
const entries = await readdir(dir).catch(() => []);
|
|
24
|
+
const count = entries.filter((e) => e.endsWith('.json')).length;
|
|
25
|
+
return { label: 'Presets directory', ok: true, detail: `${dir} (${count} preset${count === 1 ? '' : 's'})` };
|
|
26
|
+
}
|
|
27
|
+
async function checkClaudeExecutable() {
|
|
28
|
+
try {
|
|
29
|
+
const path = findClaudeExecutable();
|
|
30
|
+
return { label: 'Claude executable', ok: true, detail: path };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { label: 'Claude executable', ok: false, detail: 'not found — run: npm install -g @anthropic-ai/claude-code' };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function checkProjectEnv(cwd) {
|
|
37
|
+
const path = join(cwd, '.cc-env', 'env.json');
|
|
38
|
+
const ok = await exists(path);
|
|
39
|
+
return { label: 'Project env (.cc-env/env.json)', ok, detail: ok ? path : 'not initialized — run: cc-env init' };
|
|
40
|
+
}
|
|
41
|
+
function renderCheck(result, json) {
|
|
42
|
+
if (json)
|
|
43
|
+
return '';
|
|
44
|
+
const icon = result.ok ? '\x1b[32m✔\x1b[0m' : '\x1b[31m✘\x1b[0m';
|
|
45
|
+
const detail = result.detail ? `\x1b[2m ${result.detail}\x1b[0m` : '';
|
|
46
|
+
return ` ${icon} ${result.label}${detail ? '\n' + detail : ''}`;
|
|
47
|
+
}
|
|
48
|
+
export async function runDoctorCommand({ cwd, json = false, stdout = process.stdout, }) {
|
|
49
|
+
const globalRoot = resolveGlobalRoot();
|
|
50
|
+
const checks = await Promise.all([
|
|
51
|
+
checkGlobalRoot(globalRoot),
|
|
52
|
+
checkPresetsDir(globalRoot),
|
|
53
|
+
checkClaudeExecutable(),
|
|
54
|
+
checkProjectEnv(cwd),
|
|
55
|
+
]);
|
|
56
|
+
if (json) {
|
|
57
|
+
stdout.write(JSON.stringify(checks, null, 2) + '\n');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
stdout.write('\n');
|
|
61
|
+
for (const check of checks) {
|
|
62
|
+
stdout.write(renderCheck(check, false) + '\n');
|
|
63
|
+
}
|
|
64
|
+
stdout.write('\n');
|
|
65
|
+
const failed = checks.filter((c) => !c.ok);
|
|
66
|
+
if (failed.length === 0) {
|
|
67
|
+
stdout.write(' \x1b[32mAll checks passed.\x1b[0m\n\n');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
stdout.write(` \x1b[33m${failed.length} check${failed.length > 1 ? 's' : ''} failed.\x1b[0m\n\n`);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { spawnSync as defaultSpawnSync } from 'node:child_process';
|
|
2
1
|
import { CliError } from '../../core/errors.js';
|
|
3
|
-
export function createEditPresetCommand({ presetService,
|
|
4
|
-
return async function editPreset(name) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
throw new CliError(`Editor exited with code ${result.status ?? 1}`);
|
|
2
|
+
export function createEditPresetCommand({ presetService, renderEdit, }) {
|
|
3
|
+
return async function editPreset({ name }) {
|
|
4
|
+
if (!name)
|
|
5
|
+
throw new CliError('Usage: cc-env edit <preset-name>');
|
|
6
|
+
const existing = await presetService.read(name);
|
|
7
|
+
const result = await renderEdit({ name, env: existing.env });
|
|
8
|
+
if (!result?.confirmed) {
|
|
9
|
+
process.stdout.write('Edit cancelled.\n');
|
|
10
|
+
return;
|
|
13
11
|
}
|
|
12
|
+
await presetService.write({
|
|
13
|
+
name,
|
|
14
|
+
env: result.env,
|
|
15
|
+
createdAt: existing.createdAt,
|
|
16
|
+
updatedAt: new Date().toISOString(),
|
|
17
|
+
});
|
|
18
|
+
process.stdout.write(`Updated preset "${name}"\n`);
|
|
14
19
|
};
|
|
15
20
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CliError } from '../../core/errors.js';
|
|
2
|
+
export function createRenamePresetCommand({ presetService }) {
|
|
3
|
+
return async function renamePreset({ from, to }) {
|
|
4
|
+
if (!from || !to)
|
|
5
|
+
throw new CliError('Usage: cc-env rename <from> <to>');
|
|
6
|
+
if (from === to)
|
|
7
|
+
throw new CliError('New name must be different from the current name');
|
|
8
|
+
const existing = await presetService.read(from);
|
|
9
|
+
const names = await presetService.listNames();
|
|
10
|
+
if (names.includes(to))
|
|
11
|
+
throw new CliError(`Preset "${to}" already exists`);
|
|
12
|
+
await presetService.write({ ...existing, name: to, updatedAt: new Date().toISOString() });
|
|
13
|
+
await presetService.remove(from);
|
|
14
|
+
process.stdout.write(`Renamed preset "${from}" → "${to}"\n`);
|
|
15
|
+
};
|
|
16
|
+
}
|
package/dist/commands/run.js
CHANGED
|
@@ -9,7 +9,7 @@ const requiredInitKeys = [
|
|
|
9
9
|
'ANTHROPIC_REASONING_MODEL',
|
|
10
10
|
];
|
|
11
11
|
export function createRunCommand({ claudeSettingsEnvService, presetService, projectEnvService, projectStateService, findClaude, renderPresetSelect, spawnCommand, stdout = process.stdout, }) {
|
|
12
|
-
return async function run({ args = [], dryRun = false, yes = false, cwd, }) {
|
|
12
|
+
return async function run({ args = [], dryRun = false, yes = false, json = false, cwd, }) {
|
|
13
13
|
// Step 0: Check settings files for init-managed keys
|
|
14
14
|
const sources = await claudeSettingsEnvService.read();
|
|
15
15
|
const mergedSettingsEnv = sources.reduce((acc, s) => ({ ...acc, ...s.env }), {});
|
|
@@ -66,6 +66,14 @@ export function createRunCommand({ claudeSettingsEnvService, presetService, proj
|
|
|
66
66
|
claudeArgs = args;
|
|
67
67
|
}
|
|
68
68
|
// Step 6: Print env vars
|
|
69
|
+
if (json && dryRun) {
|
|
70
|
+
stdout.write(JSON.stringify({
|
|
71
|
+
preset: { name: selected.name, source: selected.source },
|
|
72
|
+
command: [command, ...claudeArgs],
|
|
73
|
+
env: selected.env
|
|
74
|
+
}, null, 2) + '\n');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
69
77
|
const presetKeys = new Set(Object.keys(selected.env));
|
|
70
78
|
const envBlock = formatRunEnvBlock(selected.env, presetKeys);
|
|
71
79
|
stdout.write(`Using preset: ${selected.name} (${selected.source})\n${envBlock}\n\n`);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
export function PresetEditApp({ name, env: initialEnv, onSubmit }) {
|
|
5
|
+
const { exit } = useApp();
|
|
6
|
+
const [entries, setEntries] = useState(Object.entries(initialEnv));
|
|
7
|
+
const [cursor, setCursor] = useState(0);
|
|
8
|
+
const [editing, setEditing] = useState(null);
|
|
9
|
+
const [textInput, setTextInput] = useState('');
|
|
10
|
+
const [error, setError] = useState();
|
|
11
|
+
const [step, setStep] = useState('list');
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
if (key.escape || input === 'q') {
|
|
14
|
+
if (editing !== null) {
|
|
15
|
+
setEditing(null);
|
|
16
|
+
setTextInput('');
|
|
17
|
+
setError(undefined);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
onSubmit({ env: initialEnv, confirmed: false });
|
|
21
|
+
exit();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (step === 'list' && editing === null) {
|
|
25
|
+
if (key.upArrow || input === 'k') {
|
|
26
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (key.downArrow || input === 'j') {
|
|
30
|
+
setCursor((c) => Math.min(entries.length - 1, c + 1));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (key.return && entries.length > 0) {
|
|
34
|
+
const entry = entries[cursor];
|
|
35
|
+
if (entry) {
|
|
36
|
+
setTextInput(`${entry[0]}=${entry[1]}`);
|
|
37
|
+
setEditing(cursor);
|
|
38
|
+
setError(undefined);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (input === 'd' && entries.length > 0) {
|
|
43
|
+
setEntries((prev) => prev.filter((_, i) => i !== cursor));
|
|
44
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (input === 'a') {
|
|
48
|
+
setTextInput('');
|
|
49
|
+
setEditing(entries.length);
|
|
50
|
+
setError(undefined);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (input === 's') {
|
|
54
|
+
setStep('confirm');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (editing !== null) {
|
|
59
|
+
if (key.backspace || key.delete) {
|
|
60
|
+
setTextInput((v) => v.slice(0, -1));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (key.return) {
|
|
64
|
+
const sep = textInput.indexOf('=');
|
|
65
|
+
if (sep <= 0) {
|
|
66
|
+
setError('Format must be KEY=VALUE');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const k = textInput.slice(0, sep);
|
|
70
|
+
const v = textInput.slice(sep + 1);
|
|
71
|
+
if (!/^[A-Z0-9_]+$/.test(k)) {
|
|
72
|
+
setError('Key must match [A-Z0-9_]+');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
setEntries((prev) => {
|
|
76
|
+
const next = [...prev];
|
|
77
|
+
if (editing < prev.length) {
|
|
78
|
+
next[editing] = [k, v];
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
next.push([k, v]);
|
|
82
|
+
}
|
|
83
|
+
return next;
|
|
84
|
+
});
|
|
85
|
+
setEditing(null);
|
|
86
|
+
setTextInput('');
|
|
87
|
+
setError(undefined);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (input && !key.ctrl && !key.meta) {
|
|
91
|
+
setTextInput((v) => v + input);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (step === 'confirm') {
|
|
96
|
+
if (key.return) {
|
|
97
|
+
const env = Object.fromEntries(entries);
|
|
98
|
+
onSubmit({ env, confirmed: true });
|
|
99
|
+
exit();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (input === 'q') {
|
|
103
|
+
setStep('list');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
if (step === 'confirm') {
|
|
109
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Save changes to preset \"", name, "\"?"] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: entries.map(([k, v]) => (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "\u2022 " }), _jsx(Text, { color: "magenta", children: k }), _jsx(Text, { dimColor: true, children: "=" }), _jsx(Text, { children: v })] }, k))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press enter to confirm \u00B7 q to go back" }) })] }));
|
|
110
|
+
}
|
|
111
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Editing preset: ", name] }), _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u00B7 enter edit \u00B7 d delete \u00B7 a add \u00B7 s save \u00B7 q cancel" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [entries.map(([k, v], i) => (_jsxs(Box, { children: [_jsx(Text, { children: i === cursor ? '❯ ' : ' ' }), _jsx(Text, { color: "magenta", children: k }), _jsx(Text, { dimColor: true, children: "=" }), _jsx(Text, { children: v })] }, k))), entries.length === 0 && _jsx(Text, { dimColor: true, children: "No entries. Press a to add." })] }), editing !== null && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: editing < entries.length ? 'Edit entry' : 'Add entry' }), _jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: ['>', " "] }), _jsx(Text, { color: "cyan", children: textInput }), _jsx(Text, { dimColor: true, children: "\u2588" })] }), error ? _jsx(Text, { color: "red", children: error }) : null] }))] }));
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lkangd/cc-env",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Manage runtime environment variables for Claude Code",
|
|
5
5
|
"homepage": "https://github.com/lkangd/cc-env#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/lkangd/cc-env/issues"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"type": "module",
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=20.19.2
|
|
20
|
+
"node": ">=20.19.2"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
23
|
"cc-env": "dist/cli.js"
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"build": "tsc -p tsconfig.build.json",
|
|
33
33
|
"prepack": "npm run build",
|
|
34
34
|
"dev": "tsx src/cli.ts",
|
|
35
|
-
"test": "vitest run"
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:coverage": "vitest run --coverage"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@inkjs/ui": "^2.0.0",
|
|
@@ -53,6 +54,8 @@
|
|
|
53
54
|
"@types/gradient-string": "^1.1.6",
|
|
54
55
|
"@types/node": "^20.19.0",
|
|
55
56
|
"@types/react": "^19.1.10",
|
|
57
|
+
"@vitest/coverage-istanbul": "^3.2.4",
|
|
58
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
56
59
|
"execa": "^9.6.0",
|
|
57
60
|
"react-test-renderer": "^19.2.5",
|
|
58
61
|
"tsx": "^4.20.3",
|
package/dist/commands/debug.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { toProcessEnvMap } from '../core/process-env.js';
|
|
2
|
-
import { renderEnvSummary } from '../ink/summary.js';
|
|
3
|
-
export function createDebugCommand({ projectEnvService, processEnv }) {
|
|
4
|
-
return async function debug() {
|
|
5
|
-
const projectEnv = await projectEnvService.read();
|
|
6
|
-
await renderEnvSummary({
|
|
7
|
-
title: 'Process Environment',
|
|
8
|
-
description: 'Current process environment variables',
|
|
9
|
-
env: toProcessEnvMap(processEnv)
|
|
10
|
-
});
|
|
11
|
-
await renderEnvSummary({
|
|
12
|
-
title: 'Project Environment',
|
|
13
|
-
description: 'Environment variables from .cc-env/env.json',
|
|
14
|
-
env: projectEnv
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export function createListPresetsCommand({ presetService, projectEnvService, renderList, }) {
|
|
2
|
-
return async function listPresets() {
|
|
3
|
-
const names = await presetService.listNames();
|
|
4
|
-
const globalPresets = await Promise.all(names.map((name) => presetService.read(name).then((p) => ({ name, env: p.env, source: 'global' }))));
|
|
5
|
-
const { env: projectEnv, name: projectName } = await projectEnvService.readWithMeta();
|
|
6
|
-
const projectPreset = Object.keys(projectEnv).length > 0
|
|
7
|
-
? [{ name: projectName ?? 'project', env: projectEnv, source: 'project' }]
|
|
8
|
-
: [];
|
|
9
|
-
const presets = [...projectPreset, ...globalPresets];
|
|
10
|
-
if (presets.length === 0) {
|
|
11
|
-
console.log('No presets found.');
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
await renderList(presets);
|
|
15
|
-
};
|
|
16
|
-
}
|
package/dist/core/lock.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { open } from 'node:fs/promises';
|
|
2
|
-
import lockfile from 'proper-lockfile';
|
|
3
|
-
import { ensureParentDir } from './fs.js';
|
|
4
|
-
export async function withFileLock(filePath, run) {
|
|
5
|
-
await ensureParentDir(filePath);
|
|
6
|
-
const handle = await open(filePath, 'a+');
|
|
7
|
-
try {
|
|
8
|
-
const release = await lockfile.lock(filePath, {
|
|
9
|
-
realpath: false,
|
|
10
|
-
retries: {
|
|
11
|
-
retries: 3,
|
|
12
|
-
factor: 1,
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
try {
|
|
16
|
-
return await run();
|
|
17
|
-
}
|
|
18
|
-
finally {
|
|
19
|
-
await release();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
finally {
|
|
23
|
-
await handle.close();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState } from 'react';
|
|
3
|
-
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
-
import { EnvEntries } from './summary.js';
|
|
5
|
-
export function PresetListApp({ presets, }) {
|
|
6
|
-
const { exit } = useApp();
|
|
7
|
-
const [cursor, setCursor] = useState(0);
|
|
8
|
-
const activePreset = presets[cursor];
|
|
9
|
-
const entries = useMemo(() => activePreset
|
|
10
|
-
? Object.entries(activePreset.env).sort(([a], [b]) => a.localeCompare(b))
|
|
11
|
-
: [], [activePreset]);
|
|
12
|
-
useInput((input, key) => {
|
|
13
|
-
if (key.escape || input.toLowerCase() === 'q') {
|
|
14
|
-
exit();
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
if (key.upArrow || input === 'k') {
|
|
18
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (key.downArrow || input === 'j') {
|
|
22
|
-
setCursor((c) => Math.min(presets.length - 1, c + 1));
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Preset list" }), _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u00B7 q exit" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", width: 28, marginRight: 2, children: [_jsx(Text, { bold: true, color: "cyan", children: "Presets" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: presets.map((preset, index) => (_jsxs(Box, { children: [_jsx(Text, { children: index === cursor ? '❯ ' : ' ' }), _jsx(Text, { ...(preset.source === 'project' ? { color: 'yellow' } : {}), children: preset.name }), _jsxs(Text, { dimColor: true, children: [" (", preset.source, ")"] })] }, `${preset.source}:${preset.name}`))) })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "green", paddingX: 1, children: [_jsx(Text, { bold: true, color: "green", children: activePreset?.name ?? 'Preview' }), _jsx(Text, { dimColor: true, children: activePreset?.source === 'project' ? 'Project preset' : 'Global preset' }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EnvEntries, { entries: entries }) })] })] })] }));
|
|
27
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { configSchema } from '../core/schema.js';
|
|
3
|
-
import { atomicWriteFile } from '../core/fs.js';
|
|
4
|
-
import { resolveConfigPath } from '../core/paths.js';
|
|
5
|
-
export function createConfigService(globalRoot) {
|
|
6
|
-
const filePath = resolveConfigPath(globalRoot);
|
|
7
|
-
return {
|
|
8
|
-
async read() {
|
|
9
|
-
try {
|
|
10
|
-
const content = await readFile(filePath, 'utf8');
|
|
11
|
-
return configSchema.parse(JSON.parse(content));
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
if (error.code === 'ENOENT') {
|
|
15
|
-
return configSchema.parse({});
|
|
16
|
-
}
|
|
17
|
-
throw error;
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
async write(config) {
|
|
21
|
-
const parsed = configSchema.parse(config);
|
|
22
|
-
await atomicWriteFile(filePath, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
23
|
-
return parsed;
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { envMapSchema } from '../core/schema.js';
|
|
2
|
-
export function createRuntimeEnvService() {
|
|
3
|
-
return {
|
|
4
|
-
merge({ processEnv, settingsEnv, projectEnv, presetEnv, }) {
|
|
5
|
-
return envMapSchema.parse({
|
|
6
|
-
...processEnv,
|
|
7
|
-
...settingsEnv,
|
|
8
|
-
...projectEnv,
|
|
9
|
-
...presetEnv,
|
|
10
|
-
});
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
}
|