Skip to content

Commit ec635ab

Browse files
authored
Merge pull request #42 from middleDuckAi/middleDuck/fix-sqlite-core-path
fix(sqlite): [UPD] default SQLite path to core/database
2 parents 3e56966 + 52d7e9b commit ec635ab

8 files changed

Lines changed: 193 additions & 19 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ evo install my-project --extras=sTask@main,sSeo # Install extras after setup (o
122122
- `--db-type`: Database type (`mysql`, `pgsql`, `sqlite`, or `sqlsrv`)
123123
- `--db-host`: Database host (default: `localhost`, not used for SQLite)
124124
- `--db-port`: Database port (defaults: 3306 for MySQL, 5432 for PostgreSQL, 1433 for SQL Server)
125-
- `--db-name`: Database name (for SQLite: path to database file, default: `database.sqlite`)
125+
- `--db-name`: Database name (for SQLite: database file name stored under `core/database/`, default: `database.sqlite`)
126126
- `--db-user`: Database user (not used for SQLite)
127127
- `--db-password`: Database password (not used for SQLite). If it contains shell special characters (e.g. `;`, `&`, `!`), quote it: `--db-password='p;ass'`.
128128
- `--admin-username`: Admin username

internal/engine/install/engine.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -509,8 +509,8 @@ func (e *Engine) Run(ctx context.Context, ch chan<- domain.Event, actions <-chan
509509
Active: true,
510510
ID: "db_sqlite_path",
511511
Kind: domain.QuestionInput,
512-
Prompt: "What is the path to your SQLite database file?",
513-
Default: "database.sqlite",
512+
Prompt: "What is the name of your SQLite database file?",
513+
Default: defaultSQLiteDatabaseName(),
514514
})
515515
if !ok {
516516
return
@@ -524,7 +524,7 @@ func (e *Engine) Run(ctx context.Context, ch chan<- domain.Event, actions <-chan
524524
Source: "install",
525525
Severity: domain.SeverityInfo,
526526
Payload: domain.LogPayload{
527-
Message: "Selected database path: " + dbName + ".",
527+
Message: "Selected database name: " + dbName + ".",
528528
},
529529
})
530530
default:
@@ -1763,6 +1763,10 @@ func defaultPort(dbType string) int {
17631763
}
17641764
}
17651765

