create-nexwire 4.2.3 → 4.2.4
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/express/package.json +48 -23
- package/express/src/database/migrations/sqlite/0000_init.sql +47 -60
- package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -89
- package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/express/src/database/schema.ts +2 -3
- package/express/src/framework/facade.ts +12 -1
- package/express/src/framework/maker-cli/db/core.mjs +291 -288
- package/express/src/framework/maker-cli/module/core.mjs +125 -87
- package/express/src/framework/maker-cli/module/index.mjs +3 -3
- package/express/src/framework/maker-cli/stubs/notification/controller.ts.stub +61 -62
- package/express/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -13
- package/express/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
- package/express/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
- package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +11 -10
- package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +1 -1
- package/express/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +25 -25
- package/express/src/framework/maker-cli/stubs/notification/schema.ts.stub +25 -25
- package/express/src/framework/notification/index.ts +49 -87
- package/express/src/framework/providers/adapter.ts +24 -10
- package/express/src/framework/providers/contracts.ts +80 -10
- package/express/src/framework/providers/registry.ts +27 -1
- package/express/src/resources/src/main.ts +1 -1
- package/express/src/resources/src/pages/auth/forgetPassword.vue +1 -1
- package/express/src/resources/src/pages/auth/login.vue +1 -1
- package/express/src/resources/src/pages/auth/register.vue +1 -1
- package/express/src/resources/src/pages/auth/resetPassword.vue +1 -1
- package/express/src/resources/src/pages/auth/verifyEmail.vue +1 -1
- package/express/src/resources/src/pages/dashboard/index.vue +66 -66
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/hono/package.json +44 -19
- package/hono/src/database/migrations/sqlite/0000_init.sql +47 -60
- package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -89
- package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/hono/src/database/schema.ts +2 -3
- package/hono/src/framework/facade.ts +12 -1
- package/hono/src/framework/maker-cli/db/core.mjs +291 -288
- package/hono/src/framework/maker-cli/module/core.mjs +93 -52
- package/hono/src/framework/maker-cli/module/index.mjs +3 -3
- package/hono/src/framework/maker-cli/stubs/notification/controller.ts.stub +63 -58
- package/hono/src/framework/maker-cli/stubs/notification/job.ts.stub +10 -12
- package/hono/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
- package/hono/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
- package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +11 -10
- package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +1 -1
- package/hono/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +25 -25
- package/hono/src/framework/maker-cli/stubs/notification/schema.ts.stub +25 -25
- package/hono/src/framework/notification/index.ts +49 -87
- package/hono/src/framework/providers/adapter.ts +24 -10
- package/hono/src/framework/providers/contracts.ts +80 -10
- package/hono/src/framework/providers/registry.ts +27 -1
- package/hono/src/resources/src/main.ts +1 -1
- package/hono/src/resources/src/pages/auth/forgetPassword.vue +1 -1
- package/hono/src/resources/src/pages/auth/login.vue +1 -1
- package/hono/src/resources/src/pages/auth/register.vue +1 -1
- package/hono/src/resources/src/pages/auth/resetPassword.vue +1 -1
- package/hono/src/resources/src/pages/auth/verifyEmail.vue +1 -1
- package/hono/src/resources/src/pages/dashboard/index.vue +66 -66
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/package.json +10 -10
- package/express/src/modules/auth/database/models/notification.ts +0 -24
- package/hono/src/modules/auth/database/models/notification.ts +0 -24
|
@@ -1,288 +1,291 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { glob } from "glob";
|
|
4
|
-
import { databaseNameFromUrl, databaseUrl, detectDialect, parsedDatabaseUrl, sqlitePathFromUrl } from "../utils/env-db.mjs";
|
|
5
|
-
import { packageScript, runCommand, runNodeScript } from "../utils/process.mjs";
|
|
6
|
-
|
|
7
|
-
/** Detect SQL dialect from raw SQL content by matching against known syntax hints. */
|
|
8
|
-
function detectMigrationDialectFromSql(sql = "") {
|
|
9
|
-
const source = sql.toLowerCase();
|
|
10
|
-
if (!source.trim()) return "unknown";
|
|
11
|
-
|
|
12
|
-
const postgresHints = ["generated always as identity", "bigserial", "serial", "timestamp with time zone", "public."];
|
|
13
|
-
const mysqlHints = ["auto_increment", "engine=", "`"];
|
|
14
|
-
const sqliteHints = ["autoincrement", "pragma"];
|
|
15
|
-
|
|
16
|
-
if (postgresHints.some((hint) => source.includes(hint))) return "postgresql";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
await connection.
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await admin.
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
await client.
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
await
|
|
288
|
-
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
import { databaseNameFromUrl, databaseUrl, detectDialect, parsedDatabaseUrl, sqlitePathFromUrl } from "../utils/env-db.mjs";
|
|
5
|
+
import { packageScript, runCommand, runNodeScript } from "../utils/process.mjs";
|
|
6
|
+
|
|
7
|
+
/** Detect SQL dialect from raw SQL content by matching against known syntax hints. */
|
|
8
|
+
export function detectMigrationDialectFromSql(sql = "") {
|
|
9
|
+
const source = sql.toLowerCase();
|
|
10
|
+
if (!source.trim()) return "unknown";
|
|
11
|
+
|
|
12
|
+
const postgresHints = ["generated always as identity", "bigserial", "serial", "timestamp with time zone", "public."];
|
|
13
|
+
const mysqlHints = ["auto_increment", "engine=", "`"];
|
|
14
|
+
const sqliteHints = ["autoincrement", "pragma"];
|
|
15
|
+
|
|
16
|
+
if (postgresHints.some((hint) => source.includes(hint))) return "postgresql";
|
|
17
|
+
// SQLite output quotes identifiers with backticks too, so the distinctive
|
|
18
|
+
// hints must be tested before the MySQL ones or every SQLite migration is
|
|
19
|
+
// misread as MySQL and the history is reset on the next generate.
|
|
20
|
+
if (sqliteHints.some((hint) => source.includes(hint))) return "sqlite";
|
|
21
|
+
if (mysqlHints.some((hint) => source.includes(hint))) return "mysql";
|
|
22
|
+
return "unknown";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** List existing migration .sql files for the current dialect, sorted. */
|
|
26
|
+
export async function migrationFiles() {
|
|
27
|
+
const files = await glob(`src/database/migrations/${detectDialect()}/*.sql`, {
|
|
28
|
+
cwd: process.cwd(),
|
|
29
|
+
nodir: true,
|
|
30
|
+
windowsPathsNoEscape: true
|
|
31
|
+
});
|
|
32
|
+
return files.sort();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Generate drizzle-kit CLI args for migration generation. Uses --name init for the first migration. */
|
|
36
|
+
export async function drizzleGenerateArgs() {
|
|
37
|
+
const files = await migrationFiles();
|
|
38
|
+
if (!files.length) return ["generate", "--name", "init"];
|
|
39
|
+
return ["generate"];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Create MySQL database if it does not exist. Uses DATABASE_URL credentials. */
|
|
43
|
+
async function ensureMysqlDatabaseExists() {
|
|
44
|
+
const targetUrl = parsedDatabaseUrl();
|
|
45
|
+
const database = databaseNameFromUrl(targetUrl);
|
|
46
|
+
if (!targetUrl || !database) return;
|
|
47
|
+
const mysql = await import("mysql2/promise");
|
|
48
|
+
const connection = await mysql.createConnection({
|
|
49
|
+
host: targetUrl.hostname,
|
|
50
|
+
port: targetUrl.port ? Number(targetUrl.port) : 3306,
|
|
51
|
+
user: decodeURIComponent(targetUrl.username || "root"),
|
|
52
|
+
password: decodeURIComponent(targetUrl.password || ""),
|
|
53
|
+
database: "mysql",
|
|
54
|
+
ssl: targetUrl.searchParams.get("ssl") === "true" ? {} : undefined
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
const [rows] = await connection.query("SELECT SCHEMA_NAME as name FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?", [database]);
|
|
58
|
+
const exists = Array.isArray(rows) && rows.length > 0;
|
|
59
|
+
if (!exists) {
|
|
60
|
+
const safe = database.replace(/`/g, "``");
|
|
61
|
+
await connection.query(`CREATE DATABASE \`${safe}\``);
|
|
62
|
+
console.log(`Created MySQL database: ${database}`);
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
await connection.end();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Create Postgres database if it does not exist. Uses DATABASE_URL credentials. */
|
|
70
|
+
async function ensurePostgresDatabaseExists() {
|
|
71
|
+
const targetUrl = parsedDatabaseUrl();
|
|
72
|
+
const database = databaseNameFromUrl(targetUrl);
|
|
73
|
+
if (!targetUrl || !database) return;
|
|
74
|
+
const postgres = (await import("postgres")).default;
|
|
75
|
+
const admin = postgres({
|
|
76
|
+
host: targetUrl.hostname,
|
|
77
|
+
port: targetUrl.port ? Number(targetUrl.port) : 5432,
|
|
78
|
+
user: decodeURIComponent(targetUrl.username || "postgres"),
|
|
79
|
+
password: decodeURIComponent(targetUrl.password || ""),
|
|
80
|
+
database: "postgres",
|
|
81
|
+
ssl: targetUrl.searchParams.get("ssl") === "true" ? "require" : false
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
const rows = await admin.unsafe("SELECT 1 FROM pg_database WHERE datname = $1", [database]);
|
|
85
|
+
if (!rows.length) {
|
|
86
|
+
const safe = database.replace(/"/g, '""');
|
|
87
|
+
await admin.unsafe(`CREATE DATABASE "${safe}"`);
|
|
88
|
+
console.log(`Created Postgres database: ${database}`);
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
await admin.end();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Ensure the database exists for the current DATABASE_URL dialect. Creates it if missing (MySQL/Postgres). */
|
|
96
|
+
export async function ensureDatabaseExists() {
|
|
97
|
+
const dialect = detectDialect();
|
|
98
|
+
if (dialect === "sqlite") {
|
|
99
|
+
await ensureDatabaseDirectory();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (dialect === "mysql") {
|
|
103
|
+
await ensureMysqlDatabaseExists();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (dialect === "postgresql") {
|
|
107
|
+
await ensurePostgresDatabaseExists();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Resolve the filesystem path for the SQLite database file. */
|
|
112
|
+
function sqliteDatabasePath() {
|
|
113
|
+
return path.resolve(process.cwd(), sqlitePathFromUrl(databaseUrl()));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Delete and recreate the SQLite database file. */
|
|
117
|
+
async function resetSqliteDatabase() {
|
|
118
|
+
const file = sqliteDatabasePath();
|
|
119
|
+
await fs.rm(file, { force: true });
|
|
120
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Drop and recreate the MySQL database. */
|
|
124
|
+
async function resetMysqlDatabase() {
|
|
125
|
+
const targetUrl = parsedDatabaseUrl();
|
|
126
|
+
const database = databaseNameFromUrl(targetUrl);
|
|
127
|
+
if (!targetUrl || !database) return;
|
|
128
|
+
const mysql = await import("mysql2/promise");
|
|
129
|
+
const connection = await mysql.createConnection({
|
|
130
|
+
host: targetUrl.hostname,
|
|
131
|
+
port: targetUrl.port ? Number(targetUrl.port) : 3306,
|
|
132
|
+
user: decodeURIComponent(targetUrl.username || "root"),
|
|
133
|
+
password: decodeURIComponent(targetUrl.password || ""),
|
|
134
|
+
database: "mysql",
|
|
135
|
+
ssl: targetUrl.searchParams.get("ssl") === "true" ? {} : undefined
|
|
136
|
+
});
|
|
137
|
+
try {
|
|
138
|
+
const safe = database.replace(/`/g, "``");
|
|
139
|
+
await connection.query(`DROP DATABASE IF EXISTS \`${safe}\``);
|
|
140
|
+
await connection.query(`CREATE DATABASE \`${safe}\``);
|
|
141
|
+
} finally {
|
|
142
|
+
await connection.end();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Drop and recreate the Postgres database. Terminates active connections first. */
|
|
147
|
+
async function resetPostgresDatabase() {
|
|
148
|
+
const targetUrl = parsedDatabaseUrl();
|
|
149
|
+
const database = databaseNameFromUrl(targetUrl);
|
|
150
|
+
if (!targetUrl || !database) return;
|
|
151
|
+
const postgres = (await import("postgres")).default;
|
|
152
|
+
const admin = postgres({
|
|
153
|
+
host: targetUrl.hostname,
|
|
154
|
+
port: targetUrl.port ? Number(targetUrl.port) : 5432,
|
|
155
|
+
user: decodeURIComponent(targetUrl.username || "postgres"),
|
|
156
|
+
password: decodeURIComponent(targetUrl.password || ""),
|
|
157
|
+
database: "postgres",
|
|
158
|
+
ssl: targetUrl.searchParams.get("ssl") === "true" ? "require" : false
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
const safe = database.replace(/"/g, '""');
|
|
162
|
+
await admin.unsafe("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()", [database]);
|
|
163
|
+
await admin.unsafe(`DROP DATABASE IF EXISTS "${safe}"`);
|
|
164
|
+
await admin.unsafe(`CREATE DATABASE "${safe}"`);
|
|
165
|
+
} finally {
|
|
166
|
+
await admin.end();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Drop and recreate the database for the current dialect. */
|
|
171
|
+
export async function resetDatabase() {
|
|
172
|
+
const dialect = detectDialect();
|
|
173
|
+
if (dialect === "sqlite") return await resetSqliteDatabase();
|
|
174
|
+
if (dialect === "mysql") return await resetMysqlDatabase();
|
|
175
|
+
if (dialect === "postgresql") return await resetPostgresDatabase();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Check if the database has existing app tables (excluding drizzle meta tables). */
|
|
179
|
+
export async function hasExistingAppTables() {
|
|
180
|
+
const dialect = detectDialect();
|
|
181
|
+
if (dialect === "sqlite") {
|
|
182
|
+
const libsql = await import("@libsql/client");
|
|
183
|
+
const file = sqliteDatabasePath();
|
|
184
|
+
const client = libsql.createClient({ url: `file:${file.replace(/\\/g, "/")}` });
|
|
185
|
+
try {
|
|
186
|
+
const result = await client.execute(
|
|
187
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != '__drizzle_migrations'"
|
|
188
|
+
);
|
|
189
|
+
return result.rows.length > 0;
|
|
190
|
+
} finally {
|
|
191
|
+
client.close();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (dialect === "mysql") {
|
|
196
|
+
const mysql = await import("mysql2/promise");
|
|
197
|
+
const connection = await mysql.createConnection(databaseUrl());
|
|
198
|
+
try {
|
|
199
|
+
const [rows] = await connection.query(
|
|
200
|
+
"SELECT table_name as name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name != '__drizzle_migrations'"
|
|
201
|
+
);
|
|
202
|
+
return Array.isArray(rows) && rows.length > 0;
|
|
203
|
+
} finally {
|
|
204
|
+
await connection.end();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const postgres = (await import("postgres")).default;
|
|
209
|
+
const client = postgres(databaseUrl());
|
|
210
|
+
try {
|
|
211
|
+
const rows = await client.unsafe("SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename != '__drizzle_migrations'");
|
|
212
|
+
return rows.length > 0;
|
|
213
|
+
} finally {
|
|
214
|
+
await client.end();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Run the application seed script via tsx or Bun. */
|
|
219
|
+
export async function runSeed(moduleName = "") {
|
|
220
|
+
const seedArgs = ["src/framework/database/seed.ts"];
|
|
221
|
+
if (moduleName) seedArgs.push(`--module=${moduleName}`);
|
|
222
|
+
if (process.versions?.bun) {
|
|
223
|
+
await runCommand("bun", seedArgs);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
await runNodeScript(packageScript("tsx", "dist/cli.mjs"), seedArgs);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Ensure the SQLite database directory exists. No-op for MySQL/Postgres. */
|
|
230
|
+
export async function ensureDatabaseDirectory() {
|
|
231
|
+
if (detectDialect() !== "sqlite") return;
|
|
232
|
+
const file = path.resolve(process.cwd(), sqlitePathFromUrl(databaseUrl()));
|
|
233
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Ensure the drizzle migration meta directory and _journal.json exist. */
|
|
237
|
+
export async function ensureMigrationMeta() {
|
|
238
|
+
const migrationRoot = path.resolve(process.cwd(), "src/database/migrations", detectDialect());
|
|
239
|
+
const metaRoot = path.join(migrationRoot, "meta");
|
|
240
|
+
const journalPath = path.join(metaRoot, "_journal.json");
|
|
241
|
+
await fs.mkdir(metaRoot, { recursive: true });
|
|
242
|
+
try {
|
|
243
|
+
await fs.access(journalPath);
|
|
244
|
+
} catch {
|
|
245
|
+
const dialect = detectDialect();
|
|
246
|
+
const journalDialect = dialect === "postgresql" ? "postgresql" : dialect;
|
|
247
|
+
await fs.writeFile(
|
|
248
|
+
journalPath,
|
|
249
|
+
`{
|
|
250
|
+
"version": "7",
|
|
251
|
+
"dialect": "${journalDialect}",
|
|
252
|
+
"entries": []
|
|
253
|
+
}
|
|
254
|
+
`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Sync the migration dialect to match the current DATABASE_URL. Resets migrations if dialect changed. */
|
|
260
|
+
export async function syncMigrationDialect() {
|
|
261
|
+
async function existingMigrationDialect() {
|
|
262
|
+
const files = await migrationFiles();
|
|
263
|
+
if (!files.length) return null;
|
|
264
|
+
const first = await fs.readFile(path.resolve(process.cwd(), files[0]), "utf8");
|
|
265
|
+
return detectMigrationDialectFromSql(first);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function resetMigrationFiles() {
|
|
269
|
+
const migrationRoot = path.resolve(process.cwd(), "src/database/migrations", detectDialect());
|
|
270
|
+
const dialect = detectDialect();
|
|
271
|
+
const journalDialect = dialect === "postgresql" ? "postgresql" : dialect;
|
|
272
|
+
await fs.rm(migrationRoot, { recursive: true, force: true });
|
|
273
|
+
await fs.mkdir(path.join(migrationRoot, "meta"), { recursive: true });
|
|
274
|
+
await fs.writeFile(
|
|
275
|
+
path.join(migrationRoot, "meta", "_journal.json"),
|
|
276
|
+
`{
|
|
277
|
+
"version": "7",
|
|
278
|
+
"dialect": "${journalDialect}",
|
|
279
|
+
"entries": []
|
|
280
|
+
}
|
|
281
|
+
`
|
|
282
|
+
);
|
|
283
|
+
console.log("Reset existing migration files due to dialect change.");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const target = detectDialect();
|
|
287
|
+
const current = await existingMigrationDialect();
|
|
288
|
+
if (!current || current === "unknown" || current === target) return;
|
|
289
|
+
console.log(`Migration dialect changed (${current} -> ${target}). Regenerating migration files for current DATABASE_URL.`);
|
|
290
|
+
await resetMigrationFiles();
|
|
291
|
+
}
|