@lkangd/cc-env 1.1.0 → 1.1.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.
Files changed (72) hide show
  1. package/LICENSE +15 -0
  2. package/dist/cli.js +3 -4
  3. package/package.json +8 -1
  4. package/.claude/settings.json +0 -6
  5. package/.claude/settings.local.json +0 -8
  6. package/.nvmrc +0 -1
  7. package/CHANGELOG.md +0 -66
  8. package/docs/product-specs/index.draft.md +0 -106
  9. package/docs/product-specs/index.md +0 -911
  10. package/docs/product-specs/optional.md +0 -42
  11. package/docs/references/claude-code-env.md +0 -224
  12. package/docs/superpowers/plans/2026-04-24-cc-env-init-shell-migration.md +0 -1331
  13. package/docs/superpowers/plans/2026-04-24-cc-env.md +0 -1666
  14. package/docs/superpowers/plans/2026-04-26-preset-create-interactive-refactor.md +0 -1432
  15. package/docs/superpowers/specs/2026-04-24-cc-env-design.md +0 -438
  16. package/docs/superpowers/specs/2026-04-24-cc-env-init-shell-migration-design.md +0 -181
  17. package/docs/superpowers/specs/2026-04-26-preset-create-interactive-refactor-design.md +0 -78
  18. package/src/cli.ts +0 -339
  19. package/src/commands/init.ts +0 -139
  20. package/src/commands/preset/create.ts +0 -96
  21. package/src/commands/preset/delete.ts +0 -62
  22. package/src/commands/preset/show.ts +0 -51
  23. package/src/commands/restore.ts +0 -150
  24. package/src/commands/run.ts +0 -158
  25. package/src/core/errors.ts +0 -13
  26. package/src/core/find-claude.ts +0 -70
  27. package/src/core/format.ts +0 -29
  28. package/src/core/fs.ts +0 -18
  29. package/src/core/gitignore.ts +0 -26
  30. package/src/core/logger.ts +0 -11
  31. package/src/core/mask.ts +0 -17
  32. package/src/core/paths.ts +0 -41
  33. package/src/core/process-env.ts +0 -11
  34. package/src/core/schema.ts +0 -55
  35. package/src/core/spawn.ts +0 -36
  36. package/src/flows/init-flow.ts +0 -61
  37. package/src/flows/preset-create-flow.ts +0 -129
  38. package/src/flows/restore-flow.ts +0 -144
  39. package/src/ink/init-app.tsx +0 -110
  40. package/src/ink/preset-create-app.tsx +0 -451
  41. package/src/ink/preset-delete-app.tsx +0 -114
  42. package/src/ink/preset-show-app.tsx +0 -76
  43. package/src/ink/restore-app.tsx +0 -230
  44. package/src/ink/run-preset-select-app.tsx +0 -83
  45. package/src/ink/summary.tsx +0 -91
  46. package/src/services/claude-settings-env-service.ts +0 -72
  47. package/src/services/history-service.ts +0 -48
  48. package/src/services/preset-service.ts +0 -72
  49. package/src/services/project-env-service.ts +0 -128
  50. package/src/services/project-state-service.ts +0 -31
  51. package/src/services/settings-env-service.ts +0 -40
  52. package/src/services/shell-env-service.ts +0 -112
  53. package/src/types.d.ts +0 -19
  54. package/tests/cli/help.test.ts +0 -133
  55. package/tests/cli/init.test.ts +0 -76
  56. package/tests/cli/restore.test.ts +0 -172
  57. package/tests/commands/create.test.ts +0 -263
  58. package/tests/commands/output.test.ts +0 -119
  59. package/tests/commands/run.test.ts +0 -218
  60. package/tests/core/gitignore.test.ts +0 -98
  61. package/tests/core/paths.test.ts +0 -24
  62. package/tests/core/schema-mask.test.ts +0 -182
  63. package/tests/core/spawn.test.ts +0 -47
  64. package/tests/flows/init-flow.test.ts +0 -40
  65. package/tests/flows/preset-create-flow.test.ts +0 -225
  66. package/tests/flows/restore-flow.test.ts +0 -157
  67. package/tests/integration/init-restore.test.ts +0 -406
  68. package/tests/services/claude-shell.test.ts +0 -183
  69. package/tests/services/storage.test.ts +0 -143
  70. package/tsconfig.build.json +0 -9
  71. package/tsconfig.json +0 -22
  72. package/vitest.config.ts +0 -8
