spindb 0.1.0

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 (41) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/.env.example +1 -0
  3. package/.prettierignore +4 -0
  4. package/.prettierrc +6 -0
  5. package/CLAUDE.md +162 -0
  6. package/README.md +204 -0
  7. package/TODO.md +66 -0
  8. package/bin/cli.js +7 -0
  9. package/eslint.config.js +18 -0
  10. package/package.json +52 -0
  11. package/seeds/mysql/sample-db.sql +22 -0
  12. package/seeds/postgres/sample-db.sql +27 -0
  13. package/src/bin/cli.ts +8 -0
  14. package/src/cli/commands/clone.ts +101 -0
  15. package/src/cli/commands/config.ts +215 -0
  16. package/src/cli/commands/connect.ts +106 -0
  17. package/src/cli/commands/create.ts +148 -0
  18. package/src/cli/commands/delete.ts +94 -0
  19. package/src/cli/commands/list.ts +69 -0
  20. package/src/cli/commands/menu.ts +675 -0
  21. package/src/cli/commands/restore.ts +161 -0
  22. package/src/cli/commands/start.ts +95 -0
  23. package/src/cli/commands/stop.ts +91 -0
  24. package/src/cli/index.ts +38 -0
  25. package/src/cli/ui/prompts.ts +197 -0
  26. package/src/cli/ui/spinner.ts +94 -0
  27. package/src/cli/ui/theme.ts +113 -0
  28. package/src/config/defaults.ts +49 -0
  29. package/src/config/paths.ts +53 -0
  30. package/src/core/binary-manager.ts +239 -0
  31. package/src/core/config-manager.ts +259 -0
  32. package/src/core/container-manager.ts +234 -0
  33. package/src/core/port-manager.ts +84 -0
  34. package/src/core/process-manager.ts +353 -0
  35. package/src/engines/base-engine.ts +103 -0
  36. package/src/engines/index.ts +46 -0
  37. package/src/engines/postgresql/binary-urls.ts +52 -0
  38. package/src/engines/postgresql/index.ts +298 -0
  39. package/src/engines/postgresql/restore.ts +173 -0
  40. package/src/types/index.ts +97 -0
  41. package/tsconfig.json +24 -0
