From 5cc37d0c339c15f949350c59dacca6ea666213cd Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 27 Jun 2026 09:45:34 +0300 Subject: [PATCH] fix: restore CLI validation and test baseline (#1857) Fixed lazy CLI helper imports for tunnel flows Restored update command version detection Made web test and dead-code commands run reliably --- package.json | 2 +- packages/web/bin/lib/cli-http.js | 1 + packages/web/bin/lib/cli-network.js | 1 + packages/web/bin/lib/cli-paths.js | 2 ++ packages/web/bin/lib/commands-tunnel.js | 8 +++-- packages/web/server/lib/git/routes.test.js | 8 ++--- packages/web/server/lib/github/routes.js | 1 + packages/web/server/lib/package-manager.js | 2 +- .../web/server/lib/package-manager.test.js | 9 +++++- packages/web/src/api/files.test.ts | 6 ++-- packages/web/test/bun-test-shim.ts | 31 +++++++++++++++++++ packages/web/vitest.config.ts | 10 ++++++ 12 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 packages/web/test/bun-test-shim.ts create mode 100644 packages/web/vitest.config.ts diff --git a/package.json b/package.json index f8c6ebfc..fce229c3 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "vscode:package": "bun run --cwd packages/vscode package", "vscode:type-check": "bun run --cwd packages/vscode type-check", "docs:validate": "node scripts/docs/validate-docs.mjs", - "dead-code": "bunx knip --no-exit-code --include files,exports,nsExports,types,nsTypes,enumMembers,namespaceMembers,duplicates", + "dead-code": "bunx knip@5.80.0 --no-exit-code --include files,exports,nsExports,types,nsTypes,enumMembers,duplicates", "doctor": "node scripts/react-doctor.mjs", "icons:sprite": "node scripts/generate-file-type-sprite.mjs", "icons:generate": "bun run scripts/generate-icon-sprite.mjs", diff --git a/packages/web/bin/lib/cli-http.js b/packages/web/bin/lib/cli-http.js index b4ffb9a7..16bb1d23 100644 --- a/packages/web/bin/lib/cli-http.js +++ b/packages/web/bin/lib/cli-http.js @@ -210,6 +210,7 @@ export { requestServerShutdown, requestJson, isServerHealthReady, + waitForServerHealth, fetchTunnelProvidersFromPort, fetchSystemInfoFromPort, }; diff --git a/packages/web/bin/lib/cli-network.js b/packages/web/bin/lib/cli-network.js index 042716a5..7e0e666a 100644 --- a/packages/web/bin/lib/cli-network.js +++ b/packages/web/bin/lib/cli-network.js @@ -145,6 +145,7 @@ export { resolveServeHost, resolveApiHost, formatHostForUrl, + isUnsafeBrowserPort, buildLocalUrl, detectLanIPv4Address, assertSafeBrowserPort, diff --git a/packages/web/bin/lib/cli-paths.js b/packages/web/bin/lib/cli-paths.js index 67479fae..9932b6fa 100644 --- a/packages/web/bin/lib/cli-paths.js +++ b/packages/web/bin/lib/cli-paths.js @@ -107,5 +107,7 @@ export { getLogFilePath, getTunnelProfilesFilePath, getLegacyCloudflareManagedRemoteFilePath, + readLastManagedLocalConfigPath, + writeLastManagedLocalConfigPath, getRunDir, }; diff --git a/packages/web/bin/lib/commands-tunnel.js b/packages/web/bin/lib/commands-tunnel.js index 74ead243..9f64059c 100644 --- a/packages/web/bin/lib/commands-tunnel.js +++ b/packages/web/bin/lib/commands-tunnel.js @@ -4,7 +4,7 @@ import path from 'path'; import crypto from 'crypto'; import { EXIT_CODE, TunnelCliError } from './cli-errors.js'; import { DEFAULT_PORT, findClosestMatch, generateCompletionScript, showTunnelHelp } from './cli-args.js'; -import { requestJson, fetchSystemInfoFromPort } from './cli-http.js'; +import { requestJson, fetchSystemInfoFromPort, waitForServerHealth } from './cli-http.js'; import { discoverRunningInstances, getLatestInstance, @@ -34,7 +34,11 @@ import { resolveTunnelTtlOverrides, } from './cli-tunnel-utils.js'; import { DEFAULT_TUNNEL_PROVIDER_CAPABILITIES } from './cli-tunnel-capabilities.js'; -import { assertSafeBrowserPort, buildLocalUrl } from './cli-network.js'; +import { assertSafeBrowserPort, buildLocalUrl, isUnsafeBrowserPort } from './cli-network.js'; +import { + readLastManagedLocalConfigPath, + writeLastManagedLocalConfigPath, +} from './cli-paths.js'; import { intro as clackIntro, outro as clackOutro, diff --git a/packages/web/server/lib/git/routes.test.js b/packages/web/server/lib/git/routes.test.js index ec028d48..0c0597c8 100644 --- a/packages/web/server/lib/git/routes.test.js +++ b/packages/web/server/lib/git/routes.test.js @@ -1,11 +1,11 @@ -import { beforeEach, describe, expect, it, mock } from 'bun:test'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; const gitLibraries = { - stageFiles: mock(), - unstageFiles: mock(), + stageFiles: vi.fn(), + unstageFiles: vi.fn(), }; -mock.module('./index.js', () => ({ +vi.mock('./index.js', () => ({ stageFiles: gitLibraries.stageFiles, unstageFiles: gitLibraries.unstageFiles, })); diff --git a/packages/web/server/lib/github/routes.js b/packages/web/server/lib/github/routes.js index 5496f2ba..ea9b975f 100644 --- a/packages/web/server/lib/github/routes.js +++ b/packages/web/server/lib/github/routes.js @@ -982,6 +982,7 @@ export function registerGitHubRoutes(app) { if (upstream) { try { const { getRemotes } = await import('../git/index.js'); + const { resolveGitHubRepoFromDirectory } = await import('./index.js'); const remotes = await getRemotes(directory); for (const r of remotes) { if (r?.name) { diff --git a/packages/web/server/lib/package-manager.js b/packages/web/server/lib/package-manager.js index 40684bec..fc92af10 100644 --- a/packages/web/server/lib/package-manager.js +++ b/packages/web/server/lib/package-manager.js @@ -621,7 +621,7 @@ export function getUpdateCommand(pm = detectPackageManager()) { /** * Get current installed version from package.json */ -function getCurrentVersion() { +export function getCurrentVersion() { try { const pkgPath = path.resolve(__dirname, '..', '..', 'package.json'); const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); diff --git a/packages/web/server/lib/package-manager.test.js b/packages/web/server/lib/package-manager.test.js index 7edab852..d64bf541 100644 --- a/packages/web/server/lib/package-manager.test.js +++ b/packages/web/server/lib/package-manager.test.js @@ -6,7 +6,7 @@ vi.mock('node:child_process', () => ({ spawnSync: vi.fn(() => ({ status: 0, stdout: '/usr/local/bin', stderr: '' })), })); -const { checkForUpdates } = await import('./package-manager.js'); +const { checkForUpdates, getCurrentVersion } = await import('./package-manager.js'); /** Helper: create a fetch mock that routes by URL pattern */ function createFetchMock() { @@ -244,3 +244,10 @@ describe('checkForUpdates', () => { expect(result.available).toBe(false); }); }); + +describe('getCurrentVersion', () => { + it('is exported for the CLI update command', () => { + expect(typeof getCurrentVersion).toBe('function'); + expect(getCurrentVersion()).toMatch(/^\d+\.\d+\.\d+|unknown$/); + }); +}); diff --git a/packages/web/src/api/files.test.ts b/packages/web/src/api/files.test.ts index f1b963c2..3587da99 100644 --- a/packages/web/src/api/files.test.ts +++ b/packages/web/src/api/files.test.ts @@ -32,14 +32,16 @@ describe('createWebFilesAPI', () => { runtimeFetchMock.mockResolvedValueOnce(Response.json({ path: '/worktree-b/file.txt', isFile: true, size: 12 })); await api.statFile?.('/worktree-b/file.txt', { directory: '/worktree-a' }); - expect(runtimeFetchMock).toHaveBeenLastCalledWith('/api/fs/stat?path=%2Fworktree-b%2Ffile.txt', { + expect(runtimeFetchMock).toHaveBeenLastCalledWith('/api/fs/stat', { + query: new URLSearchParams({ path: '/worktree-b/file.txt' }), headers: { 'x-opencode-directory': '/worktree-a' }, }); runtimeFetchMock.mockResolvedValueOnce(new Response('content')); await api.readFile?.('/worktree-b/file.txt', { directory: '/worktree-a' }); - expect(runtimeFetchMock).toHaveBeenLastCalledWith('/api/fs/read?path=%2Fworktree-b%2Ffile.txt', { + expect(runtimeFetchMock).toHaveBeenLastCalledWith('/api/fs/read', { + query: new URLSearchParams({ path: '/worktree-b/file.txt' }), cache: 'default', headers: { 'x-opencode-directory': '/worktree-a' }, }); diff --git a/packages/web/test/bun-test-shim.ts b/packages/web/test/bun-test-shim.ts new file mode 100644 index 00000000..3e50f1e1 --- /dev/null +++ b/packages/web/test/bun-test-shim.ts @@ -0,0 +1,31 @@ +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + test, + vi, +} from 'vitest'; + +const mock = Object.assign( + unknown>(implementation?: T) => vi.fn(implementation), + { + module: vi.mock, + }, +); + +export { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + mock, + test, + vi, +}; diff --git a/packages/web/vitest.config.ts b/packages/web/vitest.config.ts new file mode 100644 index 00000000..275cd22f --- /dev/null +++ b/packages/web/vitest.config.ts @@ -0,0 +1,10 @@ +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + 'bun:test': fileURLToPath(new URL('./test/bun-test-shim.ts', import.meta.url)), + }, + }, +});