From ac96c77626c1866853cdb0dec34d77bc194e3723 Mon Sep 17 00:00:00 2001 From: zknpr Date: Sat, 30 May 2026 22:00:40 +0200 Subject: [PATCH] ci: add PR build/typecheck/test workflow + fix latent type errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo had no PR CI (release.yml runs only on version tags), so type errors that tsx ignores at runtime slipped in. This adds .github/workflows/ci.yml (node build + `tsc --noEmit -p tsconfig.json` + npm test, on PRs to main and pushes to main) and fixes the 30 pre-existing type errors so the typecheck gate is green: - src/hostBridge.ts: the updateCellBatch SAVEPOINT fallback used `'updateCellBatch' in dbOps`, which narrowed dbOps so `executeQuery` failed to typecheck. Switched to a non-narrowing `typeof dbOps.updateCellBatch === 'function'` check (runtime behavior unchanged; DatabaseOperations declares executeQuery/updateCell/updateCellBatch). - 27 test/benchmark type-only fixes (mock casts, optional params, valid ModificationType values) — no assertion or logic changes. tsc --noEmit: 0 errors. npm test: 311 passing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 37 +++++++++++++++++++ src/hostBridge.ts | 2 +- tests/benchmarks/native_worker_ddl_batch.ts | 2 +- tests/performance/index_drop_benchmark.ts | 2 +- .../performance/native_undo_redo_benchmark.ts | 4 +- tests/unit/hostBridge.test.ts | 6 +-- tests/unit/main.test.ts | 4 +- tests/unit/modification_tracker_api.test.ts | 6 +-- tests/unit/sqlite-db.test.ts | 2 +- tests/unit/tableExporter.test.ts | 2 +- tests/unit/virtualFileSystem.test.ts | 4 +- tests/unit/webviewMessageHandler.test.ts | 6 +-- tests/unit/workerFactory.test.ts | 4 +- 13 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c06fd60 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +# Runs on every PR to main and on pushes to main, so type errors and test +# failures are caught before merge (release.yml only runs on version tags). +on: + pull_request: + branches: [main] + push: + branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-typecheck-test: + name: Build, typecheck & test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build extension, worker & WASM + run: node scripts/build.mjs + + - name: Typecheck (src + tests) + run: npx tsc --noEmit -p tsconfig.json + + - name: Unit tests + run: npm test diff --git a/src/hostBridge.ts b/src/hostBridge.ts index f53be96..49225e4 100644 --- a/src/hostBridge.ts +++ b/src/hostBridge.ts @@ -397,7 +397,7 @@ export class HostBridge implements ToastService { return update; }); - if ('updateCellBatch' in dbOps) { + if (typeof dbOps.updateCellBatch === 'function') { await dbOps.updateCellBatch(table, processedUpdates); } else { // Fallback: execute updates sequentially to avoid IPC overload and N+1 concurrency, diff --git a/tests/benchmarks/native_worker_ddl_batch.ts b/tests/benchmarks/native_worker_ddl_batch.ts index 03eb8cc..0f4a436 100644 --- a/tests/benchmarks/native_worker_ddl_batch.ts +++ b/tests/benchmarks/native_worker_ddl_batch.ts @@ -5,7 +5,7 @@ import * as path from 'path'; async function runBenchmark() { const bundle = await createNativeDatabaseConnection(vscode.Uri.file(process.cwd())); - const loadResult = await bundle.loadDatabase({ buffer: new Uint8Array() }); + const loadResult = await (bundle as any).loadDatabase({ buffer: new Uint8Array() }); const db = loadResult.databaseOps; // create table diff --git a/tests/performance/index_drop_benchmark.ts b/tests/performance/index_drop_benchmark.ts index 7a3336c..8e0e2f1 100644 --- a/tests/performance/index_drop_benchmark.ts +++ b/tests/performance/index_drop_benchmark.ts @@ -34,7 +34,7 @@ async function runBenchmark() { try { const wasmBinary = fs.readFileSync(path.resolve(__dirname, '../../node_modules/sql.js/dist/sql-wasm.wasm')); - const engineResult = await createDatabaseEngine({ wasmBinary }); + const engineResult = await createDatabaseEngine({ wasmBinary } as any); const db = engineResult.operations as WasmDatabaseEngine; const numIndexes = 50; diff --git a/tests/performance/native_undo_redo_benchmark.ts b/tests/performance/native_undo_redo_benchmark.ts index aae7ff2..e31b6c3 100644 --- a/tests/performance/native_undo_redo_benchmark.ts +++ b/tests/performance/native_undo_redo_benchmark.ts @@ -48,10 +48,10 @@ async function runBenchmark() { const start = performance.now(); // 1. Undo (Adds columns back) - await databaseOps.undoModification(mod); + await databaseOps.undoModification(mod as any); // 2. Redo (Drops columns again) - await databaseOps.redoModification(mod); + await databaseOps.redoModification(mod as any); const end = performance.now(); console.log(`Undo + Redo took ${(end - start).toFixed(2)}ms`); diff --git a/tests/unit/hostBridge.test.ts b/tests/unit/hostBridge.test.ts index 34073d0..8991774 100644 --- a/tests/unit/hostBridge.test.ts +++ b/tests/unit/hostBridge.test.ts @@ -26,7 +26,7 @@ describe('HostBridge', () => { await bridge.saveFile('../../../etc/passwd', new Uint8Array([1, 2, 3])); assert.strictEqual(showSaveDialogMock.mock.callCount(), 1); - const args = showSaveDialogMock.mock.calls[0].arguments[0]; + const args = showSaveDialogMock.mock.calls[0].arguments[0] as any; // The defaultUri path should end with the base name 'passwd', not the traversed path assert.ok(args.defaultUri.path.endsWith('/dbDir/passwd'), `Expected safe path, got ${args.defaultUri.path}`); @@ -135,7 +135,7 @@ describe('HostBridge', () => { }; const mockProvider = { webviews: new Map(), context: {} }; const bridge = new HostBridge(mockProvider as any, mockDocument as any); - bridge.ensureDatabaseInitialized = () => dbOps as any; + (bridge as any).ensureDatabaseInitialized = () => dbOps as any; await bridge.deleteRows('table1', [1]); @@ -162,7 +162,7 @@ describe('HostBridge', () => { }; const mockProvider = { webviews: new Map(), context: {} }; const bridge = new HostBridge(mockProvider as any, mockDocument as any); - bridge.ensureDatabaseInitialized = () => dbOps as any; + (bridge as any).ensureDatabaseInitialized = () => dbOps as any; await bridge.deleteColumns('table1', ['col1']); diff --git a/tests/unit/main.test.ts b/tests/unit/main.test.ts index 7d14e44..3d0e5d6 100644 --- a/tests/unit/main.test.ts +++ b/tests/unit/main.test.ts @@ -52,7 +52,7 @@ if (!mockVscode.extensions) { (mockVscode.workspace as any).registerFileSystemProvider = () => ({ dispose: () => {} }); (mockVscode.window as any).createOutputChannel = () => ({ dispose: () => {} }); (mockVscode.window as any).registerCustomEditorProvider = () => ({ dispose: () => {} }); -(mockVscode.ConfigurationTarget as any) = { Global: 1 }; +(mockVscode as any).ConfigurationTarget = { Global: 1 }; (mockVscode.workspace as any).getConfiguration = () => { return { get: () => ({}), @@ -170,7 +170,7 @@ describe('main.ts', () => { await main.activate(mockContext); assert.strictEqual(configUpdateMock.mock.calls.length, 1); - const callArgs = configUpdateMock.mock.calls[0].arguments; + const callArgs = configUpdateMock.mock.calls[0].arguments as any[]; assert.strictEqual(callArgs[0], 'patterns'); // The actual value in codebase may be different, we should check it assert.ok(callArgs[1]['*.sqlite']); diff --git a/tests/unit/modification_tracker_api.test.ts b/tests/unit/modification_tracker_api.test.ts index 4dc9e17..846b1b0 100644 --- a/tests/unit/modification_tracker_api.test.ts +++ b/tests/unit/modification_tracker_api.test.ts @@ -35,7 +35,7 @@ describe('Undo/Redo Logic Coverage', () => { tracker.stepBack(); // Undo 2 assert.strictEqual(tracker.canStepForward, true); - tracker.record({ label: '3', description: '3', modificationType: 'row_update', targetTable: 't1' }); + tracker.record({ label: '3', description: '3', modificationType: 'cell_update', targetTable: 't1' }); assert.strictEqual(tracker.canStepForward, false); assert.strictEqual(tracker.entryCount, 2); }); @@ -67,7 +67,7 @@ describe('Undo/Redo Logic Coverage', () => { await tracker.createCheckpoint(); tracker.record({ label: '2', description: '2', modificationType: 'row_delete', targetTable: 't1' }); - tracker.record({ label: '3', description: '3', modificationType: 'row_update', targetTable: 't1' }); + tracker.record({ label: '3', description: '3', modificationType: 'cell_update', targetTable: 't1' }); assert.strictEqual(tracker.entryCount, 3); assert.strictEqual(tracker.hasUncommittedChanges(), true); @@ -97,7 +97,7 @@ describe('Undo/Redo Logic Coverage', () => { tracker.record({ label: '1', description: '1', modificationType: 'row_insert', targetTable: 't1' }); await tracker.createCheckpoint(); // checkpointIndex = 1 - tracker.record({ label: '2', description: '2', modificationType: 'row_update', targetTable: 't1' }); + tracker.record({ label: '2', description: '2', modificationType: 'cell_update', targetTable: 't1' }); // timeline: ['1', '2'], checkpointIndex: 1 // Add 3rd entry, should evict '1' diff --git a/tests/unit/sqlite-db.test.ts b/tests/unit/sqlite-db.test.ts index 4ebb028..fb03263 100644 --- a/tests/unit/sqlite-db.test.ts +++ b/tests/unit/sqlite-db.test.ts @@ -244,7 +244,7 @@ describe('WasmDatabaseEngine', () => { } } as unknown as Database; - const engine = new WasmDatabaseEngine(mockDb, 5000); + const engine = new WasmDatabaseEngine(mockDb as any, 5000); const consoleWarnOrig = console.warn; let warnedMessage = ''; diff --git a/tests/unit/tableExporter.test.ts b/tests/unit/tableExporter.test.ts index 4f24885..01b475b 100644 --- a/tests/unit/tableExporter.test.ts +++ b/tests/unit/tableExporter.test.ts @@ -300,7 +300,7 @@ describe('exportTableCommand Fallback', () => { let errorMessageShown = ''; const originalShowErrorMessage = mockVscode.window.showErrorMessage; - mockVscode.window.showErrorMessage = async (msg: string) => { + (mockVscode.window as any).showErrorMessage = async (msg: string) => { errorMessageShown = msg; }; diff --git a/tests/unit/virtualFileSystem.test.ts b/tests/unit/virtualFileSystem.test.ts index 26f6e53..850277d 100644 --- a/tests/unit/virtualFileSystem.test.ts +++ b/tests/unit/virtualFileSystem.test.ts @@ -238,7 +238,7 @@ describe('SQLiteFileSystemProvider', () => { assert.strictEqual(dbOps.updateCell.mock.callCount(), 1); assert.deepStrictEqual(dbOps.updateCell.mock.calls[0].arguments, ['users', 1, 'col', 'Hello World']); - assert.strictEqual(doc.recordExternalModification.mock.callCount(), 1); + assert.strictEqual((doc.recordExternalModification as any).mock.callCount(), 1); }); it('should write binary content if not valid UTF-8', async () => { @@ -253,7 +253,7 @@ describe('SQLiteFileSystemProvider', () => { assert.strictEqual(dbOps.updateCell.mock.callCount(), 1); assert.deepStrictEqual(dbOps.updateCell.mock.calls[0].arguments, ['users', 1, 'col', content]); - assert.strictEqual(doc.recordExternalModification.mock.callCount(), 1); + assert.strictEqual((doc.recordExternalModification as any).mock.callCount(), 1); }); }); diff --git a/tests/unit/webviewMessageHandler.test.ts b/tests/unit/webviewMessageHandler.test.ts index c25cfec..3d2060a 100644 --- a/tests/unit/webviewMessageHandler.test.ts +++ b/tests/unit/webviewMessageHandler.test.ts @@ -7,7 +7,7 @@ describe('WebviewMessageHandler', () => { const hostBridge = { echo: (val: unknown) => val }; - let sentMessage: unknown = null; + let sentMessage: any = null; const postMessage = async (msg: unknown) => { sentMessage = msg; return true; @@ -45,7 +45,7 @@ describe('WebviewMessageHandler', () => { it('should handle unknown method', () => { const hostBridge = {}; - let sentMessage: unknown = null; + let sentMessage: any = null; const postMessage = async (msg: unknown) => { sentMessage = msg; return true; @@ -71,7 +71,7 @@ describe('WebviewMessageHandler', () => { const hostBridge = { echo: (val: unknown) => val }; - let sentMessage: unknown = null; + let sentMessage: any = null; const postMessage = async (msg: unknown) => { sentMessage = msg; return true; diff --git a/tests/unit/workerFactory.test.ts b/tests/unit/workerFactory.test.ts index bca13d3..137e0e5 100644 --- a/tests/unit/workerFactory.test.ts +++ b/tests/unit/workerFactory.test.ts @@ -87,8 +87,8 @@ describe('workerFactory error path tests', () => { mockVscode.workspace.fs = { readFile: async () => new Uint8Array(), stat: async () => ({ size: 0 }) - }; - mockVscode.Uri.joinPath = () => ({ scheme: 'file', fsPath: '/test/path/assets/sqlite3.wasm' }); + } as any; + (mockVscode.Uri as any).joinPath = () => ({ scheme: 'file', fsPath: '/test/path/assets/sqlite3.wasm' }); }); afterEach(() => {