Skip to content

Commit 1e1182c

Browse files
committed
feat: add migrate-cli.ts
1 parent 9cb54e3 commit 1e1182c

File tree

4 files changed

+124
-2
lines changed

4 files changed

+124
-2
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"tsc": "tsc -b tsconfig.all.json",
1414
"dev:tsc": "pnpm tsc --watch --preserveWatchOutput",
1515
"test-e2e": "playwright test",
16+
"repl": "tsx ./src/db/repl.ts",
17+
"migrate": "tsx ./src/db/migrate-cli.ts",
1618
"lint": "run-s lint:*",
1719
"lint-check": "run-s lint-check:*",
1820
"lint:isort": "pnpm lint-check:isort --fix",
@@ -52,6 +54,7 @@
5254
"@types/react-dom": "^18.2.6",
5355
"@vavite/connect": "^1.8.1",
5456
"@vitejs/plugin-react": "^4.0.2",
57+
"consola": "^3.2.3",
5558
"esbuild": "^0.18.11",
5659
"npm-run-all": "^4.1.5",
5760
"prettier": "^3.0.0",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/db/migrate-cli.ts

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,117 @@
1-
// TODO
1+
import fs from "node:fs";
2+
import process from "node:process";
3+
import { tinyassert } from "@hiogawa/utils";
4+
import { consola } from "consola";
5+
import {
6+
type Migration,
7+
type MigrationProvider,
8+
type MigrationResultSet,
9+
Migrator,
10+
sql,
11+
} from "kysely";
12+
import { db, initializeDb } from ".";
13+
import { setWorkerEnvDev } from "../utils/worker-env-dev";
14+
15+
// based on https://github.com/hi-ogawa/vite-fullstack-example/blob/e7bb3b71ea4746a4817e040fc87d9ea171328396/src/db/migrate-cli.ts#L16-L23
16+
//
17+
// usage:
18+
// pnpm migrate
19+
20+
async function mainCli() {
21+
const args = process.argv.slice(2);
22+
const command = args[0] ?? "";
23+
24+
const migrator = new Migrator({
25+
db,
26+
provider: new RawSqlMigrationProvider({
27+
directory: "./src/db/migrations",
28+
}),
29+
});
30+
31+
switch (command) {
32+
case "":
33+
case "status": {
34+
const result = await migrator.getMigrations();
35+
for (const info of result) {
36+
console.log(
37+
`${info.name}: ${info.executedAt?.toISOString() ?? "(pending)"}`,
38+
);
39+
}
40+
return;
41+
}
42+
case "up": {
43+
const result = await migrator.migrateUp();
44+
handleResult(result);
45+
return;
46+
}
47+
case "down": {
48+
const result = await migrator.migrateDown();
49+
handleResult(result);
50+
return;
51+
}
52+
case "latest": {
53+
const result = await migrator.migrateToLatest();
54+
handleResult(result);
55+
return;
56+
}
57+
default: {
58+
throw new Error("unkonwn command", { cause: command });
59+
}
60+
}
61+
}
62+
63+
function handleResult(result: MigrationResultSet) {
64+
console.log(":: executed migrations");
65+
console.log(result.results);
66+
if (result.error) {
67+
throw result.error;
68+
}
69+
}
70+
71+
//
72+
// RawSqlMigrationProvider
73+
//
74+
75+
class RawSqlMigrationProvider implements MigrationProvider {
76+
constructor(private options: { directory: string }) {}
77+
78+
async getMigrations(): Promise<Record<string, Migration>> {
79+
const migrations: Record<string, Migration> = {};
80+
const baseDir = this.options.directory;
81+
const nameDirs = await fs.promises.readdir(baseDir);
82+
for (const name of nameDirs) {
83+
const upFile = `${baseDir}/${name}/up.sql`;
84+
const downFile = `${baseDir}/${name}/down.sql`;
85+
tinyassert(fs.existsSync(upFile));
86+
migrations[name] = {
87+
up: await readSqlFile(upFile),
88+
down: fs.existsSync(downFile) ? await readSqlFile(downFile) : undefined,
89+
};
90+
}
91+
return migrations;
92+
}
93+
}
94+
95+
async function readSqlFile(filepath: string): Promise<Migration["up"]> {
96+
const content = await fs.promises.readFile(filepath, "utf-8");
97+
return async (db) => {
98+
await sql.raw(content).execute(db);
99+
};
100+
}
101+
102+
//
103+
// main
104+
//
105+
106+
async function main() {
107+
await setWorkerEnvDev();
108+
await initializeDb();
109+
try {
110+
await mainCli();
111+
} catch (e) {
112+
consola.error(e);
113+
process.exitCode = 1;
114+
}
115+
}
116+
117+
main();

src/db/repl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { db, initializeDb } from ".";
44
import { setWorkerEnvDev } from "../utils/worker-env-dev";
55

66
// usage
7-
// D1_DATABASE_ID=b7ae526e-5a2b-4c16-8d7d-4a5983c4f1f1 npx tsx ./src/db/repl.ts
7+
// D1_DATABASE_ID=b7ae526e-5a2b-4c16-8d7d-4a5983c4f1f1 pnpm repl
88
// > await sql`SELECT 1 + 1`.execute(db)
99
// > await sql`PRAGMA table_list`.execute(db)
1010

0 commit comments

Comments
 (0)