@lkangd/cc-env 1.1.1 → 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 (71) hide show
  1. package/LICENSE +15 -0
  2. package/package.json +8 -1
  3. package/.claude/settings.json +0 -6
  4. package/.claude/settings.local.json +0 -8
  5. package/.nvmrc +0 -1
  6. package/CHANGELOG.md +0 -71
  7. package/docs/product-specs/index.draft.md +0 -106
  8. package/docs/product-specs/index.md +0 -911
  9. package/docs/product-specs/optional.md +0 -42
  10. package/docs/references/claude-code-env.md +0 -224
  11. package/docs/superpowers/plans/2026-04-24-cc-env-init-shell-migration.md +0 -1331
  12. package/docs/superpowers/plans/2026-04-24-cc-env.md +0 -1666
  13. package/docs/superpowers/plans/2026-04-26-preset-create-interactive-refactor.md +0 -1432
  14. package/docs/superpowers/specs/2026-04-24-cc-env-design.md +0 -438
  15. package/docs/superpowers/specs/2026-04-24-cc-env-init-shell-migration-design.md +0 -181
  16. package/docs/superpowers/specs/2026-04-26-preset-create-interactive-refactor-design.md +0 -78
  17. package/src/cli.ts +0 -340
  18. package/src/commands/init.ts +0 -139
  19. package/src/commands/preset/create.ts +0 -96
  20. package/src/commands/preset/delete.ts +0 -62
  21. package/src/commands/preset/show.ts +0 -51
  22. package/src/commands/restore.ts +0 -150
  23. package/src/commands/run.ts +0 -158
  24. package/src/core/errors.ts +0 -13
  25. package/src/core/find-claude.ts +0 -70
  26. package/src/core/format.ts +0 -29
  27. package/src/core/fs.ts +0 -18
  28. package/src/core/gitignore.ts +0 -26
  29. package/src/core/logger.ts +0 -11
  30. package/src/core/mask.ts +0 -17
  31. package/src/core/paths.ts +0 -41
  32. package/src/core/process-env.ts +0 -11
  33. package/src/core/schema.ts +0 -55
  34. package/src/core/spawn.ts +0 -36
  35. package/src/flows/init-flow.ts +0 -61
  36. package/src/flows/preset-create-flow.ts +0 -129
  37. package/src/flows/restore-flow.ts +0 -144
  38. package/src/ink/init-app.tsx +0 -110
  39. package/src/ink/preset-create-app.tsx +0 -451
  40. package/src/ink/preset-delete-app.tsx +0 -114
  41. package/src/ink/preset-show-app.tsx +0 -76
  42. package/src/ink/restore-app.tsx +0 -230
  43. package/src/ink/run-preset-select-app.tsx +0 -83
  44. package/src/ink/summary.tsx +0 -91
  45. package/src/services/claude-settings-env-service.ts +0 -72
  46. package/src/services/history-service.ts +0 -48
  47. package/src/services/preset-service.ts +0 -72
  48. package/src/services/project-env-service.ts +0 -128
  49. package/src/services/project-state-service.ts +0 -31
  50. package/src/services/settings-env-service.ts +0 -40
  51. package/src/services/shell-env-service.ts +0 -112
  52. package/src/types.d.ts +0 -19
  53. package/tests/cli/help.test.ts +0 -133
  54. package/tests/cli/init.test.ts +0 -76
  55. package/tests/cli/restore.test.ts +0 -172
  56. package/tests/commands/create.test.ts +0 -263
  57. package/tests/commands/output.test.ts +0 -119
  58. package/tests/commands/run.test.ts +0 -218
  59. package/tests/core/gitignore.test.ts +0 -98
  60. package/tests/core/paths.test.ts +0 -24
  61. package/tests/core/schema-mask.test.ts +0 -182
  62. package/tests/core/spawn.test.ts +0 -47
  63. package/tests/flows/init-flow.test.ts +0 -40
  64. package/tests/flows/preset-create-flow.test.ts +0 -225
  65. package/tests/flows/restore-flow.test.ts +0 -157
  66. package/tests/integration/init-restore.test.ts +0 -406
  67. package/tests/services/claude-shell.test.ts +0 -183
  68. package/tests/services/storage.test.ts +0 -143
  69. package/tsconfig.build.json +0 -9
  70. package/tsconfig.json +0 -22
  71. package/vitest.config.ts +0 -8
package/src/cli.ts DELETED
@@ -1,340 +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
- program
239
- .command('show')
240
- .description('List and view all presets')
241
- .action(
242
- createShowPresetsCommand({
243
- presetService,
244
- projectEnvService,
245
- renderShow: async presets => {
246
- const app = render(h(PresetShowApp, { presets }))
247
- await app.waitUntilExit()
248
- }
249
- })
250
- )
251
-
252
- program
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
-
275
- program
276
- .command('create')
277
- .description('Create a new environment preset')
278
- .action(() =>
279
- createPresetCreateCommand({
280
- presetService,
281
- projectEnvService,
282
- renderFlow: async () => {
283
- let result: React.ComponentProps<typeof PresetCreateApp>['onSubmit'] extends (result: infer TResult) => unknown
284
- ? TResult | undefined
285
- : undefined
286
- const app = render(
287
- h(PresetCreateApp, {
288
- onSubmit: value => {
289
- result = value
290
- },
291
- readFile: async filePath => {
292
- const { readEnvFile } = await import('./commands/preset/create.js')
293
- return readEnvFile(filePath)
294
- },
295
- globalPresetPath: name => presetService.getPath(name),
296
- projectEnvPath: join(cwd, '.cc-env', 'env.json')
297
- })
298
- )
299
-
300
- await app.waitUntilExit()
301
- return result
302
- }
303
- })({ cwd })
304
- )
305
-
306
- function printBanner() {
307
- const banner = figlet.textSync('CC ENV', { font: 'ANSI Shadow' })
308
- const line = '─'.repeat(48)
309
- const styled = gradient(['#00d2ff', '#7b2ff7', '#ff0080'])(banner)
310
- process.stderr.write(`\n${styled}\x1b[2m\n${line}\x1b[0m\n\n`)
311
- }
312
-
313
- program.hook('preAction', () => {
314
- printBanner()
315
- })
316
-
317
- program.parseAsync(process.argv).catch((error: unknown) => {
318
- if (error instanceof CliError) {
319
- process.stderr.write(`\n Error: ${error.message}\n\n`)
320
- process.exitCode = error.exitCode
321
- return
322
- }
323
-
324
- if (error && typeof error === 'object' && 'code' in error) {
325
- const { code, message } = error as { code?: string; message?: string }
326
-
327
- if (code === 'commander.helpDisplayed') {
328
- process.exitCode = 0
329
- return
330
- }
331
-
332
- const hint = ` Run "cc-env --help" to see available commands and options.\n`
333
- const formatted = message?.replace(/^error:\s*/i, '') ?? 'Unknown error'
334
- process.stderr.write(`\n Error: ${formatted}\n\n${hint}\n`)
335
- process.exitCode = 1
336
- return
337
- }
338
-
339
- throw error
340
- })
@@ -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
- }