From d47c57a953a1e051ae7cbdec45209bf15539f646 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 12 Aug 2026 19:30:30 +0530 Subject: [PATCH] fix: skip comment-only migration SQL --- bun.lock | 6 ++-- packages/cli/package.json | 2 +- packages/db/package.json | 2 +- packages/db/src/migrate.ts | 48 ++++++++++++++++++++++++++++++-- packages/db/test/db.test.ts | 17 +++++++++++ packages/dev-server/package.json | 2 +- 6 files changed, 69 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 6704f4df..63be2038 100644 --- a/bun.lock +++ b/bun.lock @@ -268,7 +268,7 @@ }, "packages/cli": { "name": "@wrnexus/cli", - "version": "0.8.21", + "version": "0.8.22", "bin": { "wrnexus": "src/index.ts", }, @@ -324,7 +324,7 @@ }, "packages/db": { "name": "@wrnexus/db", - "version": "0.8.9", + "version": "0.8.10", "devDependencies": { "@types/bun": "^1.3.14", "typescript": "^5.9.2", @@ -332,7 +332,7 @@ }, "packages/dev-server": { "name": "@wrnexus/dev-server", - "version": "0.8.20", + "version": "0.8.21", "dependencies": { "@wrnexus/authz": "workspace:*", "@wrnexus/cache": "workspace:*", diff --git a/packages/cli/package.json b/packages/cli/package.json index fa854257..29bede88 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.8.21", + "version": "0.8.22", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/db/package.json b/packages/db/package.json index ecfd2f1e..2f7e2986 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.8.9", + "version": "0.8.10", "private": true, "type": "module", "main": "./src/index.ts", diff --git a/packages/db/src/migrate.ts b/packages/db/src/migrate.ts index e021ad55..11239b93 100644 --- a/packages/db/src/migrate.ts +++ b/packages/db/src/migrate.ts @@ -51,6 +51,50 @@ function section(content: string, which: "up" | "down"): string { return (next ? rest.slice(0, next.index) : rest).trim(); } +function hasExecutableSql(sql: string): boolean { + let quote = ""; + let lineComment = false; + let blockComment = false; + for (let index = 0; index < sql.length; index++) { + const char = sql[index]!; + const next = sql[index + 1] ?? ""; + if (lineComment) { + if (char === "\n" || char === "\r") lineComment = false; + continue; + } + if (blockComment) { + if (char === "*" && next === "/") { + blockComment = false; + index++; + } + continue; + } + if (quote) { + if (char === quote) { + if (next === quote) index++; + else quote = ""; + } + continue; + } + if (char === "-" && next === "-") { + lineComment = true; + index++; + continue; + } + if (char === "/" && next === "*") { + blockComment = true; + index++; + continue; + } + if (char === "'" || char === '"' || char === "`") { + quote = char; + return true; + } + if (!/\s|;/.test(char)) return true; + } + return false; +} + /** Load and parse all migration files in a directory, sorted by filename. */ export function loadMigrations(dir: string): Migration[] { if (!existsSync(dir)) return []; @@ -136,7 +180,7 @@ export async function applyMigrations( for (const migration of pending.filter(({ name }) => !current.has(name))) { throwIfAborted(options.signal); await db.tx(async (tx) => { - if (migration.up) await tx.exec(migration.up); + if (hasExecutableSql(migration.up)) await tx.exec(migration.up); await tx.exec(`INSERT INTO ${MIGRATIONS_TABLE} (name) VALUES (?)`, [migration.name]); }); done.push(migration.name); @@ -175,7 +219,7 @@ export async function rollback( try { throwIfAborted(options.signal); await db.tx(async (tx) => { - if (migration?.down) await tx.exec(migration.down); + if (migration && hasExecutableSql(migration.down)) await tx.exec(migration.down); await tx.exec(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = ?`, [last]); }); return last; diff --git a/packages/db/test/db.test.ts b/packages/db/test/db.test.ts index 5b05c592..bf7b2ce6 100644 --- a/packages/db/test/db.test.ts +++ b/packages/db/test/db.test.ts @@ -9,6 +9,7 @@ import { createTableSql, parseMigration, applyMigrations, + appliedMigrations, migrate, status, rollback, @@ -144,6 +145,22 @@ test("an empty migration set does not touch the database", async () => { expect(await applyMigrations(db, [])).toEqual([]); }); +test("comment-only migrations are recorded without executing empty SQL", async () => { + const db = createDb(sqlite()); + const migrations = [ + { + name: "0001_placeholder", + up: "-- Create application tables here.\n/* No schema yet. */\n;", + down: "-- Nothing to roll back.", + }, + ]; + + expect(await applyMigrations(db, migrations)).toEqual(["0001_placeholder"]); + expect(await appliedMigrations(db)).toEqual(["0001_placeholder"]); + expect(await rollback(db, ".", { lock: false })).toBe("0001_placeholder"); + await db.close(); +}); + test("migration dry-run plans changes without applying schema and honors cancellation", async () => { const db = createDb(sqlite()); const migrations = [ diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index fecad351..8607da09 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.8.20", + "version": "0.8.21", "type": "module", "main": "src/index.ts", "exports": {