spindb 0.38.1 → 0.40.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.
- package/README.md +1 -1
- package/dist/cli/commands/backup.js +13 -2
- package/dist/cli/commands/backup.js.map +1 -1
- package/dist/cli/commands/clone.js +13 -1
- package/dist/cli/commands/clone.js.map +1 -1
- package/dist/cli/commands/connect.js +112 -40
- package/dist/cli/commands/connect.js.map +1 -1
- package/dist/cli/commands/delete.js +27 -0
- package/dist/cli/commands/delete.js.map +1 -1
- package/dist/cli/commands/engines.js +1 -1
- package/dist/cli/commands/engines.js.map +1 -1
- package/dist/cli/commands/export.js +12 -1
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/info.js +54 -12
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/link.js +215 -0
- package/dist/cli/commands/link.js.map +1 -0
- package/dist/cli/commands/list.js +28 -7
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/logs.js +6 -0
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/menu/container-handlers.js +260 -32
- package/dist/cli/commands/menu/container-handlers.js.map +1 -1
- package/dist/cli/commands/menu/index.js +20 -4
- package/dist/cli/commands/menu/index.js.map +1 -1
- package/dist/cli/commands/menu/shell-handlers.js +206 -75
- package/dist/cli/commands/menu/shell-handlers.js.map +1 -1
- package/dist/cli/commands/query.js +35 -4
- package/dist/cli/commands/query.js.map +1 -1
- package/dist/cli/commands/restore.js +12 -1
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/run.js +6 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/start.js +17 -2
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/stop.js +16 -1
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/cli/commands/url.js +59 -15
- package/dist/cli/commands/url.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/version.js +1 -1
- package/dist/core/container-manager.js +15 -1
- package/dist/core/container-manager.js.map +1 -1
- package/dist/core/remote-container.js +228 -0
- package/dist/core/remote-container.js.map +1 -0
- package/dist/engines/ferretdb/index.js +29 -10
- package/dist/engines/ferretdb/index.js.map +1 -1
- package/dist/engines/ferretdb/restore.js.map +1 -1
- package/dist/engines/mariadb/index.js +10 -2
- package/dist/engines/mariadb/index.js.map +1 -1
- package/dist/engines/mongodb/index.js +25 -3
- package/dist/engines/mongodb/index.js.map +1 -1
- package/dist/engines/mysql/index.js +10 -2
- package/dist/engines/mysql/index.js.map +1 -1
- package/dist/engines/postgresql/index.js +14 -2
- package/dist/engines/postgresql/index.js.map +1 -1
- package/dist/engines/redis/index.js +8 -1
- package/dist/engines/redis/index.js.map +1 -1
- package/dist/engines/valkey/index.js +8 -1
- package/dist/engines/valkey/index.js.map +1 -1
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -25,12 +25,13 @@ import { handleOpenShell, handleCopyConnectionString, stopPgwebProcess, } from '
|
|
|
25
25
|
import { getPgwebStatus } from '../../../core/pgweb-utils.js';
|
|
26
26
|
import { generatePassword } from '../../../core/credential-generator.js';
|
|
27
27
|
import { saveCredentials, credentialsExist, getDefaultUsername, } from '../../../core/credential-manager.js';
|
|
28
|
-
import { UnsupportedOperationError, isValidUsername, } from '../../../core/error-handler.js';
|
|
28
|
+
import { UnsupportedOperationError, isValidUsername, logDebug, } from '../../../core/error-handler.js';
|
|
29
29
|
import { handleRunSql, handleViewLogs } from './sql-handlers.js';
|
|
30
30
|
import { handleBackupForContainer, handleRestoreForContainer, } from './backup-handlers.js';
|
|
31
31
|
import { exportToDocker, getExportBackupPath, dockerExportExists, getDockerConnectionString, } from '../../../core/docker-exporter.js';
|
|
32
32
|
import { getDefaultFormat } from '../../../config/backup-formats.js';
|
|
33
|
-
import {
|
|
33
|
+
import { parseConnectionString, detectEngineFromConnectionString, detectProvider, isLocalhost, generateRemoteContainerName, redactConnectionString, buildRemoteConfig, getDefaultPortForEngine, } from '../../../core/remote-container.js';
|
|
34
|
+
import { Engine, isFileBasedEngine, isRemoteContainer } from '../../../types/index.js';
|
|
34
35
|
import { pressEnterToContinue } from './shared.js';
|
|
35
36
|
import { getEngineIcon } from '../../constants.js';
|
|
36
37
|
export async function handleCreate() {
|
|
@@ -357,6 +358,141 @@ export async function handleCreate() {
|
|
|
357
358
|
}
|
|
358
359
|
return containerNameFinal;
|
|
359
360
|
}
|
|
361
|
+
export async function handleLinkRemote() {
|
|
362
|
+
console.log();
|
|
363
|
+
console.log(header('Link Remote Database'));
|
|
364
|
+
console.log();
|
|
365
|
+
// Step 1: Prompt for connection string
|
|
366
|
+
console.log(chalk.gray(' Passwords are automatically masked.'));
|
|
367
|
+
console.log();
|
|
368
|
+
const { connectionString } = await escapeablePrompt([
|
|
369
|
+
{
|
|
370
|
+
type: 'input',
|
|
371
|
+
name: 'connectionString',
|
|
372
|
+
message: 'Connection string:',
|
|
373
|
+
transformer: (input) => redactConnectionString(input.trim()),
|
|
374
|
+
validate: (input) => {
|
|
375
|
+
if (!input.trim())
|
|
376
|
+
return 'Connection string is required';
|
|
377
|
+
try {
|
|
378
|
+
parseConnectionString(input);
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
return error.message;
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
]);
|
|
387
|
+
const parsed = parseConnectionString(connectionString);
|
|
388
|
+
// Step 2: Detect engine
|
|
389
|
+
const detectedEngine = detectEngineFromConnectionString(connectionString);
|
|
390
|
+
let engine;
|
|
391
|
+
if (detectedEngine) {
|
|
392
|
+
engine = detectedEngine;
|
|
393
|
+
console.log(chalk.gray(` Detected engine: ${engine}`));
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
console.log(uiWarning('Could not detect engine from connection string. Please specify.'));
|
|
397
|
+
const { engineInput } = await escapeablePrompt([
|
|
398
|
+
{
|
|
399
|
+
type: 'input',
|
|
400
|
+
name: 'engineInput',
|
|
401
|
+
message: 'Engine (postgresql, mysql, mongodb, redis):',
|
|
402
|
+
validate: (input) => {
|
|
403
|
+
if (!input.trim())
|
|
404
|
+
return 'Engine is required';
|
|
405
|
+
const values = Object.values(Engine);
|
|
406
|
+
if (!values.includes(input.toLowerCase())) {
|
|
407
|
+
return `Unknown engine. Valid: ${values.join(', ')}`;
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
]);
|
|
413
|
+
engine = engineInput.toLowerCase();
|
|
414
|
+
}
|
|
415
|
+
// Extract details
|
|
416
|
+
const host = parsed.host;
|
|
417
|
+
const port = parsed.port ?? getDefaultPortForEngine(engine);
|
|
418
|
+
const database = parsed.database || 'default';
|
|
419
|
+
const provider = detectProvider(host);
|
|
420
|
+
// SpinDB collision check
|
|
421
|
+
if (isLocalhost(host) && port > 0) {
|
|
422
|
+
const containers = await containerManager.list();
|
|
423
|
+
const conflicting = containers.find((c) => c.engine === engine && c.port === port && c.status !== 'linked');
|
|
424
|
+
if (conflicting) {
|
|
425
|
+
console.log(uiError(`Port ${port} is already managed by SpinDB container "${conflicting.name}". Use "spindb connect ${conflicting.name}" instead.`));
|
|
426
|
+
await pressEnterToContinue();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Step 3: Container name
|
|
431
|
+
const defaultName = generateRemoteContainerName({
|
|
432
|
+
engine,
|
|
433
|
+
host,
|
|
434
|
+
database,
|
|
435
|
+
provider,
|
|
436
|
+
});
|
|
437
|
+
let containerName = await promptContainerName(defaultName);
|
|
438
|
+
if (!containerName)
|
|
439
|
+
return;
|
|
440
|
+
// Check uniqueness — re-prompt until unique (same pattern as create command)
|
|
441
|
+
while (await containerManager.exists(containerName, { engine })) {
|
|
442
|
+
console.log(chalk.yellow(` Container "${containerName}" already exists.`));
|
|
443
|
+
containerName = await promptContainerName();
|
|
444
|
+
if (!containerName)
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// Create container
|
|
448
|
+
const containerPath = paths.getContainerPath(containerName, { engine });
|
|
449
|
+
await mkdir(containerPath, { recursive: true });
|
|
450
|
+
const remoteConfig = buildRemoteConfig({
|
|
451
|
+
host,
|
|
452
|
+
connectionString,
|
|
453
|
+
provider,
|
|
454
|
+
});
|
|
455
|
+
const config = {
|
|
456
|
+
name: containerName,
|
|
457
|
+
engine,
|
|
458
|
+
version: 'unknown',
|
|
459
|
+
port,
|
|
460
|
+
database,
|
|
461
|
+
databases: [database],
|
|
462
|
+
created: new Date().toISOString(),
|
|
463
|
+
status: 'linked',
|
|
464
|
+
remote: remoteConfig,
|
|
465
|
+
};
|
|
466
|
+
await containerManager.saveConfig(containerName, { engine }, config);
|
|
467
|
+
// Save credentials — always use 'remote' as credential key for linked containers
|
|
468
|
+
try {
|
|
469
|
+
await saveCredentials(containerName, engine, {
|
|
470
|
+
username: 'remote',
|
|
471
|
+
password: parsed.password || '',
|
|
472
|
+
connectionString,
|
|
473
|
+
engine,
|
|
474
|
+
container: containerName,
|
|
475
|
+
database,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
catch (credError) {
|
|
479
|
+
console.log(uiWarning('Could not save credentials. The full connection string may not be retrievable.'));
|
|
480
|
+
logDebug(`Credential save failed: ${credError.message}`);
|
|
481
|
+
}
|
|
482
|
+
console.log();
|
|
483
|
+
console.log(uiSuccess(`Linked remote database as "${containerName}"`));
|
|
484
|
+
console.log();
|
|
485
|
+
console.log(chalk.gray(' ') + chalk.white('Engine:'.padEnd(14)) + chalk.cyan(engine));
|
|
486
|
+
console.log(chalk.gray(' ') + chalk.white('Host:'.padEnd(14)) + chalk.cyan(host));
|
|
487
|
+
if (provider) {
|
|
488
|
+
console.log(chalk.gray(' ') +
|
|
489
|
+
chalk.white('Provider:'.padEnd(14)) +
|
|
490
|
+
chalk.magenta(provider));
|
|
491
|
+
}
|
|
492
|
+
console.log();
|
|
493
|
+
await pressEnterToContinue();
|
|
494
|
+
return containerName;
|
|
495
|
+
}
|
|
360
496
|
export async function handleList(showMainMenu, options) {
|
|
361
497
|
console.clear();
|
|
362
498
|
console.log(header('Containers'));
|
|
@@ -400,20 +536,27 @@ export async function handleList(showMainMenu, options) {
|
|
|
400
536
|
const containerChoices = containers.map((c, i) => {
|
|
401
537
|
const size = sizes[i];
|
|
402
538
|
const isFileBased = isFileBasedEngine(c.engine);
|
|
539
|
+
const isLinked = c.status === 'linked';
|
|
403
540
|
// Status display
|
|
404
|
-
const statusDisplay =
|
|
405
|
-
?
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
:
|
|
541
|
+
const statusDisplay = isLinked
|
|
542
|
+
? chalk.magenta('↔ linked')
|
|
543
|
+
: isFileBased
|
|
544
|
+
? c.status === 'running'
|
|
545
|
+
? chalk.blue('● available')
|
|
546
|
+
: chalk.gray('○ missing')
|
|
547
|
+
: c.status === 'running'
|
|
548
|
+
? chalk.green('● running')
|
|
549
|
+
: chalk.gray('○ stopped');
|
|
411
550
|
// Truncate name if too long
|
|
412
551
|
const displayName = c.name.length > COL_NAME - 1
|
|
413
552
|
? c.name.slice(0, COL_NAME - 2) + '…'
|
|
414
553
|
: c.name;
|
|
415
|
-
// Port or dash for file-based
|
|
416
|
-
const portDisplay =
|
|
554
|
+
// Port, provider for linked, or dash for file-based
|
|
555
|
+
const portDisplay = isLinked
|
|
556
|
+
? (c.remote?.provider || 'remote').slice(0, COL_PORT - 1)
|
|
557
|
+
: isFileBased
|
|
558
|
+
? '—'
|
|
559
|
+
: String(c.port);
|
|
417
560
|
// Size display
|
|
418
561
|
const sizeDisplay = size !== null ? formatBytes(size) : '—';
|
|
419
562
|
// Build formatted row
|
|
@@ -437,8 +580,10 @@ export async function handleList(showMainMenu, options) {
|
|
|
437
580
|
};
|
|
438
581
|
});
|
|
439
582
|
// Calculate summary
|
|
440
|
-
const
|
|
441
|
-
const
|
|
583
|
+
const linkedContainers = containers.filter((c) => c.status === 'linked');
|
|
584
|
+
const localContainers = containers.filter((c) => c.status !== 'linked');
|
|
585
|
+
const serverContainers = localContainers.filter((c) => !isFileBasedEngine(c.engine));
|
|
586
|
+
const fileBasedContainers = localContainers.filter((c) => isFileBasedEngine(c.engine));
|
|
442
587
|
const running = serverContainers.filter((c) => c.status === 'running').length;
|
|
443
588
|
const stopped = serverContainers.filter((c) => c.status !== 'running').length;
|
|
444
589
|
const available = fileBasedContainers.filter((c) => c.status === 'running').length;
|
|
@@ -450,8 +595,11 @@ export async function handleList(showMainMenu, options) {
|
|
|
450
595
|
if (fileBasedContainers.length > 0) {
|
|
451
596
|
parts.push(`${available} file-based available${missing > 0 ? `, ${missing} missing` : ''}`);
|
|
452
597
|
}
|
|
453
|
-
|
|
454
|
-
|
|
598
|
+
if (linkedContainers.length > 0) {
|
|
599
|
+
parts.push(`${linkedContainers.length} linked`);
|
|
600
|
+
}
|
|
601
|
+
// Check if there are any server-based (toggleable) containers (exclude linked)
|
|
602
|
+
const hasServerContainers = containers.some((c) => !isFileBasedEngine(c.engine) && c.status !== 'linked');
|
|
455
603
|
// Build the full choice list with footer items
|
|
456
604
|
// IMPORTANT: Containers must come FIRST because filterableCount slices from index 0
|
|
457
605
|
const summary = `${containers.length} container(s): ${parts.join('; ')}`;
|
|
@@ -484,7 +632,9 @@ export async function handleList(showMainMenu, options) {
|
|
|
484
632
|
if (selectedContainer.startsWith(TOGGLE_PREFIX)) {
|
|
485
633
|
const containerName = selectedContainer.slice(TOGGLE_PREFIX.length);
|
|
486
634
|
const config = await containerManager.getConfig(containerName);
|
|
487
|
-
if (config &&
|
|
635
|
+
if (config &&
|
|
636
|
+
!isFileBasedEngine(config.engine) &&
|
|
637
|
+
!isRemoteContainer(config)) {
|
|
488
638
|
const isRunning = await processManager.isRunning(containerName, {
|
|
489
639
|
engine: config.engine,
|
|
490
640
|
});
|
|
@@ -530,6 +680,7 @@ export async function showContainerSubmenu(containerName, showMainMenu, selected
|
|
|
530
680
|
console.error(uiError(`Container "${containerName}" not found`));
|
|
531
681
|
return;
|
|
532
682
|
}
|
|
683
|
+
const isRemote = isRemoteContainer(config);
|
|
533
684
|
// File-based databases: Check file existence instead of running status
|
|
534
685
|
const isSQLite = config.engine === Engine.SQLite;
|
|
535
686
|
const isDuckDB = config.engine === Engine.DuckDB;
|
|
@@ -537,7 +688,12 @@ export async function showContainerSubmenu(containerName, showMainMenu, selected
|
|
|
537
688
|
let isRunning;
|
|
538
689
|
let status;
|
|
539
690
|
let locationInfo;
|
|
540
|
-
if (
|
|
691
|
+
if (isRemote) {
|
|
692
|
+
isRunning = false;
|
|
693
|
+
status = 'linked';
|
|
694
|
+
locationInfo = `→ ${config.remote?.provider || config.remote?.host || 'remote'}`;
|
|
695
|
+
}
|
|
696
|
+
else if (isFileBasedDB) {
|
|
541
697
|
const fileExists = existsSync(config.database);
|
|
542
698
|
isRunning = fileExists; // For file-based DBs, "running" means "file exists"
|
|
543
699
|
status = fileExists ? 'available' : 'missing';
|
|
@@ -571,6 +727,63 @@ export async function showContainerSubmenu(containerName, showMainMenu, selected
|
|
|
571
727
|
disabled: '', // Empty string hides the "(Disabled)" text
|
|
572
728
|
};
|
|
573
729
|
}
|
|
730
|
+
// Remote containers get a simplified action set
|
|
731
|
+
if (isRemote) {
|
|
732
|
+
actionChoices.push(new inquirer.Separator(chalk.gray(`── Linked ──`)));
|
|
733
|
+
// Connect (open console)
|
|
734
|
+
actionChoices.push({
|
|
735
|
+
name: `${chalk.blue('>')} Open console`,
|
|
736
|
+
value: 'shell',
|
|
737
|
+
});
|
|
738
|
+
// Copy connection string
|
|
739
|
+
actionChoices.push({
|
|
740
|
+
name: `${chalk.green('⎘')} Copy connection string`,
|
|
741
|
+
value: 'copy',
|
|
742
|
+
});
|
|
743
|
+
actionChoices.push(new inquirer.Separator());
|
|
744
|
+
// Unlink (delete)
|
|
745
|
+
actionChoices.push({
|
|
746
|
+
name: `${chalk.red('✕')} Unlink remote database`,
|
|
747
|
+
value: 'delete',
|
|
748
|
+
});
|
|
749
|
+
actionChoices.push(new inquirer.Separator());
|
|
750
|
+
// Navigation
|
|
751
|
+
actionChoices.push({
|
|
752
|
+
name: `${chalk.blue('←')} Back to containers`,
|
|
753
|
+
value: 'back',
|
|
754
|
+
}, {
|
|
755
|
+
name: `${chalk.blue('⌂')} Back to main menu ${chalk.gray('(esc)')}`,
|
|
756
|
+
value: 'main',
|
|
757
|
+
}, new inquirer.Separator());
|
|
758
|
+
const { action } = await escapeablePrompt([
|
|
759
|
+
{
|
|
760
|
+
type: 'list',
|
|
761
|
+
name: 'action',
|
|
762
|
+
message: 'What would you like to do?',
|
|
763
|
+
choices: actionChoices,
|
|
764
|
+
pageSize: getPageSize(),
|
|
765
|
+
},
|
|
766
|
+
]);
|
|
767
|
+
switch (action) {
|
|
768
|
+
case 'shell':
|
|
769
|
+
await handleOpenShell(containerName, activeDatabase);
|
|
770
|
+
await showContainerSubmenu(containerName, showMainMenu, activeDatabase);
|
|
771
|
+
return;
|
|
772
|
+
case 'copy':
|
|
773
|
+
await handleCopyConnectionString(containerName, activeDatabase);
|
|
774
|
+
await showContainerSubmenu(containerName, showMainMenu, activeDatabase);
|
|
775
|
+
return;
|
|
776
|
+
case 'delete':
|
|
777
|
+
await handleDelete(containerName);
|
|
778
|
+
return;
|
|
779
|
+
case 'back':
|
|
780
|
+
await handleList(showMainMenu);
|
|
781
|
+
return;
|
|
782
|
+
case 'main':
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
574
787
|
// Determine if database-specific actions can be performed
|
|
575
788
|
// Requires: database selected + (running for server DBs OR file exists for file-based DBs)
|
|
576
789
|
const containerReady = isFileBasedDB ? existsSync(config.database) : isRunning;
|
|
@@ -838,8 +1051,10 @@ export async function showContainerSubmenu(containerName, showMainMenu, selected
|
|
|
838
1051
|
}
|
|
839
1052
|
export async function handleStart() {
|
|
840
1053
|
const containers = await containerManager.list();
|
|
841
|
-
// Filter for stopped containers, excluding file-based DBs
|
|
842
|
-
const stopped = containers.filter((c) => c.status !== 'running' &&
|
|
1054
|
+
// Filter for stopped containers, excluding file-based DBs and linked containers
|
|
1055
|
+
const stopped = containers.filter((c) => c.status !== 'running' &&
|
|
1056
|
+
c.status !== 'linked' &&
|
|
1057
|
+
!isFileBasedEngine(c.engine));
|
|
843
1058
|
if (stopped.length === 0) {
|
|
844
1059
|
console.log(uiWarning('All containers are already running'));
|
|
845
1060
|
return;
|
|
@@ -1397,25 +1612,38 @@ async function handleDelete(containerName) {
|
|
|
1397
1612
|
console.error(uiError(`Container "${containerName}" not found`));
|
|
1398
1613
|
return;
|
|
1399
1614
|
}
|
|
1400
|
-
const
|
|
1615
|
+
const isRemote = isRemoteContainer(config);
|
|
1616
|
+
const confirmMsg = isRemote
|
|
1617
|
+
? `Unlink "${containerName}"? The remote database will not be affected.`
|
|
1618
|
+
: `Are you sure you want to delete "${containerName}"? This cannot be undone.`;
|
|
1619
|
+
const confirmed = await promptConfirm(confirmMsg, false);
|
|
1401
1620
|
if (!confirmed) {
|
|
1402
|
-
console.log(uiWarning('Deletion cancelled'));
|
|
1621
|
+
console.log(uiWarning(isRemote ? 'Unlink cancelled' : 'Deletion cancelled'));
|
|
1403
1622
|
return;
|
|
1404
1623
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1624
|
+
// Remote containers: skip process checks
|
|
1625
|
+
if (!isRemote) {
|
|
1626
|
+
const running = await processManager.isRunning(containerName, {
|
|
1627
|
+
engine: config.engine,
|
|
1628
|
+
});
|
|
1629
|
+
if (running) {
|
|
1630
|
+
const stopSpinner = createSpinner(`Stopping ${containerName}...`);
|
|
1631
|
+
stopSpinner.start();
|
|
1632
|
+
const engine = getEngine(config.engine);
|
|
1633
|
+
await engine.stop(config);
|
|
1634
|
+
stopSpinner.succeed(`Stopped "${containerName}"`);
|
|
1635
|
+
}
|
|
1414
1636
|
}
|
|
1415
|
-
const deleteSpinner = createSpinner(`Deleting ${containerName}...`);
|
|
1637
|
+
const deleteSpinner = createSpinner(isRemote ? `Unlinking ${containerName}...` : `Deleting ${containerName}...`);
|
|
1416
1638
|
deleteSpinner.start();
|
|
1417
1639
|
await containerManager.delete(containerName, { force: true });
|
|
1418
|
-
|
|
1640
|
+
if (isRemote) {
|
|
1641
|
+
deleteSpinner.succeed(`Unlinked "${containerName}"`);
|
|
1642
|
+
console.log(chalk.gray(' The remote database is not affected.'));
|
|
1643
|
+
}
|
|
1644
|
+
else {
|
|
1645
|
+
deleteSpinner.succeed(`Container "${containerName}" deleted`);
|
|
1646
|
+
}
|
|
1419
1647
|
}
|
|
1420
1648
|
async function isDockerContainerRunning(containerName) {
|
|
1421
1649
|
try {
|