spindb 0.19.4 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +24 -25
  2. package/cli/commands/backup.ts +42 -64
  3. package/cli/commands/create.ts +30 -13
  4. package/cli/commands/deps.ts +2 -1
  5. package/cli/commands/engines.ts +4 -4
  6. package/cli/commands/menu/backup-handlers.ts +18 -85
  7. package/cli/commands/restore.ts +95 -49
  8. package/cli/ui/prompts.ts +33 -37
  9. package/config/backup-formats.ts +204 -133
  10. package/config/engine-defaults.ts +29 -41
  11. package/config/paths.ts +6 -2
  12. package/core/backup-restore.ts +2 -2
  13. package/core/base-binary-manager.ts +515 -0
  14. package/core/base-document-binary-manager.ts +504 -0
  15. package/core/base-embedded-binary-manager.ts +538 -0
  16. package/core/base-server-binary-manager.ts +507 -0
  17. package/core/container-manager.ts +20 -0
  18. package/core/fs-error-utils.ts +79 -0
  19. package/core/homebrew-version-manager.ts +9 -8
  20. package/core/hostdb-client.ts +71 -35
  21. package/core/hostdb-metadata.ts +8 -7
  22. package/core/hostdb-releases-factory.ts +237 -0
  23. package/core/platform-service.ts +31 -14
  24. package/core/start-with-retry.ts +1 -11
  25. package/core/version-utils.ts +27 -0
  26. package/engines/clickhouse/backup.ts +15 -1
  27. package/engines/clickhouse/binary-manager.ts +63 -292
  28. package/engines/clickhouse/binary-urls.ts +19 -47
  29. package/engines/clickhouse/hostdb-releases.ts +15 -187
  30. package/engines/clickhouse/index.ts +52 -14
  31. package/engines/clickhouse/restore.ts +54 -11
  32. package/engines/clickhouse/version-maps.ts +6 -3
  33. package/engines/duckdb/binary-manager.ts +22 -450
  34. package/engines/duckdb/binary-urls.ts +11 -98
  35. package/engines/duckdb/hostdb-releases.ts +23 -0
  36. package/engines/duckdb/index.ts +107 -44
  37. package/engines/duckdb/version-maps.ts +19 -6
  38. package/engines/mariadb/backup.ts +7 -2
  39. package/engines/mariadb/binary-manager.ts +24 -355
  40. package/engines/mariadb/binary-urls.ts +31 -50
  41. package/engines/mariadb/hostdb-releases.ts +14 -177
  42. package/engines/mariadb/index.ts +20 -18
  43. package/engines/mariadb/restore.ts +2 -2
  44. package/engines/mariadb/version-maps.ts +4 -3
  45. package/engines/mongodb/backup.ts +9 -8
  46. package/engines/mongodb/binary-manager.ts +26 -413
  47. package/engines/mongodb/binary-urls.ts +23 -31
  48. package/engines/mongodb/hostdb-releases.ts +3 -93
  49. package/engines/mongodb/index.ts +15 -13
  50. package/engines/mongodb/restore.ts +40 -9
  51. package/engines/mongodb/version-maps.ts +4 -2
  52. package/engines/mysql/binary-detection.ts +7 -6
  53. package/engines/mysql/binary-manager.ts +21 -374
  54. package/engines/mysql/binary-urls.ts +20 -38
  55. package/engines/mysql/hostdb-releases.ts +26 -184
  56. package/engines/mysql/index.ts +21 -16
  57. package/engines/mysql/version-maps.ts +7 -4
  58. package/engines/postgresql/binary-manager.ts +90 -576
  59. package/engines/postgresql/binary-urls.ts +31 -67
  60. package/engines/postgresql/hostdb-releases.ts +14 -175
  61. package/engines/postgresql/index.ts +33 -79
  62. package/engines/postgresql/restore.ts +24 -18
  63. package/engines/postgresql/version-maps.ts +0 -1
  64. package/engines/redis/backup.ts +5 -5
  65. package/engines/redis/binary-manager.ts +23 -450
  66. package/engines/redis/binary-urls.ts +25 -41
  67. package/engines/redis/hostdb-releases.ts +14 -168
  68. package/engines/redis/index.ts +20 -14
  69. package/engines/redis/restore.ts +51 -21
  70. package/engines/redis/version-maps.ts +5 -4
  71. package/engines/sqlite/binary-manager.ts +28 -455
  72. package/engines/sqlite/binary-urls.ts +11 -102
  73. package/engines/sqlite/hostdb-releases.ts +23 -0
  74. package/engines/sqlite/index.ts +60 -28
  75. package/engines/sqlite/version-maps.ts +4 -3
  76. package/engines/valkey/backup.ts +5 -5
  77. package/engines/valkey/binary-manager.ts +24 -452
  78. package/engines/valkey/binary-urls.ts +20 -41
  79. package/engines/valkey/hostdb-releases.ts +14 -173
  80. package/engines/valkey/index.ts +20 -14
  81. package/engines/valkey/restore.ts +51 -22
  82. package/engines/valkey/version-maps.ts +4 -3
  83. package/package.json +1 -1
  84. package/types/index.ts +89 -8
  85. package/core/binary-manager.ts +0 -799
  86. package/engines/postgresql/edb-binary-urls.ts +0 -161