@@ -0,0 +1,101 @@
1
+ import { Command } from 'commander'
2
+ import chalk from 'chalk'
3
+ import { containerManager } from '@/core/container-manager'
4
+ import { processManager } from '@/core/process-manager'
5
+ import { getEngine } from '@/engines'
6
+ import { promptContainerSelect, promptContainerName } from '@/cli/ui/prompts'
7
+ import { createSpinner } from '@/cli/ui/spinner'
8
+ import { error, warning, connectionBox } from '@/cli/ui/theme'
9
+
10
+ export const cloneCommand = new Command('clone')
11
+ .description('Clone a container with all its data')
12
+ .argument('[source]', 'Source container name')
13
+ .argument('[target]', 'Target container name')
14
+ .action(async (source: string | undefined, target: string | undefined) => {
15
+ try {
16
+ let sourceName = source
17
+ let targetName = target
18
+
19
+ // Interactive selection if no source provided
20
+ if (!sourceName) {
21
+ const containers = await containerManager.list()
22
+ const stopped = containers.filter((c) => c.status !== 'running')
23
+
24
+ if (containers.length === 0) {
25
+ console.log(
26
+ warning('No containers found. Create one with: spindb create'),
27
+ )
28
+ return
29
+ }
30
+
31
+ if (stopped.length === 0) {
32
+ console.log(
33
+ warning(
34
+ 'All containers are running. Stop a container first to clone it.',
35
+ ),
36
+ )
37
+ console.log(
38
+ chalk.gray(
39
+ ' Cloning requires the source container to be stopped.',
40
+ ),
41
+ )
42
+ return
43
+ }
44
+
45
+ const selected = await promptContainerSelect(
46
+ stopped,
47
+ 'Select container to clone:',
48
+ )
49
+ if (!selected) return
50
+ sourceName = selected
51
+ }
52
+
53
+ // Check source exists
54
+ const sourceConfig = await containerManager.getConfig(sourceName)
55
+ if (!sourceConfig) {
56
+ console.error(error(`Container "${sourceName}" not found`))
57
+ process.exit(1)
58
+ }
59
+
60
+ // Check source is stopped
61
+ const running = await processManager.isRunning(sourceName)
62
+ if (running) {
63
+ console.error(
64
+ error(
65
+ `Container "${sourceName}" is running. Stop it first to clone.`,
66
+ ),
67
+ )
68
+ process.exit(1)
69
+ }
70
+
71
+ // Get target name
72
+ if (!targetName) {
73
+ targetName = await promptContainerName(`${sourceName}-copy`)
74
+ }
75
+
76
+ // Clone the container
77
+ const cloneSpinner = createSpinner(
78
+ `Cloning ${sourceName} to ${targetName}...`,
79
+ )
80
+ cloneSpinner.start()
81
+
82
+ const newConfig = await containerManager.clone(sourceName, targetName)
83
+
84
+ cloneSpinner.succeed(`Cloned "${sourceName}" to "${targetName}"`)
85
+
86
+ // Get engine for connection string
87
+ const engine = getEngine(newConfig.engine)
88
+ const connectionString = engine.getConnectionString(newConfig)
89
+
90
+ console.log()
91
+ console.log(connectionBox(targetName, connectionString, newConfig.port))
92
+ console.log()
93
+ console.log(chalk.gray(' Start the cloned container:'))
94
+ console.log(chalk.cyan(` spindb start ${targetName}`))
95
+ console.log()
96
+ } catch (err) {
97
+ const e = err as Error
98
+ console.error(error(e.message))
99
+ process.exit(1)
100
+ }
101
+ })
@@ -0,0 +1,215 @@
1
+ import { Command } from 'commander'
2
+ import chalk from 'chalk'
3
+ import { existsSync } from 'fs'
4
+ import { configManager } from '@/core/config-manager'
5
+ import { error, success, header } from '@/cli/ui/theme'
6
+ import { createSpinner } from '@/cli/ui/spinner'
7
+ import type { BinaryTool } from '@/types'
8
+
9
+ const VALID_TOOLS: BinaryTool[] = [
10
+ 'psql',
11
+ 'pg_dump',
12
+ 'pg_restore',
13
+ 'pg_basebackup',
14
+ ]
15
+
16
+ export const configCommand = new Command('config')
17
+ .description('Manage spindb configuration')
18
+ .addCommand(
19
+ new Command('show')
20
+ .description('Show current configuration')
21
+ .action(async () => {
22
+ try {
23
+ const config = await configManager.getConfig()
24
+
25
+ console.log()
26
+ console.log(header('SpinDB Configuration'))
27
+ console.log()
28
+
29
+ console.log(chalk.bold(' Binary Paths:'))
30
+ console.log(chalk.gray(' ' + '─'.repeat(50)))
31
+
32
+ for (const tool of VALID_TOOLS) {
33
+ const binaryConfig = config.binaries[tool]
34
+ if (binaryConfig) {
35
+ const sourceLabel =
36
+ binaryConfig.source === 'system'
37
+ ? chalk.blue('system')
38
+ : binaryConfig.source === 'custom'
39
+ ? chalk.yellow('custom')
40
+ : chalk.green('bundled')
41
+ const versionLabel = binaryConfig.version
42
+ ? chalk.gray(` (v${binaryConfig.version})`)
43
+ : ''
44
+ console.log(
45
+ ` ${chalk.cyan(tool.padEnd(15))} ${binaryConfig.path}${versionLabel} [${sourceLabel}]`,
46
+ )
47
+ } else {
48
+ console.log(
49
+ ` ${chalk.cyan(tool.padEnd(15))} ${chalk.gray('not configured')}`,
50
+ )
51
+ }
52
+ }
53
+
54
+ console.log()
55
+
56
+ if (config.updatedAt) {
57
+ console.log(
58
+ chalk.gray(
59
+ ` Last updated: ${new Date(config.updatedAt).toLocaleString()}`,
60
+ ),
61
+ )
62
+ console.log()
63
+ }
64
+ } catch (err) {
65
+ const e = err as Error
66
+ console.error(error(e.message))
67
+ process.exit(1)
68
+ }
69
+ }),
70
+ )
71
+ .addCommand(
72
+ new Command('detect')
73
+ .description('Auto-detect PostgreSQL client tools on your system')
74
+ .action(async () => {
75
+ try {
76
+ console.log()
77
+ console.log(header('Detecting PostgreSQL Tools'))
78
+ console.log()
79
+
80
+ const spinner = createSpinner(
81
+ 'Searching for PostgreSQL client tools...',
82
+ )
83
+ spinner.start()
84
+
85
+ // Clear existing configs to force re-detection
86
+ await configManager.clearAllBinaries()
87
+
88
+ const { found, missing } = await configManager.initialize()
89
+
90
+ spinner.succeed('Detection complete')
91
+ console.log()
92
+
93
+ if (found.length > 0) {
94
+ console.log(chalk.bold(' Found:'))
95
+ for (const tool of found) {
96
+ const config = await configManager.getBinaryConfig(tool)
97
+ if (config) {
98
+ const versionLabel = config.version
99
+ ? chalk.gray(` (v${config.version})`)
100
+ : ''
101
+ console.log(
102
+ ` ${chalk.green('✓')} ${chalk.cyan(tool.padEnd(15))} ${config.path}${versionLabel}`,
103
+ )
104
+ }
105
+ }
106
+ console.log()
107
+ }
108
+
109
+ if (missing.length > 0) {
110
+ console.log(chalk.bold(' Not found:'))
111
+ for (const tool of missing) {
112
+ console.log(` ${chalk.red('✗')} ${chalk.cyan(tool)}`)
113
+ }
114
+ console.log()
115
+ console.log(chalk.gray(' Install missing tools:'))
116
+ console.log(
117
+ chalk.gray(
118
+ ' macOS: brew install libpq && brew link --force libpq',
119
+ ),
120
+ )
121
+ console.log(chalk.gray(' Ubuntu: apt install postgresql-client'))
122
+ console.log()
123
+ }
124
+ } catch (err) {
125
+ const e = err as Error
126
+ console.error(error(e.message))
127
+ process.exit(1)
128
+ }
129
+ }),
130
+ )
131
+ .addCommand(
132
+ new Command('set')
133
+ .description('Set a custom binary path')
134
+ .argument('<tool>', `Tool name (${VALID_TOOLS.join(', ')})`)
135
+ .argument('<path>', 'Path to the binary')
136
+ .action(async (tool: string, path: string) => {
137
+ try {
138
+ // Validate tool name
139
+ if (!VALID_TOOLS.includes(tool as BinaryTool)) {
140
+ console.error(error(`Invalid tool: ${tool}`))
141
+ console.log(chalk.gray(` Valid tools: ${VALID_TOOLS.join(', ')}`))
142
+ process.exit(1)
143
+ }
144
+
145
+ // Validate path exists
146
+ if (!existsSync(path)) {
147
+ console.error(error(`File not found: ${path}`))
148
+ process.exit(1)
149
+ }
150
+
151
+ await configManager.setBinaryPath(tool as BinaryTool, path, 'custom')
152
+
153
+ const config = await configManager.getBinaryConfig(tool as BinaryTool)
154
+ const versionLabel = config?.version ? ` (v${config.version})` : ''
155
+
156
+ console.log(success(`Set ${tool} to: ${path}${versionLabel}`))
157
+ } catch (err) {
158
+ const e = err as Error
159
+ console.error(error(e.message))
160
+ process.exit(1)
161
+ }
162
+ }),
163
+ )
164
+ .addCommand(
165
+ new Command('unset')
166
+ .description('Remove a custom binary path')
167
+ .argument('<tool>', `Tool name (${VALID_TOOLS.join(', ')})`)
168
+ .action(async (tool: string) => {
169
+ try {
170
+ // Validate tool name
171
+ if (!VALID_TOOLS.includes(tool as BinaryTool)) {
172
+ console.error(error(`Invalid tool: ${tool}`))
173
+ console.log(chalk.gray(` Valid tools: ${VALID_TOOLS.join(', ')}`))
174
+ process.exit(1)
175
+ }
176
+
177
+ await configManager.clearBinaryPath(tool as BinaryTool)
178
+ console.log(success(`Removed ${tool} configuration`))
179
+ } catch (err) {
180
+ const e = err as Error
181
+ console.error(error(e.message))
182
+ process.exit(1)
183
+ }
184
+ }),
185
+ )
186
+ .addCommand(
187
+ new Command('path')
188
+ .description('Show the path for a specific tool')
189
+ .argument('<tool>', `Tool name (${VALID_TOOLS.join(', ')})`)
190
+ .action(async (tool: string) => {
191
+ try {
192
+ // Validate tool name
193
+ if (!VALID_TOOLS.includes(tool as BinaryTool)) {
194
+ console.error(error(`Invalid tool: ${tool}`))
195
+ console.log(chalk.gray(` Valid tools: ${VALID_TOOLS.join(', ')}`))
196
+ process.exit(1)
197
+ }
198
+
199
+ const path = await configManager.getBinaryPath(tool as BinaryTool)
200
+ if (path) {
201
+ console.log(path)
202
+ } else {
203
+ console.error(error(`${tool} not found`))
204
+ console.log(
205
+ chalk.gray(` Run 'spindb config detect' to auto-detect tools`),
206
+ )
207
+ process.exit(1)
208
+ }
209
+ } catch (err) {
210
+ const e = err as Error
211
+ console.error(error(e.message))
212
+ process.exit(1)
213
+ }
214
+ }),
215
+ )
@@ -0,0 +1,106 @@
1
+ import { Command } from 'commander'
2
+ import { spawn } from 'child_process'
3
+ import chalk from 'chalk'
4
+ import { containerManager } from '@/core/container-manager'
5
+ import { processManager } from '@/core/process-manager'
6
+ import { getEngine } from '@/engines'
7
+ import { promptContainerSelect } from '@/cli/ui/prompts'
8
+ import { error, warning, info } from '@/cli/ui/theme'
9
+
10
+ export const connectCommand = new Command('connect')
11
+ .description('Connect to a container with psql')
12
+ .argument('[name]', 'Container name')
13
+ .option('-d, --database <name>', 'Database name', 'postgres')
14
+ .action(async (name: string | undefined, options: { database: string }) => {
15
+ try {
16
+ let containerName = name
17
+
18
+ // Interactive selection if no name provided
19
+ if (!containerName) {
20
+ const containers = await containerManager.list()
21
+ const running = containers.filter((c) => c.status === 'running')
22
+
23
+ if (running.length === 0) {
24
+ if (containers.length === 0) {
25
+ console.log(
26
+ warning('No containers found. Create one with: spindb create'),
27
+ )
28
+ } else {
29
+ console.log(
30
+ warning(
31
+ 'No running containers. Start one first with: spindb start',
32
+ ),
33
+ )
34
+ }
35
+ return
36
+ }
37
+
38
+ const selected = await promptContainerSelect(
39
+ running,
40
+ 'Select container to connect to:',
41
+ )
42
+ if (!selected) return
43
+ containerName = selected
44
+ }
45
+
46
+ // Get container config
47
+ const config = await containerManager.getConfig(containerName)
48
+ if (!config) {
49
+ console.error(error(`Container "${containerName}" not found`))
50
+ process.exit(1)
51
+ }
52
+
53
+ // Check if running
54
+ const running = await processManager.isRunning(containerName)
55
+ if (!running) {
56
+ console.error(
57
+ error(`Container "${containerName}" is not running. Start it first.`),
58
+ )
59
+ process.exit(1)
60
+ }
61
+
62
+ // Get engine
63
+ const engine = getEngine(config.engine)
64
+ const connectionString = engine.getConnectionString(
65
+ config,
66
+ options.database,
67
+ )
68
+
69
+ console.log(info(`Connecting to ${containerName}:${options.database}...`))
70
+ console.log()
71
+
72
+ // Try to use system psql (the bundled binaries don't include psql)
73
+ const psqlProcess = spawn('psql', [connectionString], {
74
+ stdio: 'inherit',
75
+ })
76
+
77
+ psqlProcess.on('error', (err: NodeJS.ErrnoException) => {
78
+ if (err.code === 'ENOENT') {
79
+ console.log(warning('psql not found on your system.'))
80
+ console.log()
81
+ console.log(
82
+ chalk.gray(
83
+ ' Install PostgreSQL client tools or connect manually:',
84
+ ),
85
+ )
86
+ console.log(chalk.cyan(` ${connectionString}`))
87
+ console.log()
88
+ console.log(chalk.gray(' On macOS with Homebrew:'))
89
+ console.log(
90
+ chalk.cyan(' brew install libpq && brew link --force libpq'),
91
+ )
92
+ console.log()
93
+ } else {
94
+ console.error(error(err.message))
95
+ }
96
+ })
97
+
98
+ await new Promise<void>((resolve) => {
99
+ psqlProcess.on('close', () => resolve())
100
+ })
101
+ } catch (err) {
102
+ const e = err as Error
103
+ console.error(error(e.message))
104
+ process.exit(1)
105
+ }
106
+ })
@@ -0,0 +1,148 @@
1
+ import { Command } from 'commander'
2
+ import chalk from 'chalk'
3
+ import { containerManager } from '@/core/container-manager'
4
+ import { portManager } from '@/core/port-manager'
5
+ import { getEngine } from '@/engines'
6
+ import { defaults } from '@/config/defaults'
7
+ import { promptCreateOptions } from '@/cli/ui/prompts'
8
+ import { createSpinner } from '@/cli/ui/spinner'
9
+ import { header, error, connectionBox } from '@/cli/ui/theme'
10
+
11
+ export const createCommand = new Command('create')
12
+ .description('Create a new database container')
13
+ .argument('[name]', 'Container name')
14
+ .option('-e, --engine <engine>', 'Database engine', defaults.engine)
15
+ .option(
16
+ '--pg-version <version>',
17
+ 'PostgreSQL version',
18
+ defaults.postgresVersion,
19
+ )
20
+ .option('-p, --port <port>', 'Port number')
21
+ .option('--no-start', 'Do not start the container after creation')
22
+ .action(
23
+ async (
24
+ name: string | undefined,
25
+ options: {
26
+ engine: string
27
+ pgVersion: string
28
+ port?: string
29
+ start: boolean
30
+ },
31
+ ) => {
32
+ try {
33
+ let containerName = name
34
+ let engine = options.engine
35
+ let version = options.pgVersion
36
+
37
+ // Interactive mode if no name provided
38
+ if (!containerName) {
39
+ const answers = await promptCreateOptions()
40
+ containerName = answers.name
41
+ engine = answers.engine
42
+ version = answers.version
43
+ }
44
+
45
+ console.log(header('Creating Database Container'))
46
+ console.log()
47
+
48
+ // Get the engine
49
+ const dbEngine = getEngine(engine)
50
+
51
+ // Find available port
52
+ const portSpinner = createSpinner('Finding available port...')
53
+ portSpinner.start()
54
+
55
+ let port: number
56
+ if (options.port) {
57
+ port = parseInt(options.port, 10)
58
+ const available = await portManager.isPortAvailable(port)
59
+ if (!available) {
60
+ portSpinner.fail(`Port ${port} is already in use`)
61
+ process.exit(1)
62
+ }
63
+ portSpinner.succeed(`Using port ${port}`)
64
+ } else {
65
+ const { port: foundPort, isDefault } =
66
+ await portManager.findAvailablePort()
67
+ port = foundPort
68
+ if (isDefault) {
69
+ portSpinner.succeed(`Using default port ${port}`)
70
+ } else {
71
+ portSpinner.warn(`Default port 5432 is in use, using port ${port}`)
72
+ }
73
+ }
74
+
75
+ // Ensure binaries are available
76
+ const binarySpinner = createSpinner(
77
+ `Checking PostgreSQL ${version} binaries...`,
78
+ )
79
+ binarySpinner.start()
80
+
81
+ const isInstalled = await dbEngine.isBinaryInstalled(version)
82
+ if (isInstalled) {
83
+ binarySpinner.succeed(`PostgreSQL ${version} binaries ready (cached)`)
84
+ } else {
85
+ binarySpinner.text = `Downloading PostgreSQL ${version} binaries...`
86
+ await dbEngine.ensureBinaries(version, ({ message }) => {
87
+ binarySpinner.text = message
88
+ })
89
+ binarySpinner.succeed(`PostgreSQL ${version} binaries downloaded`)
90
+ }
91
+
92
+ // Create container
93
+ const createSpinnerInstance = createSpinner('Creating container...')
94
+ createSpinnerInstance.start()
95
+
96
+ await containerManager.create(containerName, {
97
+ engine: dbEngine.name,
98
+ version,
99
+ port,
100
+ })
101
+
102
+ createSpinnerInstance.succeed('Container created')
103
+
104
+ // Initialize database
105
+ const initSpinner = createSpinner('Initializing database...')
106
+ initSpinner.start()
107
+
108
+ await dbEngine.initDataDir(containerName, version, {
109
+ superuser: defaults.superuser,
110
+ })
111
+
112
+ initSpinner.succeed('Database initialized')
113
+
114
+ // Start container if requested
115
+ if (options.start !== false) {
116
+ const startSpinner = createSpinner('Starting PostgreSQL...')
117
+ startSpinner.start()
118
+
119
+ const config = await containerManager.getConfig(containerName)
120
+ if (config) {
121
+ await dbEngine.start(config)
122
+ await containerManager.updateConfig(containerName, {
123
+ status: 'running',
124
+ })
125
+ }
126
+
127
+ startSpinner.succeed('PostgreSQL started')
128
+ }
129
+
130
+ // Show success message
131
+ const config = await containerManager.getConfig(containerName)
132
+ if (config) {
133
+ const connectionString = dbEngine.getConnectionString(config)
134
+
135
+ console.log()
136
+ console.log(connectionBox(containerName, connectionString, port))
137
+ console.log()
138
+ console.log(chalk.gray(' Connect with:'))
139
+ console.log(chalk.cyan(` spindb connect ${containerName}`))
140
+ console.log()
141
+ }
142
+ } catch (err) {
143
+ const e = err as Error
144
+ console.error(error(e.message))
145
+ process.exit(1)
146
+ }
147
+ },
148
+ )
@@ -0,0 +1,94 @@
1
+ import { Command } from 'commander'
2
+ import { containerManager } from '@/core/container-manager'
3
+ import { processManager } from '@/core/process-manager'
4
+ import { getEngine } from '@/engines'
5
+ import { promptContainerSelect, promptConfirm } from '@/cli/ui/prompts'
6
+ import { createSpinner } from '@/cli/ui/spinner'
7
+ import { error, warning } from '@/cli/ui/theme'
8
+
9
+ export const deleteCommand = new Command('delete')
10
+ .alias('rm')
11
+ .description('Delete a container')
12
+ .argument('[name]', 'Container name')
13
+ .option('-f, --force', 'Force delete (stop if running)')
14
+ .option('-y, --yes', 'Skip confirmation')
15
+ .action(
16
+ async (
17
+ name: string | undefined,
18
+ options: { force?: boolean; yes?: boolean },
19
+ ) => {
20
+ try {
21
+ let containerName = name
22
+
23
+ // Interactive selection if no name provided
24
+ if (!containerName) {
25
+ const containers = await containerManager.list()
26
+
27
+ if (containers.length === 0) {
28
+ console.log(warning('No containers found'))
29
+ return
30
+ }
31
+
32
+ const selected = await promptContainerSelect(
33
+ containers,
34
+ 'Select container to delete:',
35
+ )
36
+ if (!selected) return
37
+ containerName = selected
38
+ }
39
+
40
+ // Get container config
41
+ const config = await containerManager.getConfig(containerName)
42
+ if (!config) {
43
+ console.error(error(`Container "${containerName}" not found`))
44
+ process.exit(1)
45
+ }
46
+
47
+ // Confirm deletion
48
+ if (!options.yes) {
49
+ const confirmed = await promptConfirm(
50
+ `Are you sure you want to delete "${containerName}"? This cannot be undone.`,
51
+ false,
52
+ )
53
+ if (!confirmed) {
54
+ console.log(warning('Deletion cancelled'))
55
+ return
56
+ }
57
+ }
58
+
59
+ // Check if running
60
+ const running = await processManager.isRunning(containerName)
61
+ if (running) {
62
+ if (options.force) {
63
+ // Stop the container first
64
+ const stopSpinner = createSpinner(`Stopping ${containerName}...`)
65
+ stopSpinner.start()
66
+
67
+ const engine = getEngine(config.engine)
68
+ await engine.stop(config)
69
+
70
+ stopSpinner.succeed(`Stopped "${containerName}"`)
71
+ } else {
72
+ console.error(
73
+ error(
74
+ `Container "${containerName}" is running. Stop it first or use --force`,
75
+ ),
76
+ )
77
+ process.exit(1)
78
+ }
79
+ }
80
+
81
+ // Delete the container
82
+ const deleteSpinner = createSpinner(`Deleting ${containerName}...`)
83
+ deleteSpinner.start()
84
+
85
+ await containerManager.delete(containerName, { force: true })
86
+
87
+ deleteSpinner.succeed(`Container "${containerName}" deleted`)
88
+ } catch (err) {
89
+ const e = err as Error
90
+ console.error(error(e.message))
91
+ process.exit(1)
92
+ }
93
+ },
94
+ )