1766+
func defaultSQLiteDatabaseName() string {
1767+
return "database.sqlite"
1768+
}
1769+
17661770
func dbDriverLabel(dbType string) string {
17671771
switch strings.ToLower(strings.TrimSpace(dbType)) {
17681772
case "mysql":
@@ -1850,7 +1854,10 @@ try {
18501854
$pass = $cfg["password"] ?? "";
18511855
$timeout = [\PDO::ATTR_TIMEOUT => 5];
18521856
if ($type === "sqlite") {
1853-
if ($name === "") { echo json_encode(["ok"=>false,"error"=>"SQLite database path is required."]); exit(0); }
1857+
if ($name === "") { echo json_encode(["ok"=>false,"error"=>"SQLite database name is required."]); exit(0); }
1858+
if ($name !== ":memory:" && !str_starts_with($name, "file:") && !preg_match('/^(\/|[A-Za-z]:[\\\\\\/])/', $name)) {
1859+
$name = "core/database/" . basename(str_replace("\\", "/", $name));
1860+
}
18541861
new \PDO("sqlite:".$name, null, null, $timeout);
18551862
echo json_encode(["ok"=>true]); exit(0);
18561863
}

internal/ui/view.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ var logValuePrefixes = []string{
523523
"Selected database name:",
524524
"Selected database user:",
525525
"Selected database password:",
526-
"Selected database path:",
526+
"Selected database name:",
527527
"Your Admin username:",
528528
"Your Admin email:",
529529
"Your Admin password:",

src/Commands/InstallCommand.php

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -391,19 +391,20 @@ protected function askDatabaseHost(): string
391391
/**
392392
* Ask for database name.
393393
*
394-
* @param string|null $type Database type (for SQLite, asks for file path)
394+
* @param string|null $type Database type (for SQLite, asks for file name)
395395
*/
396396
protected function askDatabaseName(?string $type = null): string
397397
{
398398
if ($type === 'sqlite') {
399+
$defaultName = $this->defaultSqliteDatabaseName();
399400
$answer = $this->tui->ask(
400-
'What is the path to your SQLite database file?',
401-
'database.sqlite'
401+
'What is the name of your SQLite database file?',
402+
$defaultName
402403
);
403404

404-
$this->tui->replaceLastLogs('<fg=green>✔</> Selected database path: ' . $answer . '.', 2);
405+
$this->tui->replaceLastLogs('<fg=green>✔</> Selected database name: ' . $answer . '.', 2);
405406

406-
return $answer ?: 'database.sqlite';
407+
return $this->normalizeSqliteDatabaseName($answer ?: $defaultName);
407408
}
408409

409410
$answer = $this->tui->ask(
@@ -490,8 +491,8 @@ protected function testDatabaseConnection(array $config): bool
490491
if ($type === 'sqlite') {
491492
// SQLite: Connect directly to the database file
492493
if (empty($config['name'])) {
493-
$this->tui->replaceLastLogs('<fg=red>✗</> Database connection failed: SQLite database path is required.', 2);
494-
$this->lastDatabaseConnectionError = 'SQLite database path is required.';
494+
$this->tui->replaceLastLogs('<fg=red>✗</> Database connection failed: SQLite database name is required.', 2);
495+
$this->lastDatabaseConnectionError = 'SQLite database name is required.';
495496
return false;
496497
}
497498
$this->createConnection($config);
@@ -2681,7 +2682,7 @@ protected function assertDatabaseReadyForPackageDiscovery(string $projectPath, a
26812682

26822683
if ($type === 'sqlite') {
26832684
if ($dbName === '') {
2684-
throw new \RuntimeException('SQLite database path is empty.');
2685+
throw new \RuntimeException('SQLite database name is empty.');
26852686
}
26862687
if ($dbName !== ':memory:' && !str_starts_with($dbName, 'file:') && !is_file($dbName)) {
26872688
throw new \RuntimeException("SQLite database file not found: {$dbName}");
@@ -3323,7 +3324,8 @@ protected function getDatabaseConfigForOperations(string $projectPath, array $db
33233324
return $dbConfig;
33243325
}
33253326

3326-
$name = (string) ($dbConfig['name'] ?? '');
3327+
$name = $this->resolveSqliteDatabasePath((string) ($dbConfig['name'] ?? ''));
3328+
$dbConfig['name'] = $name;
33273329
if ($name === '' || $name === ':memory:' || str_starts_with($name, 'file:')) {
33283330
return $dbConfig;
33293331
}
@@ -3338,6 +3340,44 @@ protected function getDatabaseConfigForOperations(string $projectPath, array $db
33383340
return $dbConfig;
33393341
}
33403342

3343+
protected function defaultSqliteDatabaseName(): string
3344+
{
3345+
return 'database.sqlite';
3346+
}
3347+
3348+
protected function resolveSqliteDatabasePath(string $path): string
3349+
{
3350+
$path = trim($path);
3351+
if ($path === '') {
3352+
return 'core/database/' . $this->defaultSqliteDatabaseName();
3353+
}
3354+
3355+
if ($path === ':memory:' || str_starts_with($path, 'file:')) {
3356+
return $path;
3357+
}
3358+
3359+
if (str_starts_with($path, '/') || preg_match('/^[A-Za-z]:[\\\\\\/]/', $path) === 1) {
3360+
return $path;
3361+
}
3362+
3363+
return 'core/database/' . $this->normalizeSqliteDatabaseName($path);
3364+
}
3365+
3366+
protected function normalizeSqliteDatabaseName(string $name): string
3367+
{
3368+
$name = trim($name);
3369+
if ($name === '') {
3370+
return $this->defaultSqliteDatabaseName();
3371+
}
3372+
3373+
$normalized = str_replace('\\', '/', $name);
3374+
$normalized = preg_replace('#^(?:\./)+#', '', $normalized) ?? $normalized;
3375+
$normalized = trim($normalized, '/');
3376+
$basename = basename($normalized);
3377+
3378+
return ($basename !== '' && $basename !== '.') ? $basename : $this->defaultSqliteDatabaseName();
3379+
}
3380+
33413381
/**
33423382
* Detect base URL for the installed application.
33433383
*

src/Concerns/ConfiguresDatabase.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ protected function buildDsn(string $type, string $host, ?int $port, ?string $dat
8282
{
8383
if ($type === 'sqlite') {
8484
// SQLite: database is the path to the file
85-
$path = $database ?: ':memory:';
85+
$path = $database ?: $this->defaultSqliteDatabasePath();
86+
if ($path !== ':memory:' && !str_starts_with($path, 'file:') && !$this->isAbsolutePath($path)) {
87+
$path = $this->normalizeRelativeSqliteDatabasePath($path);
88+
}
8689
return "sqlite:{$path}";
8790
}
8891

@@ -153,7 +156,10 @@ protected function createDatabase(array $config, string $collation): bool
153156

154157
if ($type === 'sqlite') {
155158
// SQLite: Create database file
156-
$databasePath = $databaseName ?: 'database.sqlite';
159+
$databasePath = $databaseName ?: $this->defaultSqliteDatabasePath();
160+
if ($databasePath !== ':memory:' && !str_starts_with($databasePath, 'file:') && !$this->isAbsolutePath($databasePath)) {
161+
$databasePath = $this->normalizeRelativeSqliteDatabasePath($databasePath);
162+
}
157163
$directory = dirname($databasePath);
158164

159165
if (!empty($directory) && !is_dir($directory)) {
@@ -254,4 +260,29 @@ protected function createDatabase(array $config, string $collation): bool
254260
return false;
255261
}
256262
}
263+
264+
protected function defaultSqliteDatabasePath(): string
265+
{
266+
return 'core/database/database.sqlite';
267+
}
268+
269+
protected function normalizeRelativeSqliteDatabasePath(string $path): string
270+
{
271+
$normalized = trim(str_replace('\\', '/', $path));
272+
$normalized = preg_replace('#^(?:\./)+#', '', $normalized) ?? $normalized;
273+
$normalized = trim($normalized, '/');
274+
$basename = basename($normalized);
275+
if ($basename === '' || $basename === '.') {
276+
return $this->defaultSqliteDatabasePath();
277+
}
278+
279+
return 'core/database/' . $basename;
280+
}
281+
282+
protected function isAbsolutePath(string $path): bool
283+
{
284+
return str_starts_with($path, '/')
285+
|| str_starts_with($path, '\\\\')
286+
|| preg_match('/^[A-Za-z]:[\\\\\\/]/', $path) === 1;
287+
}
257288
}

src/Process/CreatesDatabaseConfig.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,13 @@ public function __invoke(string $projectPath, array $config): bool
8989
protected function generateConfigContent(array $params): string
9090
{
9191
$rawDriver = (string) $params['driver'];
92+
$rawDatabase = (string) $params['database'];
93+
if ($rawDriver === 'sqlite') {
94+
$rawDatabase = $this->normalizeSqliteDatabasePath($rawDatabase);
95+
}
9296
$driver = $this->escapePhpString($rawDriver);
9397
$host = $this->escapePhpString((string) $params['host']);
9498
$port = $this->escapePhpString((string) ($params['port'] ?? ''));
95-
$rawDatabase = (string) $params['database'];
9699
$database = $this->escapePhpString($rawDatabase);
97100
$username = $this->escapePhpString((string) $params['username']);
98101
$password = $this->escapePhpString((string) $params['password']);
@@ -133,6 +136,28 @@ protected function generateConfigContent(array $params): string
133136
PHP;
134137
}
135138

139+
protected function normalizeSqliteDatabasePath(string $path): string
140+
{
141+
$path = trim($path);
142+
if ($path === '') {
143+
return 'core/database/database.sqlite';
144+
}
145+
146+
if ($path === ':memory:' || str_starts_with($path, 'file:') || $this->isAbsolutePath($path)) {
147+
return $path;
148+
}
149+
150+
$normalized = str_replace('\\', '/', $path);
151+
$normalized = preg_replace('#^(?:\./)+#', '', $normalized) ?? $normalized;
152+
$normalized = trim($normalized, '/');
153+
$basename = basename($normalized);
154+
if ($basename === '' || $basename === '.') {
155+
return 'core/database/database.sqlite';
156+
}
157+
158+
return 'core/database/' . $basename;
159+
}
160+
136161
protected function isAbsolutePath(string $path): bool
137162
{
138163
if ($path === '') {

tests/Unit/CreatesDatabaseConfigTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,34 @@ public function testGenerateConfigContentForPostgreSQL(): void
7373
$this->assertStringContainsString("'engine' => env('DB_ENGINE', '')", $content);
7474
}
7575

76+
public function testGenerateConfigContentForSqliteStoresFileInsideCoreDatabase(): void
77+
{
78+
$reflection = new \ReflectionClass($this->processor);
79+
$method = $reflection->getMethod('generateConfigContent');
80+
$method->setAccessible(true);
81+
82+
$params = [
83+
'driver' => 'sqlite',
84+
'host' => '',
85+
'port' => null,
86+
'database' => 'database.sqlite',
87+
'username' => '',
88+
'password' => '',
89+
'charset' => 'utf8',
90+
'collation' => 'utf8',
91+
'prefix' => 'evo_',
92+
'method' => 'SET CHARACTER SET',
93+
'engine' => '',
94+
];
95+
96+
$content = $method->invoke($this->processor, $params);
97+
98+
$this->assertStringContainsString(
99+
"'database' => env('DB_DATABASE', dirname(__DIR__, 4) . '/core/database/database.sqlite')",
100+
$content
101+
);
102+
}
103+
76104
public function testGetDefaultPortForMySQL(): void
77105
{
78106
$reflection = new \ReflectionClass($this->processor);

tests/Unit/InstallCommandAdminDirectoryTest.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,50 @@ public function testWriteCoreCustomEnvDoesNotWriteMgrDirForManager(): void
9999
$this->removeTempDir($projectPath);
100100
}
101101

102+
public function testWriteCoreCustomEnvWritesSqlitePathInsideCoreDatabase(): void
103+
{
104+
$cmd = $this->makeCommand();
105+
106+
$projectPath = $this->makeTempProjectDir();
107+
@mkdir($projectPath . '/core/custom', 0755, true);
108+
109+
$cmd->writeCoreCustomEnvPublic($projectPath, [
110+
'database' => [
111+
'type' => 'sqlite',
112+
'name' => 'database.sqlite',
113+
'prefix' => 'evo_',
114+
],
115+
]);
116+
117+
$env = (string) file_get_contents($projectPath . '/core/custom/.env');
118+
$expected = 'DB_DATABASE="' . $projectPath . '/core/database/database.sqlite"' . "\n";
119+
$this->assertStringContainsString($expected, $env);
120+
121+
$this->removeTempDir($projectPath);
122+
}
123+
124+
public function testWriteCoreCustomEnvStoresOnlySqliteFileNameInsideCoreDatabase(): void
125+
{
126+
$cmd = $this->makeCommand();
127+
128+
$projectPath = $this->makeTempProjectDir();
129+
@mkdir($projectPath . '/core/custom', 0755, true);
130+
131+
$cmd->writeCoreCustomEnvPublic($projectPath, [
132+
'database' => [
133+
'type' => 'sqlite',
134+
'name' => 'nested/custom.sqlite',
135+
'prefix' => 'evo_',
136+
],
137+
]);
138+
139+
$env = (string) file_get_contents($projectPath . '/core/custom/.env');
140+
$expected = 'DB_DATABASE="' . $projectPath . '/core/database/custom.sqlite"' . "\n";
141+
$this->assertStringContainsString($expected, $env);
142+
143+
$this->removeTempDir($projectPath);
144+
}
145+
102146
public function testApplyManagerDirectoryRenamesManagerFolder(): void
103147
{
104148
$cmd = $this->makeCommand();
@@ -170,4 +214,3 @@ public function applyManagerDirectoryPublic(string $projectPath, array $options)
170214
$this->applyManagerDirectory($projectPath, $options);
171215
}
172216
}
173-

0 commit comments

Comments
 (0)