package/README.md CHANGED
@@ -269,8 +269,8 @@ psql $(spindb url mydb)
269
269
  spindb backup mydb # Auto-generated filename
270
270
  spindb backup mydb --name production-backup # Custom name
271
271
  spindb backup mydb --output ./backups/ # Custom directory
272
- spindb backup mydb --format sql # SQL text format
273
- spindb backup mydb --format dump # Binary format
272
+ spindb backup mydb --format sql # SQL text format (PostgreSQL)
273
+ spindb backup mydb --format custom # Custom binary format (PostgreSQL)
274
274
 
275
275
  # Restore from backups
276
276
  spindb restore mydb backup.dump
@@ -430,7 +430,7 @@ spindb create modern --engine postgresql --db-version 18
430
430
 
431
431
  # Backup formats
432
432
  spindb backup myapp --format sql # Plain SQL (.sql)
433
- spindb backup myapp --format dump # Binary custom format (.dump)
433
+ spindb backup myapp --format custom # Binary custom format (.dump)
434
434
  ```
435
435
 
436
436
  **Versions:** 15, 16, 17, 18
@@ -563,12 +563,12 @@ Every engine supports backup and restore with engine-specific formats:
563
563
 
564
564
  | Format | Extension | Tool | Use Case |
565
565
  |--------|-----------|------|----------|
566
- | SQL | `.sql` | pg_dump | Human-readable, portable |
567
- | Custom | `.dump` | pg_dump -Fc | Compressed, faster restore |
566
+ | sql | `.sql` | pg_dump | Human-readable, portable |
567
+ | custom | `.dump` | pg_dump -Fc | Compressed, faster restore |
568
568
 
569
569
  ```bash
570
- spindb backup mydb --sql # Plain SQL
571
- spindb backup mydb --dump # Binary custom format
570
+ spindb backup mydb --format sql # Plain SQL
571
+ spindb backup mydb --format custom # Binary custom format
572
572
  spindb restore mydb backup.dump
573
573
  ```
574
574
 
@@ -576,23 +576,23 @@ spindb restore mydb backup.dump
576
576
 
577
577
  | Format | Extension | Tool | Use Case |
578
578
  |--------|-----------|------|----------|
579
- | SQL | `.sql` | mysqldump / mariadb-dump | Human-readable |
580
- | Compressed | `.sql.gz` | mysqldump + gzip | Smaller file size |
579
+ | sql | `.sql` | mysqldump / mariadb-dump | Human-readable |
580
+ | compressed | `.sql.gz` | mysqldump + gzip | Smaller file size |
581
581
 
582
582
  ```bash
583
- spindb backup mydb --sql # Plain SQL
584
- spindb backup mydb --dump # Compressed SQL
583
+ spindb backup mydb --format sql # Plain SQL
584
+ spindb backup mydb --format compressed # Compressed SQL
585
585
  ```
586
586
 
587
587
  ### MongoDB
588
588
 
589
589
  | Format | Extension | Tool | Use Case |
590
590
  |--------|-----------|------|----------|
591
- | BSON | `.bson` | mongodump | Binary, preserves all types |
592
- | Archive | `.archive` | mongodump --archive | Single compressed file |
591
+ | bson | _(directory)_ | mongodump | Binary, preserves all types |
592
+ | archive | `.archive` | mongodump --archive | Single compressed file |
593
593
 
594
594
  ```bash
595
- spindb backup mydb # BSON directory
595
+ spindb backup mydb --format bson # BSON directory
596
596
  spindb backup mydb --format archive # Single .archive file
