spindb 0.34.0 → 0.34.5
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 +13 -1
- package/cli/commands/attach.ts +38 -9
- package/cli/commands/backups.ts +5 -0
- package/cli/commands/connect.ts +6 -6
- package/cli/commands/detach.ts +16 -9
- package/cli/commands/doctor.ts +2 -2
- package/cli/commands/duckdb.ts +273 -0
- package/cli/commands/edit.ts +31 -21
- package/cli/commands/info.ts +26 -16
- package/cli/commands/list.ts +44 -26
- package/cli/commands/menu/shell-handlers.ts +134 -23
- package/cli/commands/menu/sql-handlers.ts +46 -1
- package/cli/commands/menu/update-handlers.ts +2 -2
- package/cli/commands/sqlite.ts +21 -0
- package/cli/index.ts +2 -0
- package/config/engines.json +2 -2
- package/engines/base-engine.ts +8 -0
- package/engines/duckdb/scanner.ts +22 -0
- package/engines/file-based-utils.ts +262 -0
- package/engines/influxdb/index.ts +46 -1
- package/engines/sqlite/scanner.ts +11 -88
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DuckDB Scanner — thin wrapper around shared file-based-utils
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Engine } from '../../types'
|
|
6
|
+
import {
|
|
7
|
+
scanForUnregisteredFiles,
|
|
8
|
+
deriveContainerName as sharedDeriveContainerName,
|
|
9
|
+
type UnregisteredFile,
|
|
10
|
+
} from '../file-based-utils'
|
|
11
|
+
|
|
12
|
+
export type { UnregisteredFile }
|
|
13
|
+
|
|
14
|
+
export async function scanForUnregisteredDuckDBFiles(
|
|
15
|
+
directory?: string,
|
|
16
|
+
): Promise<UnregisteredFile[]> {
|
|
17
|
+
return scanForUnregisteredFiles(Engine.DuckDB, directory)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function deriveContainerName(fileName: string): string {
|
|
21
|
+
return sharedDeriveContainerName(fileName, Engine.DuckDB)
|
|
22
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized utilities for file-based engines (SQLite, DuckDB)
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of truth for:
|
|
5
|
+
* - Extension → engine mapping
|
|
6
|
+
* - Engine → valid extensions
|
|
7
|
+
* - Engine → registry
|
|
8
|
+
* - Container name derivation from filenames
|
|
9
|
+
* - Scanning for unregistered files
|
|
10
|
+
*
|
|
11
|
+
* All file-based engine behavior should go through this module
|
|
12
|
+
* so that adding a new file-based engine only requires changes here.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readdir } from 'fs/promises'
|
|
16
|
+
import { existsSync } from 'fs'
|
|
17
|
+
import { resolve, extname } from 'path'
|
|
18
|
+
import { Engine, isFileBasedEngine } from '../types'
|
|
19
|
+
import { sqliteRegistry } from './sqlite/registry'
|
|
20
|
+
import { duckdbRegistry } from './duckdb/registry'
|
|
21
|
+
|
|
22
|
+
// ============================================================
|
|
23
|
+
// Extension Mapping
|
|
24
|
+
// ============================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Map of file extensions to their corresponding file-based engine.
|
|
28
|
+
* This is the single source of truth for extension → engine detection.
|
|
29
|
+
*/
|
|
30
|
+
const EXTENSION_TO_ENGINE: Record<string, Engine> = {
|
|
31
|
+
'.sqlite': Engine.SQLite,
|
|
32
|
+
'.sqlite3': Engine.SQLite,
|
|
33
|
+
'.db': Engine.SQLite,
|
|
34
|
+
'.duckdb': Engine.DuckDB,
|
|
35
|
+
'.ddb': Engine.DuckDB,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Valid extensions per engine, derived from the extension map.
|
|
40
|
+
* Used for validation (e.g., ensuring a SQLite container only relocates to a SQLite extension).
|
|
41
|
+
*/
|
|
42
|
+
const ENGINE_EXTENSIONS: Record<Engine.SQLite | Engine.DuckDB, string[]> = {
|
|
43
|
+
[Engine.SQLite]: ['.sqlite', '.sqlite3', '.db'],
|
|
44
|
+
[Engine.DuckDB]: ['.duckdb', '.ddb'],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extension regex per engine (for stripping extensions from filenames).
|
|
49
|
+
*/
|
|
50
|
+
const ENGINE_EXTENSION_REGEX: Record<Engine.SQLite | Engine.DuckDB, RegExp> = {
|
|
51
|
+
[Engine.SQLite]: /\.(sqlite3?|db)$/i,
|
|
52
|
+
[Engine.DuckDB]: /\.(duckdb|ddb)$/i,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Combined regex matching any file-based engine extension.
|
|
57
|
+
*/
|
|
58
|
+
export const FILE_BASED_EXTENSION_REGEX = /\.(sqlite3?|db|duckdb|ddb)$/i
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Detect which file-based engine a file belongs to based on its extension.
|
|
62
|
+
* Returns null if the extension is not recognized as a file-based database.
|
|
63
|
+
*/
|
|
64
|
+
export function detectEngineFromPath(filePath: string): Engine | null {
|
|
65
|
+
const ext = extname(filePath).toLowerCase()
|
|
66
|
+
return EXTENSION_TO_ENGINE[ext] ?? null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the valid file extensions for a file-based engine.
|
|
71
|
+
*/
|
|
72
|
+
export function getExtensionsForEngine(
|
|
73
|
+
engine: Engine.SQLite | Engine.DuckDB,
|
|
74
|
+
): string[] {
|
|
75
|
+
return ENGINE_EXTENSIONS[engine]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get all valid file-based database extensions.
|
|
80
|
+
*/
|
|
81
|
+
export function getAllFileBasedExtensions(): string[] {
|
|
82
|
+
return Object.keys(EXTENSION_TO_ENGINE)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a file path has a valid extension for the given engine.
|
|
87
|
+
*/
|
|
88
|
+
export function isValidExtensionForEngine(
|
|
89
|
+
filePath: string,
|
|
90
|
+
engine: Engine.SQLite | Engine.DuckDB,
|
|
91
|
+
): boolean {
|
|
92
|
+
const ext = extname(filePath).toLowerCase()
|
|
93
|
+
return ENGINE_EXTENSIONS[engine].includes(ext)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Human-readable string of valid extensions for a file-based engine.
|
|
98
|
+
*/
|
|
99
|
+
export function formatExtensionsForEngine(
|
|
100
|
+
engine: Engine.SQLite | Engine.DuckDB,
|
|
101
|
+
): string {
|
|
102
|
+
return ENGINE_EXTENSIONS[engine].join(', ')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Human-readable string of all valid file-based extensions.
|
|
107
|
+
*/
|
|
108
|
+
export function formatAllExtensions(): string {
|
|
109
|
+
return Object.keys(EXTENSION_TO_ENGINE).join(', ')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================
|
|
113
|
+
// Registry Access
|
|
114
|
+
// ============================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Common interface for file-based engine registries.
|
|
118
|
+
* Both SQLite and DuckDB registries implement this shape.
|
|
119
|
+
*/
|
|
120
|
+
export type FileBasedRegistry = {
|
|
121
|
+
add(entry: { name: string; filePath: string; created: string }): Promise<void>
|
|
122
|
+
get(name: string): Promise<{ name: string; filePath: string } | null>
|
|
123
|
+
remove(name: string): Promise<boolean>
|
|
124
|
+
update(
|
|
125
|
+
name: string,
|
|
126
|
+
updates: { filePath?: string; lastVerified?: string },
|
|
127
|
+
): Promise<boolean>
|
|
128
|
+
exists(name: string): Promise<boolean>
|
|
129
|
+
isPathRegistered(filePath: string): Promise<boolean>
|
|
130
|
+
getByPath(
|
|
131
|
+
filePath: string,
|
|
132
|
+
): Promise<{ name: string; filePath: string } | null>
|
|
133
|
+
isFolderIgnored(folderPath: string): Promise<boolean>
|
|
134
|
+
addIgnoreFolder(folderPath: string): Promise<void>
|
|
135
|
+
removeIgnoreFolder(folderPath: string): Promise<boolean>
|
|
136
|
+
listIgnoredFolders(): Promise<string[]>
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get the registry for a file-based engine.
|
|
141
|
+
* Throws if the engine is not file-based.
|
|
142
|
+
*/
|
|
143
|
+
export function getRegistryForEngine(engine: Engine): FileBasedRegistry {
|
|
144
|
+
switch (engine) {
|
|
145
|
+
case Engine.SQLite:
|
|
146
|
+
return sqliteRegistry
|
|
147
|
+
case Engine.DuckDB:
|
|
148
|
+
return duckdbRegistry
|
|
149
|
+
default:
|
|
150
|
+
if (isFileBasedEngine(engine)) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`File-based engine "${engine}" has no registry configured in getRegistryForEngine()`,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`"${engine}" is not a file-based engine`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ============================================================
|
|
160
|
+
// Container Name Derivation
|
|
161
|
+
// ============================================================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Derive a valid container name from a database filename.
|
|
165
|
+
* Removes the engine-specific extension and sanitizes for use as a container name.
|
|
166
|
+
*
|
|
167
|
+
* - Must start with a letter
|
|
168
|
+
* - Can contain letters, numbers, hyphens, underscores
|
|
169
|
+
* - Falls back to engine-specific default if result is empty
|
|
170
|
+
*/
|
|
171
|
+
export function deriveContainerName(
|
|
172
|
+
fileName: string,
|
|
173
|
+
engine: Engine.SQLite | Engine.DuckDB,
|
|
174
|
+
): string {
|
|
175
|
+
const extensionRegex = ENGINE_EXTENSION_REGEX[engine]
|
|
176
|
+
const fallback = engine === Engine.SQLite ? 'sqlite-db' : 'duckdb-db'
|
|
177
|
+
|
|
178
|
+
// Remove extension
|
|
179
|
+
const base = fileName.replace(extensionRegex, '')
|
|
180
|
+
|
|
181
|
+
// If nothing remains after extension removal, return engine-specific fallback
|
|
182
|
+
const sanitizedBase = base
|
|
183
|
+
.replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
184
|
+
.replace(/^-+|-+$/g, '')
|
|
185
|
+
if (!sanitizedBase) {
|
|
186
|
+
return fallback
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Convert to valid container name (alphanumeric, hyphens, underscores)
|
|
190
|
+
let name = base.replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
191
|
+
|
|
192
|
+
// Ensure starts with letter
|
|
193
|
+
if (!/^[a-zA-Z]/.test(name)) {
|
|
194
|
+
name = 'db-' + name
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Remove consecutive hyphens
|
|
198
|
+
name = name.replace(/-+/g, '-')
|
|
199
|
+
|
|
200
|
+
// Trim leading/trailing hyphens
|
|
201
|
+
name = name.replace(/^-+|-+$/g, '')
|
|
202
|
+
|
|
203
|
+
return name || fallback
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================================
|
|
207
|
+
// File Scanning
|
|
208
|
+
// ============================================================
|
|
209
|
+
|
|
210
|
+
export type UnregisteredFile = {
|
|
211
|
+
fileName: string
|
|
212
|
+
absolutePath: string
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Scan a directory for unregistered file-based database files.
|
|
217
|
+
*
|
|
218
|
+
* @param engine The file-based engine to scan for
|
|
219
|
+
* @param directory Directory to scan (defaults to CWD)
|
|
220
|
+
* @returns Array of unregistered files
|
|
221
|
+
*/
|
|
222
|
+
export async function scanForUnregisteredFiles(
|
|
223
|
+
engine: Engine.SQLite | Engine.DuckDB,
|
|
224
|
+
directory: string = process.cwd(),
|
|
225
|
+
): Promise<UnregisteredFile[]> {
|
|
226
|
+
const absoluteDir = resolve(directory)
|
|
227
|
+
const registry = getRegistryForEngine(engine)
|
|
228
|
+
const extensionRegex = ENGINE_EXTENSION_REGEX[engine]
|
|
229
|
+
|
|
230
|
+
// Check if folder is ignored
|
|
231
|
+
if (await registry.isFolderIgnored(absoluteDir)) {
|
|
232
|
+
return []
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check if directory exists
|
|
236
|
+
if (!existsSync(absoluteDir)) {
|
|
237
|
+
return []
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const entries = await readdir(absoluteDir, { withFileTypes: true })
|
|
242
|
+
|
|
243
|
+
const matchingFiles = entries
|
|
244
|
+
.filter((e) => e.isFile())
|
|
245
|
+
.filter((e) => extensionRegex.test(e.name))
|
|
246
|
+
.map((e) => ({
|
|
247
|
+
fileName: e.name,
|
|
248
|
+
absolutePath: resolve(absoluteDir, e.name),
|
|
249
|
+
}))
|
|
250
|
+
|
|
251
|
+
const unregistered: UnregisteredFile[] = []
|
|
252
|
+
for (const file of matchingFiles) {
|
|
253
|
+
if (!(await registry.isPathRegistered(file.absolutePath))) {
|
|
254
|
+
unregistered.push(file)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return unregistered
|
|
259
|
+
} catch {
|
|
260
|
+
return []
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -1044,8 +1044,41 @@ export class InfluxDBEngine extends BaseEngine {
|
|
|
1044
1044
|
const database = options.database || container.database
|
|
1045
1045
|
|
|
1046
1046
|
if (options.file) {
|
|
1047
|
-
// Read file content and execute as SQL
|
|
1048
1047
|
const content = await readFile(options.file, 'utf-8')
|
|
1048
|
+
|
|
1049
|
+
// Ensure the database exists (InfluxDB creates DBs implicitly on write,
|
|
1050
|
+
// but SQL queries fail if the DB doesn't exist yet)
|
|
1051
|
+
const createDbResp = await influxdbApiRequest(
|
|
1052
|
+
port,
|
|
1053
|
+
'POST',
|
|
1054
|
+
'/api/v3/configure/database',
|
|
1055
|
+
{ db: database },
|
|
1056
|
+
)
|
|
1057
|
+
if (createDbResp.status >= 400) {
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
`Failed to create database "${database}": HTTP ${createDbResp.status} — ${JSON.stringify(createDbResp.data)}`,
|
|
1060
|
+
)
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Line protocol files (.lp) → write via /api/v3/write_lp
|
|
1064
|
+
if (options.file.endsWith('.lp')) {
|
|
1065
|
+
const lines = content
|
|
1066
|
+
.split('\n')
|
|
1067
|
+
.filter((line) => line.trim().length > 0 && !line.startsWith('#'))
|
|
1068
|
+
.join('\n')
|
|
1069
|
+
const response = await influxdbApiRequest(
|
|
1070
|
+
port,
|
|
1071
|
+
'POST',
|
|
1072
|
+
`/api/v3/write_lp?db=${encodeURIComponent(database)}`,
|
|
1073
|
+
lines,
|
|
1074
|
+
)
|
|
1075
|
+
if (response.status >= 400) {
|
|
1076
|
+
throw new Error(`Write error: ${JSON.stringify(response.data)}`)
|
|
1077
|
+
}
|
|
1078
|
+
return
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// SQL files → execute via /api/v3/query_sql
|
|
1049
1082
|
const statements = content
|
|
1050
1083
|
.split('\n')
|
|
1051
1084
|
.filter((line) => !line.startsWith('--') && line.trim().length > 0)
|
|
@@ -1076,6 +1109,18 @@ export class InfluxDBEngine extends BaseEngine {
|
|
|
1076
1109
|
}
|
|
1077
1110
|
|
|
1078
1111
|
if (options.sql) {
|
|
1112
|
+
// Ensure database exists for inline SQL too
|
|
1113
|
+
const createDbResp2 = await influxdbApiRequest(
|
|
1114
|
+
port,
|
|
1115
|
+
'POST',
|
|
1116
|
+
'/api/v3/configure/database',
|
|
1117
|
+
{ db: database },
|
|
1118
|
+
)
|
|
1119
|
+
if (createDbResp2.status >= 400) {
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
`Failed to create database "${database}": HTTP ${createDbResp2.status} — ${JSON.stringify(createDbResp2.data)}`,
|
|
1122
|
+
)
|
|
1123
|
+
}
|
|
1079
1124
|
const response = await influxdbApiRequest(
|
|
1080
1125
|
port,
|
|
1081
1126
|
'POST',
|
|
@@ -1,99 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQLite Scanner
|
|
3
|
-
*
|
|
4
|
-
* Scans directories for unregistered SQLite database files.
|
|
5
|
-
* Used to detect SQLite databases in the current working directory
|
|
6
|
-
* that are not yet registered with SpinDB.
|
|
2
|
+
* SQLite Scanner — thin wrapper around shared file-based-utils
|
|
7
3
|
*/
|
|
8
4
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { Engine } from '../../types'
|
|
6
|
+
import {
|
|
7
|
+
scanForUnregisteredFiles,
|
|
8
|
+
deriveContainerName as sharedDeriveContainerName,
|
|
9
|
+
type UnregisteredFile,
|
|
10
|
+
} from '../file-based-utils'
|
|
13
11
|
|
|
14
|
-
export type UnregisteredFile
|
|
15
|
-
fileName: string
|
|
16
|
-
absolutePath: string
|
|
17
|
-
}
|
|
12
|
+
export type { UnregisteredFile }
|
|
18
13
|
|
|
19
|
-
/**
|
|
20
|
-
* Scan a directory for unregistered SQLite files
|
|
21
|
-
* Returns files with .sqlite, .sqlite3, or .db extensions
|
|
22
|
-
* that are not already in the registry
|
|
23
|
-
*
|
|
24
|
-
* @param directory Directory to scan (defaults to CWD)
|
|
25
|
-
* @returns Array of unregistered SQLite files
|
|
26
|
-
*/
|
|
27
14
|
export async function scanForUnregisteredSqliteFiles(
|
|
28
|
-
directory
|
|
15
|
+
directory?: string,
|
|
29
16
|
): Promise<UnregisteredFile[]> {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Check if folder is ignored
|
|
33
|
-
if (await sqliteRegistry.isFolderIgnored(absoluteDir)) {
|
|
34
|
-
return []
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Check if directory exists
|
|
38
|
-
if (!existsSync(absoluteDir)) {
|
|
39
|
-
return []
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// Get all files in directory
|
|
44
|
-
const entries = await readdir(absoluteDir, { withFileTypes: true })
|
|
45
|
-
|
|
46
|
-
// Filter for SQLite files
|
|
47
|
-
const sqliteFiles = entries
|
|
48
|
-
.filter((e) => e.isFile())
|
|
49
|
-
.filter((e) => /\.(sqlite3?|db)$/i.test(e.name))
|
|
50
|
-
.map((e) => ({
|
|
51
|
-
fileName: e.name,
|
|
52
|
-
absolutePath: resolve(absoluteDir, e.name),
|
|
53
|
-
}))
|
|
54
|
-
|
|
55
|
-
// Filter out already registered files
|
|
56
|
-
const unregistered: UnregisteredFile[] = []
|
|
57
|
-
for (const file of sqliteFiles) {
|
|
58
|
-
if (!(await sqliteRegistry.isPathRegistered(file.absolutePath))) {
|
|
59
|
-
unregistered.push(file)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return unregistered
|
|
64
|
-
} catch {
|
|
65
|
-
// If we can't read the directory, return empty
|
|
66
|
-
return []
|
|
67
|
-
}
|
|
17
|
+
return scanForUnregisteredFiles(Engine.SQLite, directory)
|
|
68
18
|
}
|
|
69
19
|
|
|
70
|
-
/**
|
|
71
|
-
* Derive a valid container name from a filename
|
|
72
|
-
* Removes extension and converts to valid container name format:
|
|
73
|
-
* - Must start with a letter
|
|
74
|
-
* - Can contain letters, numbers, hyphens, underscores
|
|
75
|
-
*
|
|
76
|
-
* @param fileName The SQLite filename (e.g., "my-database.sqlite")
|
|
77
|
-
* @returns A valid container name (e.g., "my-database")
|
|
78
|
-
*/
|
|
79
20
|
export function deriveContainerName(fileName: string): string {
|
|
80
|
-
|
|
81
|
-
const base = fileName.replace(/\.(sqlite3?|db)$/i, '')
|
|
82
|
-
|
|
83
|
-
// Convert to valid container name (alphanumeric, hyphens, underscores)
|
|
84
|
-
// Replace invalid chars with hyphens
|
|
85
|
-
let name = base.replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
86
|
-
|
|
87
|
-
// Ensure starts with letter
|
|
88
|
-
if (!/^[a-zA-Z]/.test(name)) {
|
|
89
|
-
name = 'db-' + name
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Remove consecutive hyphens
|
|
93
|
-
name = name.replace(/-+/g, '-')
|
|
94
|
-
|
|
95
|
-
// Trim leading/trailing hyphens
|
|
96
|
-
name = name.replace(/^-+|-+$/g, '')
|
|
97
|
-
|
|
98
|
-
return name || 'sqlite-db'
|
|
21
|
+
return sharedDeriveContainerName(fileName, Engine.SQLite)
|
|
99
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spindb",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.5",
|
|
4
4
|
"author": "Bob Bass <bob@bbass.co>",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"description": "Zero-config Docker-free local database containers. Create, backup, and clone a variety of popular databases.",
|