package/src/cli.ts DELETED
@@ -1,339 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import React from 'react'
4
- import { render } from 'ink'
5
- import { join } from 'node:path'
6
- import figlet from 'figlet'
7
- import gradient from 'gradient-string'
8
-
9
- import { Command } from 'commander'
10
-
11
- const h = React.createElement
12
-
13
- import { createInitCommand } from './commands/init.js'
14
- import { createPresetCreateCommand } from './commands/preset/create.js'
15
- import { createDeletePresetCommand } from './commands/preset/delete.js'
16
- import { PresetDeleteApp } from './ink/preset-delete-app.js'
17
- import { createShowPresetsCommand } from './commands/preset/show.js'
18
- import { createRestoreCommand } from './commands/restore.js'
19
- import { createRunCommand } from './commands/run.js'
20
- import { findClaudeExecutable } from './core/find-claude.js'
21
- import { InitApp } from './ink/init-app.js'
22
- import { renderEnvSummary } from './ink/summary.js'
23
- import { PresetCreateApp } from './ink/preset-create-app.js'
24
- import { PresetShowApp } from './ink/preset-show-app.js'
25
- import { RunPresetSelectApp } from './ink/run-preset-select-app.js'
26
- import { advanceRestoreFlow, createRestoreFlowState } from './flows/restore-flow.js'
27
- import { RestoreApp } from './ink/restore-app.js'
28
- import { CliError } from './core/errors.js'
29
- import { resolveGlobalRoot } from './core/paths.js'
30
- import { spawnCommand } from './core/spawn.js'
31
- import { createClaudeSettingsEnvService } from './services/claude-settings-env-service.js'
32
- import { createHistoryService } from './services/history-service.js'
33
- import { createPresetService } from './services/preset-service.js'
34
- import { createProjectEnvService } from './services/project-env-service.js'
35
- import { createProjectStateService } from './services/project-state-service.js'
36
- import { createSettingsEnvService } from './services/settings-env-service.js'
37
- import { createShellEnvService } from './services/shell-env-service.js'
38
-
39
- const program = new Command()
40
-
41
- program.name('cc-env').description('Manage runtime environment variables for Claude Code')
42
-
43
- const homeDir = process.env.HOME ?? process.cwd()
44
- const cwd = process.cwd()
45
- const settingsPath = join(cwd, 'settings.json')
46
- const globalRoot = resolveGlobalRoot()
47
-
48
- const claudeSettingsEnvService = createClaudeSettingsEnvService({ homeDir, cwd })
49
- const settingsEnvService = createSettingsEnvService({ settingsPath })
50
- const shellEnvService = createShellEnvService({ homeDir })
51
- const projectEnvService = createProjectEnvService({ cwd })
52
- const presetService = createPresetService(globalRoot)
53
- const historyService = createHistoryService(globalRoot)
54
-
55
- async function runRestoreFlow(context: { records: Awaited<ReturnType<typeof historyService.list>>; yes: boolean }) {
56
- const state = createRestoreFlowState(context.records)
57
- const firstRecord = context.records[0]
58
-
59
- if (!firstRecord) {
60
- render(h(RestoreApp, { state }))
61
- return undefined
62
- }
63
-
64
- if (context.yes) {
65
- const selectedRecordState = advanceRestoreFlow(state, {
66
- type: 'select-record',
67
- timestamp: firstRecord.timestamp
68
- })
69
-
70
- if (firstRecord.action === 'init') {
71
- const doneState = advanceRestoreFlow(selectedRecordState, { type: 'confirm' })
72
- if (doneState.step !== 'done') {
73
- return undefined
74
- }
75
-
76
- return {
77
- confirmed: true,
78
- timestamp: firstRecord.timestamp
79
- }
80
- }
81
-
82
- const confirmState = advanceRestoreFlow(selectedRecordState, {
83
- type: 'select-target',
84
- targetType: firstRecord.targetType,
85
- ...(firstRecord.targetType === 'preset' ? { targetName: firstRecord.targetName } : {})
86
- })
87
-
88
- const doneState = advanceRestoreFlow(confirmState, { type: 'confirm' })
89
-
90
- if (doneState.step === 'done' && doneState.targetType === 'preset') {
91
- return {
92
- confirmed: true,
93
- timestamp: doneState.selectedTimestamp,
94
- targetType: doneState.targetType,
95
- targetName: doneState.targetName
96
- }
97
- }
98
-
99
- if (doneState.step === 'done') {
100
- return {
101
- confirmed: true,
102
- timestamp: doneState.selectedTimestamp,
103
- targetType: doneState.targetType
104
- }
105
- }
106
-
107
- return undefined
108
- }
109
-
110
- let result:
111
- | {
112
- confirmed: boolean
113
- timestamp?: string
114
- targetType?: 'settings' | 'preset'
115
- targetName?: string
116
- }
117
- | undefined
118
-
119
- const app = render(
120
- h(RestoreApp, {
121
- state,
122
- onSubmit: value => {
123
- result = value
124
- }
125
- })
126
- )
127
-
128
- await app.waitUntilExit()
129
- return result
130
- }
131
-
132
- program.exitOverride().configureOutput({
133
- writeErr: str => {
134
- if (!str.startsWith('error:')) {
135
- process.stderr.write(str)
136
- }
137
- }
138
- })
139
-
140
- program
141
- .command('run [args...]')
142
- .allowUnknownOption(true)
143
- .description('Run claude with merged environment variables')
144
- .option('--dry-run', 'Preview the merged env without executing')
145
- .option('-y, --yes', 'Auto-select the default preset without interactive prompts')
146
- .action((args, options) => {
147
- const rawArgs = args ?? []
148
-
149
- return createRunCommand({
150
- claudeSettingsEnvService,
151
- presetService,
152
- projectEnvService,
153
- projectStateService: createProjectStateService(globalRoot),
154
- findClaude: findClaudeExecutable,
155
- renderPresetSelect: async ({ presets, defaultIndex }) => {
156
- let result: (typeof presets)[number] | undefined
157
- const app = render(
158
- h(RunPresetSelectApp, {
159
- presets,
160
- defaultIndex,
161
- onSubmit: preset => {
162
- result = preset
163
- }
164
- })
165
- )
166
- await app.waitUntilExit()
167
- return result
168
- },
169
- spawnCommand
170
- })({
171
- args: rawArgs,
172
- dryRun: options.dryRun ?? false,
173
- yes: options.yes ?? false,
174
- cwd
175
- })
176
- })
177
-
178
- program
179
- .command('init')
180
- .description('Initialize cc-env for the current project')
181
- .option('-y, --yes', 'Accept all defaults without interactive prompts')
182
- .action(options =>
183
- createInitCommand({
184
- claudeSettingsEnvService,
185
- shellEnvService,
186
- historyService,
187
- renderEnvSummary,
188
- renderFlow: async context => {
189
- if (context.yes) {
190
- return {
191
- selectedKeys: context.requiredKeys,
192
- confirmed: true
193
- }
194
- }
195
-
196
- let result:
197
- | {
198
- selectedKeys: string[]
199
- confirmed: boolean
200
- }
201
- | undefined
202
-
203
- const app = render(
204
- h(InitApp, {
205
- ...context,
206
- onSubmit: value => {
207
- result = value
208
- }
209
- })
210
- )
211
-
212
- await app.waitUntilExit()
213
- return result
214
- }
215
- })({
216
- yes: options.yes
217
- })
218
- )
219
-
220
- program
221
- .command('restore')
222
- .description('Restore environment variables from a previous snapshot')
223
- .option('-y, --yes', 'Accept all defaults without interactive prompts')
224
- .action(options =>
225
- createRestoreCommand({
226
- historyService,
227
- claudeSettingsEnvService,
228
- shellEnvService,
229
- settingsEnvService,
230
- presetService,
231
- renderEnvSummary: renderEnvSummary,
232
- renderFlow: context => runRestoreFlow(context)
233
- })({
234
- yes: options.yes
235
- })
236
- )
237
-
238
- const presetCommand = program.command('preset').description('Manage environment presets')
239
- presetCommand
240
- .command('show')
241
- .description('List and view all presets')
242
- .action(
243
- createShowPresetsCommand({
244
- presetService,
245
- projectEnvService,
246
- renderShow: async presets => {
247
- const app = render(h(PresetShowApp, { presets }))
248
- await app.waitUntilExit()
249
- }
250
- })
251
- )
252
- presetCommand
253
- .command('delete')
254
- .description('Delete a saved preset')
255
- .action(
256
- createDeletePresetCommand({
257
- presetService,
258
- projectEnvService,
259
- renderDelete: async presets => {
260
- let result: (typeof presets)[number] | undefined
261
- const app = render(
262
- h(PresetDeleteApp, {
263
- presets,
264
- onSubmit: preset => {
265
- result = preset
266
- }
267
- })
268
- )
269
- await app.waitUntilExit()
270
- return result
271
- }
272
- })
273
- )
274
- presetCommand
275
- .command('create')
276
- .description('Create a new environment preset')
277
- .action(() =>
278
- createPresetCreateCommand({
279
- presetService,
280
- projectEnvService,
281
- renderFlow: async () => {
282
- let result: React.ComponentProps<typeof PresetCreateApp>['onSubmit'] extends (result: infer TResult) => unknown
283
- ? TResult | undefined
284
- : undefined
285
- const app = render(
286
- h(PresetCreateApp, {
287
- onSubmit: value => {
288
- result = value
289
- },
290
- readFile: async filePath => {
291
- const { readEnvFile } = await import('./commands/preset/create.js')
292
- return readEnvFile(filePath)
293
- },
294
- globalPresetPath: name => presetService.getPath(name),
295
- projectEnvPath: join(cwd, '.cc-env', 'env.json')
296
- })
297
- )
298
-
299
- await app.waitUntilExit()
300
- return result
301
- }
302
- })({ cwd })
303
- )
304
-
305
- function printBanner() {
306
- const banner = figlet.textSync('CC ENV', { font: 'ANSI Shadow' })
307
- const line = '─'.repeat(48)
308
- const styled = gradient(['#00d2ff', '#7b2ff7', '#ff0080'])(banner)
309
- process.stderr.write(`\n${styled}\x1b[2m\n${line}\x1b[0m\n\n`)
310
- }
311
-
312
- program.hook('preAction', () => {
313
- printBanner()
314
- })
315
-
316
- program.parseAsync(process.argv).catch((error: unknown) => {
317
- if (error instanceof CliError) {
318
- process.stderr.write(`\n Error: ${error.message}\n\n`)
319
- process.exitCode = error.exitCode
320
- return
321
- }
322
-
323
- if (error && typeof error === 'object' && 'code' in error) {
324
- const { code, message } = error as { code?: string; message?: string }
325
-
326
- if (code === 'commander.helpDisplayed') {
327
- process.exitCode = 0
328
- return
329
- }
330
-
331
- const hint = ` Run "cc-env --help" to see available commands and options.\n`
332
- const formatted = message?.replace(/^error:\s*/i, '') ?? 'Unknown error'
333
- process.stderr.write(`\n Error: ${formatted}\n\n${hint}\n`)
334
- process.exitCode = 1
335
- return
336
- }
337
-
338
- throw error
339
- })
@@ -1,139 +0,0 @@
1
- import React from 'react'
2
- import { Box, Text } from 'ink'
3
-
4
- import { CliError } from '../core/errors.js'
5
- import { envMapSchema, type EnvMap, type InitHistoryRecord, type SourceEntry } from '../core/schema.js'
6
- import type { ClaudeSettingsSource } from '../services/claude-settings-env-service.js'
7
- import type { ShellWriteRecord } from '../services/shell-env-service.js'
8
-
9
- const h = React.createElement
10
-
11
- const requiredInitKeys = [
12
- 'ANTHROPIC_AUTH_TOKEN',
13
- 'ANTHROPIC_BASE_URL',
14
- 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
15
- 'ANTHROPIC_DEFAULT_OPUS_MODEL',
16
- 'ANTHROPIC_DEFAULT_SONNET_MODEL',
17
- 'ANTHROPIC_REASONING_MODEL',
18
- ] as const
19
-
20
- type ClaudeSettingsEnvService = {
21
- read: () => Promise<ClaudeSettingsSource[]>
22
- write: (sources: Array<{ path: string; env: EnvMap }>) => Promise<void>
23
- }
24
-
25
- type ShellEnvService = {
26
- write: (env: EnvMap) => Promise<ShellWriteRecord[]>
27
- }
28
-
29
- type HistoryService = {
30
- write: (record: InitHistoryRecord) => Promise<unknown>
31
- }
32
-
33
- type InitFlowResult = {
34
- selectedKeys: string[]
35
- confirmed?: boolean
36
- }
37
-
38
- function omitKeys(env: EnvMap, keys: string[]): EnvMap {
39
- return envMapSchema.parse(
40
- Object.fromEntries(Object.entries(env).filter(([key]) => !keys.includes(key))),
41
- )
42
- }
43
-
44
- export function createInitCommand({
45
- claudeSettingsEnvService,
46
- shellEnvService,
47
- historyService,
48
- renderFlow,
49
- renderEnvSummary,
50
- }: {
51
- claudeSettingsEnvService: ClaudeSettingsEnvService
52
- shellEnvService: ShellEnvService
53
- historyService: HistoryService
54
- renderFlow: (context: {
55
- keys: string[]
56
- requiredKeys: string[]
57
- yes: boolean
58
- sourceFiles: string[]
59
- }) => Promise<InitFlowResult | void> | InitFlowResult | void
60
- renderEnvSummary: (props: {
61
- title: string
62
- env: EnvMap
63
- fromFiles?: string[]
64
- toFiles?: string[]
65
- footer?: React.ReactNode
66
- }) => Promise<void>
67
- }) {
68
- return async function init({ yes = false }: { yes?: boolean } = {}): Promise<void> {
69
- const sources = await claudeSettingsEnvService.read()
70
-
71
- if (sources.every((s) => !s.exists)) {
72
- throw new CliError('No Claude settings files were found')
73
- }
74
-
75
- const effectiveEnv = envMapSchema.parse(
76
- sources.reduce<Record<string, unknown>>((acc, source) => ({ ...acc, ...source.env }), {}),
77
- )
78
- const keys = Object.keys(effectiveEnv).sort()
79
- const requiredKeys = requiredInitKeys.filter((key) => key in effectiveEnv)
80
- const sourceFiles = sources.map((s) => s.path)
81
- const result = await renderFlow({ keys, requiredKeys, yes, sourceFiles })
82
-
83
- if (!result?.confirmed) {
84
- return
85
- }
86
-
87
- const migratedEnv = envMapSchema.parse(
88
- Object.fromEntries(
89
- result.selectedKeys
90
- .filter((key) => key in effectiveEnv)
91
- .map((key) => [key, effectiveEnv[key]]),
92
- ),
93
- )
94
-
95
- if (Object.keys(migratedEnv).length === 0) {
96
- throw new CliError('No selected env values found to migrate')
97
- }
98
-
99
- const initSources: SourceEntry[] = sources.map((source) => ({
100
- file: source.path,
101
- backup: envMapSchema.parse(
102
- Object.fromEntries(
103
- result.selectedKeys
104
- .filter((key) => key in source.env)
105
- .map((key) => [key, source.env[key]]),
106
- ),
107
- ),
108
- }))
109
-
110
- const timestamp = new Date().toISOString()
111
- const shellWrites = await shellEnvService.write(migratedEnv)
112
-
113
- await historyService.write({
114
- timestamp,
115
- action: 'init',
116
- migratedKeys: result.selectedKeys,
117
- sources: initSources,
118
- shellWrites,
119
- })
120
-
121
- await claudeSettingsEnvService.write(
122
- sources.map((source) => ({
123
- path: source.path,
124
- env: omitKeys(source.env, result.selectedKeys),
125
- })),
126
- )
127
-
128
- await renderEnvSummary({
129
- title: 'Migrated',
130
- env: migratedEnv,
131
- fromFiles: initSources.map((s) => s.file),
132
- toFiles: shellWrites.map((sw) => sw.filePath),
133
- footer: h(Box, { flexDirection: 'column' },
134
- h(Text, { color: 'green' }, 'Init complete'),
135
- h(Text, { bold: true, color: 'green' }, 'Please restart your terminal for the migrated environment variables to take effect.'),
136
- ),
137
- })
138
- }
139
- }
@@ -1,96 +0,0 @@
1
- import { readFile } from 'node:fs/promises'
2
- import { extname } from 'node:path'
3
-
4
- import { parse as parseYaml } from 'yaml'
5
-
6
- import { CliError } from '../../core/errors.js'
7
- import { ensureGitignoreEntry } from '../../core/gitignore.js'
8
- import { type EnvMap } from '../../core/schema.js'
9
- import { toProcessEnvMap } from '../../core/process-env.js'
10
- import type { PresetCreateAppResult } from '../../ink/preset-create-app.js'
11
-
12
- type PresetService = {
13
- write: (preset: {
14
- name: string
15
- createdAt: string
16
- updatedAt: string
17
- env: EnvMap
18
- }) => Promise<unknown>
19
- }
20
-
21
- type ProjectEnvService = {
22
- write: (env: EnvMap, meta?: { name?: string; createdAt?: string; updatedAt?: string }) => Promise<unknown>
23
- }
24
-
25
- export async function readEnvFile(filePath: string): Promise<{ allKeys: string[]; env: EnvMap }> {
26
- try {
27
- const content = await readFile(filePath, 'utf8')
28
- const extension = extname(filePath).toLowerCase()
29
-
30
- if (extension !== '.yaml' && extension !== '.yml' && extension !== '.json') {
31
- throw new CliError(`Unsupported file format: ${extension}`, 2)
32
- }
33
-
34
- const parsed = extension === '.yaml' || extension === '.yml'
35
- ? parseYaml(content)
36
- : JSON.parse(content)
37
-
38
- const raw = (parsed ?? {}) as Record<string, unknown>
39
- const source = extension === '.json'
40
- && raw
41
- && typeof raw === 'object'
42
- && 'env' in raw
43
- && raw.env
44
- && typeof raw.env === 'object'
45
- && !Array.isArray(raw.env)
46
- ? raw.env as Record<string, unknown>
47
- : raw
48
-
49
- const env = toProcessEnvMap(source)
50
- return {
51
- allKeys: Object.keys(env),
52
- env,
53
- }
54
- } catch (error) {
55
- if (error instanceof CliError) throw error
56
- throw new CliError(`Failed to read env file: ${filePath}`, 2)
57
- }
58
- }
59
-
60
- export function createPresetCreateCommand({
61
- presetService,
62
- projectEnvService,
63
- renderFlow,
64
- ensureGitignore = (dir, entry) => ensureGitignoreEntry(dir, entry),
65
- }: {
66
- presetService: PresetService
67
- projectEnvService: ProjectEnvService
68
- renderFlow: () => Promise<PresetCreateAppResult | void>
69
- ensureGitignore?: (dir: string, entry: string) => Promise<void>
70
- }) {
71
- return async function createPreset({ cwd }: { cwd: string }): Promise<void> {
72
- const result = await renderFlow()
73
-
74
- if (!result) return
75
-
76
- const selectedEnv: EnvMap = {}
77
- for (const key of result.selectedKeys) {
78
- selectedEnv[key] = result.env[key] ?? ''
79
- }
80
-
81
- const timestamp = new Date().toISOString()
82
-
83
- if (result.destination === 'project') {
84
- await projectEnvService.write(selectedEnv, { name: result.presetName, createdAt: timestamp, updatedAt: timestamp })
85
- await ensureGitignore(cwd, '.cc-env')
86
- return
87
- }
88
-
89
- await presetService.write({
90
- name: result.presetName,
91
- createdAt: timestamp,
92
- updatedAt: timestamp,
93
- env: selectedEnv,
94
- })
95
- }
96
- }
@@ -1,62 +0,0 @@
1
- import type { EnvMap } from '../../core/schema.js'
2
-
3
- export type PresetSource = 'global' | 'project'
4
-
5
- type PresetService = {
6
- listNames: () => Promise<string[]>
7
- read: (name: string) => Promise<{ env: EnvMap }>
8
- remove: (name: string) => Promise<void>
9
- }
10
-
11
- type ProjectEnvService = {
12
- readWithMeta: () => Promise<{ env: EnvMap; name?: string | undefined }>
13
- write: (env: EnvMap) => Promise<EnvMap>
14
- }
15
-
16
- export type PresetDeleteItem = {
17
- name: string
18
- env: EnvMap
19
- source: PresetSource
20
- }
21
-
22
- export function createDeletePresetCommand({
23
- presetService,
24
- projectEnvService,
25
- renderDelete,
26
- }: {
27
- presetService: PresetService
28
- projectEnvService: ProjectEnvService
29
- renderDelete: (presets: Array<PresetDeleteItem>) => Promise<PresetDeleteItem | undefined>
30
- }) {
31
- return async function deletePreset(): Promise<void> {
32
- const names = await presetService.listNames()
33
- const globalPresets = await Promise.all(
34
- names.map((name) =>
35
- presetService.read(name).then((p) => ({ name, env: p.env, source: 'global' as const })),
36
- ),
37
- )
38
-
39
- const { env: projectEnv, name: projectName } = await projectEnvService.readWithMeta()
40
- const projectPreset =
41
- Object.keys(projectEnv).length > 0
42
- ? [{ name: projectName ?? 'project', env: projectEnv, source: 'project' as const }]
43
- : []
44
-
45
- const presets = [...projectPreset, ...globalPresets]
46
- if (presets.length === 0) {
47
- console.log('No presets found.')
48
- return
49
- }
50
-
51
- const selected = await renderDelete(presets)
52
- if (!selected) return
53
-
54
- if (selected.source === 'project') {
55
- await projectEnvService.write({})
56
- } else {
57
- await presetService.remove(selected.name)
58
- }
59
-
60
- console.log(`Deleted preset: ${selected.name}`)
61
- }
62
- }
@@ -1,51 +0,0 @@
1
- import type { EnvMap } from '../../core/schema.js'
2
-
3
- type PresetService = {
4
- listNames: () => Promise<string[]>
5
- read: (name: string) => Promise<{ env: EnvMap }>
6
- }
7
-
8
- type ProjectEnvService = {
9
- readWithMeta: () => Promise<{ env: EnvMap; name?: string | undefined }>
10
- }
11
-
12
- export type PresetSource = 'global' | 'project'
13
-
14
- export type PresetShowItem = {
15
- name: string
16
- env: EnvMap
17
- source: PresetSource
18
- }
19
-
20
- export function createShowPresetsCommand({
21
- presetService,
22
- projectEnvService,
23
- renderShow,
24
- }: {
25
- presetService: PresetService
26
- projectEnvService: ProjectEnvService
27
- renderShow: (presets: Array<PresetShowItem>) => Promise<void>
28
- }) {
29
- return async function showPresets(): Promise<void> {
30
- const names = await presetService.listNames()
31
- const globalPresets = await Promise.all(
32
- names.map((name) =>
33
- presetService.read(name).then((p) => ({ name, env: p.env, source: 'global' as const })),
34
- ),
35
- )
36
-
37
- const { env: projectEnv, name: projectName } = await projectEnvService.readWithMeta()
38
- const projectPreset =
39
- Object.keys(projectEnv).length > 0
40
- ? [{ name: projectName ?? 'project', env: projectEnv, source: 'project' as const }]
41
- : []
42
-
43
- const presets = [...projectPreset, ...globalPresets]
44
- if (presets.length === 0) {
45
- console.log('No presets found.')
46
- return
47
- }
48
-
49
- await renderShow(presets)
50
- }
51
- }