597
597
  ```
598
598
 
@@ -600,12 +600,12 @@ spindb backup mydb --format archive # Single .archive file
600
600
 
601
601
  | Format | Extension | Tool | Use Case |
602
602
  |--------|-----------|------|----------|
603
- | RDB | `.rdb` | BGSAVE | Binary snapshot, requires restart |
604
- | Text | `.redis` / `.valkey` | Custom | Human-readable commands |
603
+ | rdb | `.rdb` | BGSAVE | Binary snapshot, requires stop/start |
604
+ | text | `.redis` / `.valkey` | Custom | Human-readable commands |
605
605
 
606
606
  ```bash
607
- spindb backup mydb --dump # RDB snapshot
608
- spindb backup mydb --sql # Text commands
607
+ spindb backup mydb --format rdb # RDB snapshot (default)
608
+ spindb backup mydb --format text # Text commands
609
609
 
610
610
  # Restore with merge or replace strategy
611
611
  spindb restore mydb backup.redis # Prompts: Replace all / Merge
@@ -615,23 +615,22 @@ spindb restore mydb backup.redis # Prompts: Replace all / Merge
615
615
 
616
616
  | Format | Extension | Tool | Use Case |
617
617
  |--------|-----------|------|----------|
618
- | SQL | `.sql` | .dump / duckdb | Human-readable |
619
- | Binary | `.sqlite` / `.duckdb` | File copy | Exact database copy |
618
+ | sql | `.sql` | .dump / duckdb | Human-readable |
619
+ | binary | `.sqlite` / `.duckdb` | File copy | Exact database copy |
620
620
 
621
621
  ```bash
622
- spindb backup mydb --sql # SQL dump
623
- spindb backup mydb --dump # Binary copy
622
+ spindb backup mydb --format sql # SQL dump
623
+ spindb backup mydb --format binary # Binary copy (default)
624
624
  ```
625
625
 
626
626
  ### ClickHouse
627
627
 
628
628
  | Format | Extension | Tool | Use Case |
629
629
  |--------|-----------|------|----------|
630
- | SQL | `.sql` | clickhouse-client | Plain SQL dump |
631
- | Native | `.clickhouse` | clickhouse-backup | Native format (future) |
630
+ | sql | `.sql` | clickhouse-client | Plain SQL dump |
632
631
 
633
632
  ```bash
634
- spindb backup mydb --sql # SQL dump
633
+ spindb backup mydb --format sql # SQL dump (only format)
635
634
  ```
636
635
 
637
636
  ---
@@ -14,6 +14,15 @@ import {
14
14
  import { createSpinner } from '../ui/spinner'
15
15
  import { uiSuccess, uiError, uiWarning, formatBytes } from '../ui/theme'
16
16
  import { getMissingDependencies } from '../../core/dependency-manager'
17
+ import { isFileBasedEngine } from '../../types'
18
+ import {
19
+ getBackupExtension,
20
+ getBackupSpinnerLabel,
21
+ getDefaultFormat,
22
+ isValidFormat,
23
+ getValidFormats,
24
+ } from '../../config/backup-formats'
25
+ import type { BackupFormatType } from '../../types'
17
26
 
18
27
  function generateTimestamp(): string {
19
28
  const now = new Date()
@@ -28,42 +37,6 @@ function generateDefaultFilename(
28
37
  return `${containerName}-${database}-backup-${timestamp}`
29
38
  }
30
39
 
31
- function getExtension(format: 'sql' | 'dump', engine: string): string {
32
- // Handle 'sql' format (human-readable option)
33
- if (format === 'sql') {
34
- // MongoDB uses BSON directory format for 'sql' choice
35
- return engine === 'mongodb' ? '' : '.sql'
36
- }
37
-
38
- // Handle 'dump' format (binary/compressed option)
39
- switch (engine) {
40
- case 'mysql':
41
- return '.sql.gz'
42
- case 'sqlite':
43
- return '.sqlite'
44
- case 'mongodb':
45
- return '.archive'
46
- case 'redis':
47
- return '.rdb'
48
- case 'postgresql':
49
- default:
50
- return '.dump'
51
- }
52
- }
53
-
54
- function getFormatDescription(format: 'sql' | 'dump', engine: string): string {
55
- if (engine === 'redis') {
56
- return 'RDB snapshot'
57
- }
58
- if (engine === 'mongodb') {
59
- return format === 'sql' ? 'BSON directory' : 'archive'
60
- }
61
- if (engine === 'sqlite') {
62
- return format === 'sql' ? 'SQL' : 'binary'
63
- }
64
- return format === 'sql' ? 'SQL' : 'dump'
65
- }
66
-
67
40
  export const backupCommand = new Command('backup')
68
41
  .description('Create a backup of a database')
69
42
  .argument('[container]', 'Container name')
@@ -73,9 +46,7 @@ export const backupCommand = new Command('backup')
73
46
  '-o, --output <path>',
74
47
  'Output directory (defaults to current directory)',
75
48
  )
76
- .option('--format <format>', 'Output format: sql or dump')
77
- .option('--sql', 'Output as plain SQL (shorthand for --format sql)')
78
- .option('--dump', 'Output as dump format (shorthand for --format dump)')
49
+ .option('--format <format>', 'Backup format (engine-specific, e.g., sql, custom, rdb, binary)')
79
50
  .option('-j, --json', 'Output result as JSON')
80
51
  .action(
81
52
  async (
@@ -85,8 +56,6 @@ export const backupCommand = new Command('backup')
85
56
  name?: string
86
57
  output?: string
87
58
  format?: string
88
- sql?: boolean
89
- dump?: boolean
90
59
  json?: boolean
91
60
  },
92
61
  ) => {
@@ -130,16 +99,19 @@ export const backupCommand = new Command('backup')
130
99
 
131
100
  const { engine: engineName } = config
132
101
 
133
- const running = await processManager.isRunning(containerName, {
134
- engine: engineName,
135
- })
136
- if (!running) {
137
- console.error(
138
- uiError(
139
- `Container "${containerName}" is not running. Start it first.`,
140
- ),
141
- )
142
- process.exit(1)
102
+ // File-based engines (SQLite, DuckDB) don't need to be "running"
103
+ if (!isFileBasedEngine(engineName)) {
104
+ const running = await processManager.isRunning(containerName, {
105
+ engine: engineName,
106
+ })
107
+ if (!running) {
108
+ console.error(
109
+ uiError(
110
+ `Container "${containerName}" is not running. Start it first.`,
111
+ ),
112
+ )
113
+ process.exit(1)
114
+ }
143
115
  }
144
116
 
145
117
  const engine = getEngine(engineName)
@@ -193,20 +165,26 @@ export const backupCommand = new Command('backup')
193
165
  }
194
166
  }
195
167
 
196
- let format: 'sql' | 'dump' = 'sql'
168
+ let format: BackupFormatType = getDefaultFormat(engineName)
197
169
 
198
- if (options.sql) {
199
- format = 'sql'
200
- } else if (options.dump) {
201
- format = 'dump'
202
- } else if (options.format) {
203
- if (options.format !== 'sql' && options.format !== 'dump') {
204
- console.error(uiError('Format must be "sql" or "dump"'))
170
+ if (options.format) {
171
+ if (!isValidFormat(engineName, options.format)) {
172
+ const validFormats = getValidFormats(engineName)
173
+ console.error(
174
+ uiError(
175
+ `Invalid format "${options.format}" for ${engineName}. ` +
176
+ `Valid formats: ${validFormats.join(', ')}`,
177
+ ),
178
+ )
205
179
  process.exit(1)
206
180
  }
207
- format = options.format as 'sql' | 'dump'
181
+ // Safe to cast: isValidFormat above guarantees the format is valid
182
+ format = options.format as BackupFormatType
208
183
  } else if (!containerArg) {
209
- format = await promptBackupFormat(engineName)
184
+ const selectedFormat = await promptBackupFormat(engineName)
185
+ if (selectedFormat) {
186
+ format = selectedFormat
187
+ }
210
188
  }
211
189
 
212
190
  const defaultFilename = generateDefaultFilename(
@@ -219,13 +197,13 @@ export const backupCommand = new Command('backup')
219
197
  filename = await promptBackupFilename(defaultFilename)
220
198
  }
221
199
 
222
- const extension = getExtension(format, engineName)
200
+ const extension = getBackupExtension(engineName, format)
223
201
  const outputDir = options.output || process.cwd()
224
202
  const outputPath = join(outputDir, `${filename}${extension}`)
225
203
 
226
- const formatDesc = getFormatDescription(format, engineName)
204
+ const spinnerLabel = getBackupSpinnerLabel(engineName, format)
227
205
  const backupSpinner = createSpinner(
228
- `Creating ${formatDesc} backup of "${databaseName}"...`,
206
+ `Creating ${spinnerLabel} backup of "${databaseName}"...`,
229
207
  )
230
208
  backupSpinner.start()
231
209
 
@@ -444,23 +444,40 @@ export const createCommand = new Command('create')
444
444
  database = answers.database
445
445
  }
446
446
 
447
- // Redis uses numbered databases (0-15), default to "0"
447
+ // Redis/Valkey use numbered databases (0-15), default to "0"
448
448
  // Other engines default to container name (with hyphens replaced by underscores for SQL compatibility)
449
- if (engine === Engine.Redis) {
449
+ if (engine === Engine.Redis || engine === Engine.Valkey) {
450
450
  database = database ?? '0'
451
+ // Validate Redis/Valkey database is a pure integer string 0-15
452
+ // Reject decimals ("1.5"), scientific notation ("1e2"), and trailing garbage ("5abc")
453
+ if (!/^[0-9]+$/.test(database)) {
454
+ console.error(
455
+ uiError(
456
+ 'Redis/Valkey database must be an integer between 0 and 15',
457
+ ),
458
+ )
459
+ process.exit(1)
460
+ }
461
+ const dbIndex = parseInt(database, 10)
462
+ if (dbIndex < 0 || dbIndex > 15) {
463
+ console.error(
464
+ uiError(
465
+ 'Redis/Valkey database must be an integer between 0 and 15',
466
+ ),
467
+ )
468
+ process.exit(1)
469
+ }
451
470
  } else {
452
471
  database = database ?? containerName.replace(/-/g, '_')
453
- }
454
-
455
- // Validate database name to prevent SQL injection
456
- // Skip for Redis which uses numbered databases (0-15)
457
- if (engine !== Engine.Redis && !isValidDatabaseName(database)) {
458
- console.error(
459
- uiError(
460
- 'Database name must start with a letter and contain only letters, numbers, and underscores',
461
- ),
462
- )
463
- process.exit(1)
472
+ // Validate database name to prevent SQL injection
473
+ if (!isValidDatabaseName(database)) {
474
+ console.error(
475
+ uiError(
476
+ 'Database name must start with a letter and contain only letters, numbers, and underscores',
477
+ ),
478
+ )
479
+ process.exit(1)
480
+ }
464
481
  }
465
482
 
466
483
  console.log(header('Creating Database Container'))
@@ -13,6 +13,7 @@ import {
13
13
  getCurrentPlatform,
14
14
  type DependencyStatus,
15
15
  } from '../../core/dependency-manager'
16
+ import { Platform } from '../../types'
16
17
  import {
17
18
  engineDependencies,
18
19
  getEngineDependencies,
@@ -134,7 +135,7 @@ depsCommand
134
135
  console.log()
135
136
 
136
137
  const platform = getCurrentPlatform()
137
- if (platform === 'darwin') {
138
+ if (platform === Platform.Darwin) {
138
139
  console.log(chalk.gray(' macOS: Install Homebrew first:'))
139
140
  console.log(
140
141
  chalk.cyan(
@@ -10,7 +10,7 @@ import { containerManager } from '../../core/container-manager'
10
10
  import { processManager } from '../../core/process-manager'
11
11
  import { configManager } from '../../core/config-manager'
12
12
  import { getEngine } from '../../engines'
13
- import { binaryManager } from '../../core/binary-manager'
13
+ import { postgresqlBinaryManager } from '../../engines/postgresql/binary-manager'
14
14
  import { paths } from '../../config/paths'
15
15
  import { platformService } from '../../core/platform-service'
16
16
  import {
@@ -41,7 +41,7 @@ import {
41
41
  type InstalledRedisEngine,
42
42
  type InstalledValkeyEngine,
43
43
  } from '../helpers'
44
- import { Engine } from '../../types'
44
+ import { Engine, Platform } from '../../types'
45
45
  import {
46
46
  loadEnginesJson,
47
47
  type EngineConfig,
@@ -969,7 +969,7 @@ enginesCommand
969
969
 
970
970
  // Show the path for reference
971
971
  const { platform, arch } = platformService.getPlatformInfo()
972
- const fullVersion = binaryManager.getFullVersion(version)
972
+ const fullVersion = postgresqlBinaryManager.getFullVersion(version)
973
973
  const binPath = paths.getBinaryPath({
974
974
  engine: 'postgresql',
975
975
  version: fullVersion,
@@ -1290,7 +1290,7 @@ enginesCommand
1290
1290
  if (['clickhouse', 'ch'].includes(normalizedEngine)) {
1291
1291
  // Check platform support
1292
1292
  const { platform } = platformService.getPlatformInfo()
1293
- if (platform === 'win32') {
1293
+ if (platform === Platform.Win32) {
1294
1294
  console.error(
1295
1295
  uiError('ClickHouse is not supported on Windows via hostdb'),
1296
1296
  )
@@ -10,8 +10,6 @@ import { platformService } from '../../../core/platform-service'
10
10
  import { portManager } from '../../../core/port-manager'
11
11
  import { getEngine } from '../../../engines'
12
12
  import { defaults } from '../../../config/defaults'
13
- import { getPostgresHomebrewPackage } from '../../../config/engine-defaults'
14
- import { updatePostgresClientTools } from '../../../engines/postgresql/binary-manager'
15
13
  import {
16
14
  getBackupExtension,
17
15
  getBackupSpinnerLabel,
@@ -667,90 +665,25 @@ export async function handleRestore(): Promise<void> {
667
665
  )
668
666
  console.log()
669
667
 
670
- const { shouldUpgrade } = await inquirer.prompt({
671
- type: 'list',
672
- name: 'shouldUpgrade',
673
- message: `Would you like to upgrade PostgreSQL client tools to support PostgreSQL ${requiredVersion}?`,
674
- choices: [
675
- { name: 'Yes', value: true },
676
- { name: 'No', value: false },
677
- ],
678
- default: 0,
679
- })
680
-
681
- if (shouldUpgrade) {
682
- console.log()
683
- const upgradeSpinner = createSpinner(
684
- 'Upgrading PostgreSQL client tools...',
685
- )
686
- upgradeSpinner.start()
687
-
688
- try {
689
- const updateSuccess = await updatePostgresClientTools()
690
-
691
- if (updateSuccess) {
692
- upgradeSpinner.succeed('PostgreSQL client tools upgraded')
693
- console.log()
694
- console.log(
695
- uiSuccess(
696
- 'Please try the restore again with the updated tools.',
697
- ),
698
- )
699
- await pressEnterToContinue()
700
- return
701
- } else {
702
- upgradeSpinner.fail('Upgrade failed')
703
- console.log()
704
- console.log(
705
- uiError('Automatic upgrade failed. Please upgrade manually:'),
706
- )
707
- const pgPackage = getPostgresHomebrewPackage()
708
- const latestMajor = pgPackage.split('@')[1]
709
- console.log(
710
- uiWarning(
711
- ` macOS: brew install ${pgPackage} && brew link --force ${pgPackage}`,
712
- ),
713
- )
714
- console.log(
715
- chalk.gray(
716
- ` This installs PostgreSQL ${latestMajor} client tools: pg_restore, pg_dump, psql, and libpq`,
717
- ),
718
- )
719
- console.log(
720
- uiWarning(
721
- ` Ubuntu/Debian: sudo apt update && sudo apt install postgresql-client-${latestMajor}`,
722
- ),
723
- )
724
- console.log(
725
- chalk.gray(
726
- ` This installs PostgreSQL ${latestMajor} client tools: pg_restore, pg_dump, psql, and libpq`,
727
- ),
728
- )
729
- await pressEnterToContinue()
730
- return
731
- }
732
- } catch {
733
- upgradeSpinner.fail('Upgrade failed')
734
- console.log(uiError('Failed to upgrade PostgreSQL client tools'))
735
- console.log(
736
- chalk.gray(
737
- 'Manual upgrade may be required for pg_restore, pg_dump, and psql',
738
- ),
739
- )
740
- await pressEnterToContinue()
741
- return
742
- }
743
- } else {
744
- console.log()
745
- console.log(
746
- uiWarning(
747
- 'Restore cancelled. Please upgrade PostgreSQL client tools manually and try again.',
748
- ),
749
- )
750
- await pressEnterToContinue()
751
- return
752
- }
668
+ console.log()
669
+ console.log(
670
+ uiWarning(
671
+ `To restore this backup, download PostgreSQL ${requiredVersion} binaries:`,
672
+ ),
673
+ )
674
+ console.log(
675
+ chalk.cyan(` spindb engines download postgresql ${requiredVersion}`),
676
+ )
677
+ console.log()
678
+ console.log(
679
+ chalk.gray(
680
+ 'Then create a new container with that version and try the restore again.',
681
+ ),
682
+ )
683
+ await pressEnterToContinue()
684
+ return
753
685
  } else {
686
+ // Other restore errors - show warnings
754
687
  restoreSpinner.warn('Restore completed with warnings')
755
688
  if (result.stderr) {
756
689
  console.log()