From 355becc3a85618110061c54a458c5a305ba851d9 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:36:37 -0700 Subject: [PATCH 01/12] test(cli): stop exporting test helper symbols used only in-file Nine symbols in apps/cli/tests/helpers were exported but only referenced inside their own module: DEFAULT_USER_AGENT and RecordedRequest in command-mocks, RecordedRequest, StubResponse, StubTransportFailure and RouteHandler in compute, LiveWorkspace in live, and StorageRoute and RecordedStorageRequest in storage. knip did not report them because the whole tests/ directory was excluded via `ignore` in knip.json. That exclusion is being removed so the helpers are analysed like any other source; dropping the unused exports first keeps that change from surfacing findings it should not have to carry. Co-Authored-By: Claude Fable 5.1 --- apps/cli/tests/helpers/command-mocks.ts | 4 ++-- apps/cli/tests/helpers/compute.ts | 8 ++++---- apps/cli/tests/helpers/live.ts | 2 +- apps/cli/tests/helpers/storage.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/cli/tests/helpers/command-mocks.ts b/apps/cli/tests/helpers/command-mocks.ts index 5b2bfe35fd..f2c2a86ea8 100644 --- a/apps/cli/tests/helpers/command-mocks.ts +++ b/apps/cli/tests/helpers/command-mocks.ts @@ -71,7 +71,7 @@ import { export const VALID_REF = "abcdefghijklmnopqrst"; export const VALID_TOKEN = "sbp_" + "a".repeat(40); export const DEFAULT_API_URL = "https://api.supabase.com"; -export const DEFAULT_USER_AGENT = "SupabaseCLI/0.0.0-dev"; +const DEFAULT_USER_AGENT = "SupabaseCLI/0.0.0-dev"; // No-op layers — drop-in for tests that don't assert on telemetry / cache state. export const mockLinkedProjectCacheLayer = Layer.succeed(LinkedProjectCache, { @@ -468,7 +468,7 @@ function makeHttpClientLayer( // otherwise the raw decoded string is stored. Falsy bodies (no request body) record `undefined`. export type HttpMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE"; -export interface RecordedRequest { +interface RecordedRequest { readonly url: string; readonly method: string; readonly headers: Readonly>; diff --git a/apps/cli/tests/helpers/compute.ts b/apps/cli/tests/helpers/compute.ts index 9605fdd9fc..eb712c5a3c 100644 --- a/apps/cli/tests/helpers/compute.ts +++ b/apps/cli/tests/helpers/compute.ts @@ -26,7 +26,7 @@ import { mockOutput, mockProcessControl, mockRuntimeInfo, mockTty } from "./mock export const COMPUTE_PROJECT_REF = "abcdefghijklmnopqrst"; -export interface RecordedRequest { +interface RecordedRequest { readonly method: string; readonly url: string; /** @@ -41,7 +41,7 @@ export interface RecordedRequest { readonly byteLength: number; } -export interface StubResponse { +interface StubResponse { readonly status: number; readonly body?: unknown; } @@ -51,7 +51,7 @@ export interface StubResponse { * Distinct from a `StubResponse` with an error status, which is a server that * answered. */ -export interface StubTransportFailure { +interface StubTransportFailure { readonly transportError: string; } @@ -62,7 +62,7 @@ function isTransportFailure( } /** How a test answers one request; sequential entries reply to repeated calls. */ -export type RouteHandler = +type RouteHandler = | StubResponse | StubTransportFailure | ReadonlyArray; diff --git a/apps/cli/tests/helpers/live.ts b/apps/cli/tests/helpers/live.ts index 828321e128..6d13ba887b 100644 --- a/apps/cli/tests/helpers/live.ts +++ b/apps/cli/tests/helpers/live.ts @@ -14,7 +14,7 @@ export type LiveProject = LiveCliProjectEnvironment["project"]; type RunOptions = NonNullable[1]>; type RunResult = Awaited>; -export interface LiveWorkspace { +interface LiveWorkspace { readonly path: string; } diff --git a/apps/cli/tests/helpers/storage.ts b/apps/cli/tests/helpers/storage.ts index 30d3bd0885..bc391afba0 100644 --- a/apps/cli/tests/helpers/storage.ts +++ b/apps/cli/tests/helpers/storage.ts @@ -31,7 +31,7 @@ import { * or recursive flows need one route per expected call unless `persist` is set. * `when` narrows a match by the parsed request body. */ -export interface StorageRoute { +interface StorageRoute { readonly method: string; /** Substring matched against the request URL. */ readonly match: string; @@ -49,7 +49,7 @@ export interface StorageRoute { readonly persist?: boolean; } -export interface RecordedStorageRequest { +interface RecordedStorageRequest { readonly method: string; readonly url: string; readonly headers: Record; From 0f1b89b03083940f6130de8b0daa9ce7c376a637 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:36:38 -0700 Subject: [PATCH 02/12] chore(config): stop exporting release plan variant interfaces ReleaseDuePlan and NoReleasePlan exist only to form the exported ReleasePlan union in the same file; nothing imports them directly. knip hid this because scripts/release-plan.ts was an entry file, and exports of entry files are not checked by default. The upcoming knip config keeps the file as an entry, so this would otherwise stay invisible. Co-Authored-By: Claude Fable 5.1 --- packages/config/scripts/release-plan.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/config/scripts/release-plan.ts b/packages/config/scripts/release-plan.ts index 4bf3d2b049..5327a5612c 100644 --- a/packages/config/scripts/release-plan.ts +++ b/packages/config/scripts/release-plan.ts @@ -30,7 +30,7 @@ interface ConfigPackageJson { readonly private?: boolean; } -export interface ReleaseDuePlan { +interface ReleaseDuePlan { readonly due: true; readonly version: string; readonly bumpType: string; @@ -38,7 +38,7 @@ export interface ReleaseDuePlan { readonly isPrivate: boolean; } -export interface NoReleasePlan { +interface NoReleasePlan { readonly due: false; } From 1277e9f6dff565ed18e1bb0c2ee48478a814fddd Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:37:04 -0700 Subject: [PATCH 03/12] chore(cli): remove the unused PropFlags telemetry property name PropFlags was declared in the telemetry event catalog but never referenced by any event definition, emitter, or test. knip did not report it because event-catalog.ts was excluded via `ignore` in knip.json; that exclusion is being removed so the catalog is analysed like the rest of the source. Co-Authored-By: Claude Fable 5.1 --- apps/cli/src/shared/telemetry/event-catalog.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/cli/src/shared/telemetry/event-catalog.ts b/apps/cli/src/shared/telemetry/event-catalog.ts index 513828a59c..5720929844 100644 --- a/apps/cli/src/shared/telemetry/event-catalog.ts +++ b/apps/cli/src/shared/telemetry/event-catalog.ts @@ -28,7 +28,6 @@ export const PropCliVersion = "cli_version"; export const PropEnvSignals = "env_signals"; export const PropCommandRunId = "command_run_id"; export const PropCommand = "command"; -export const PropFlags = "flags"; export const PropExitCode = "exit_code"; export const PropDurationMs = "duration_ms"; export const PropOutputFormat = "output_format"; From fe974945b47ee389fe34b453d3d9cf156b7b5f94 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:37:07 -0700 Subject: [PATCH 04/12] chore(api): drop the unused undici dependency @supabase/api declared undici as a runtime dependency since the package was created, but nothing in the package or the repo imports it. The only consumer is @effect/platform-node, which declares undici as its own dependency and therefore keeps receiving it transitively. knip reported this correctly; the report was silenced with ignoreDependencies in knip.json. Removing the dependency retires the workaround instead of keeping it. Co-Authored-By: Claude Fable 5.1 --- packages/api/package.json | 3 +-- pnpm-lock.yaml | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 13bdbaa8cf..538628d160 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -23,8 +23,7 @@ "dependencies": { "@effect/platform-bun": "catalog:", "@effect/platform-node": "catalog:", - "effect": "catalog:", - "undici": "^8.10.0" + "effect": "catalog:" }, "devDependencies": { "@tsconfig/bun": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b61093fa5d..8cf9712c65 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -492,9 +492,6 @@ importers: effect: specifier: 'catalog:' version: 4.0.0-rc.112 - undici: - specifier: ^8.10.0 - version: 8.10.1 devDependencies: '@tsconfig/bun': specifier: 'catalog:' From e6edfe7d01ff6bcf9aa03e3d38a5a76eac37bdb4 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:37:07 -0700 Subject: [PATCH 05/12] ci: run the .github/scripts test suites through a root package script The bun:test suites under .github/scripts were only ever invoked inline from the github-scripts-ci workflow. Moving the invocation into a root `test:github-scripts` script gives developers a local command and lets knip's Bun plugin derive the test-file entries from the script, which is what allows the hard-coded `.github/scripts/**/*.ts` entry to leave the knip config. The workflow now calls the script and keeps its existing guard against silent zero-test discovery. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/github-scripts-ci.yml | 11 ++++++----- package.json | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/github-scripts-ci.yml b/.github/workflows/github-scripts-ci.yml index ada4560ee1..b7ce2a3df5 100644 --- a/.github/workflows/github-scripts-ci.yml +++ b/.github/workflows/github-scripts-ci.yml @@ -46,11 +46,12 @@ jobs: - name: Run tests run: | set -uo pipefail - # The leading "./" is load-bearing: `bun test .github/scripts` - # (without it) silently discovers ZERO tests and still exits 0. - # Capture output to a file instead of piping it, so `test_exit` - # below is `bun test`'s own exit code, not `tee`/`grep`'s. - bun test ./.github/scripts > /tmp/github-scripts-test-output.txt 2>&1 + # The root `test:github-scripts` script runs `bun test ./.github/scripts`; + # its leading "./" is load-bearing, since `bun test .github/scripts` + # silently discovers ZERO tests and still exits 0. Capture output to a + # file instead of piping it, so `test_exit` below is the script's own + # exit code, not `tee`/`grep`'s. + pnpm run test:github-scripts > /tmp/github-scripts-test-output.txt 2>&1 test_exit=$? cat /tmp/github-scripts-test-output.txt if [ "$test_exit" -ne 0 ]; then diff --git a/package.json b/package.json index 4335c748ff..748c796942 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test:live": "pnpm exec turbo run supabase#test:live --concurrency=1 --", "record": "pnpm exec turbo run @supabase/cli-e2e#record --", "test:smoke": "pnpm exec turbo run supabase#test:smoke --", + "test:github-scripts": "bun test ./.github/scripts", "dev:docs": "pnpm exec turbo run @supabase/docs#dev", "test:unit": "pnpm exec turbo run test:unit:run --filter=!@supabase/cli-go --", "test:integration": "pnpm exec turbo run test:integration:run --", From 0185b0e6f528e53385c3590a294fac18e67f6358 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:44:52 -0700 Subject: [PATCH 06/12] test(cli): remove test helper mocks with no consumers Analysing apps/cli/tests with knip for the first time surfaced helpers that nothing imports. mockCrypto, mockCredentials, mockApi, mockProjectLinkRemote and withEnv in mocks.ts had no callers at all; the two tests that mention withEnv define their own local version. They are deleted together with the imports only they used. mockProjectLinkState, mockCliProjectLocalServiceVersions, mockComputeHttp and spawnSupabase are still used inside their own modules, so they lose the export keyword instead. Co-Authored-By: Claude Fable 5.1 --- apps/cli/tests/helpers/cli.ts | 2 +- apps/cli/tests/helpers/compute.ts | 2 +- apps/cli/tests/helpers/mocks.ts | 176 +----------------------------- 3 files changed, 5 insertions(+), 175 deletions(-) diff --git a/apps/cli/tests/helpers/cli.ts b/apps/cli/tests/helpers/cli.ts index 56b7d0a229..066ce33e4a 100644 --- a/apps/cli/tests/helpers/cli.ts +++ b/apps/cli/tests/helpers/cli.ts @@ -291,7 +291,7 @@ function outputTail(label: string, output: string): string { return `${label}:\n${tail}`; } -export function spawnSupabase( +function spawnSupabase( args: string[], options?: { cwd?: string; diff --git a/apps/cli/tests/helpers/compute.ts b/apps/cli/tests/helpers/compute.ts index eb712c5a3c..8d9f2628be 100644 --- a/apps/cli/tests/helpers/compute.ts +++ b/apps/cli/tests/helpers/compute.ts @@ -100,7 +100,7 @@ function isRouteSequence( * build-context upload, so a test can assert the whole request sequence — mint * the slot, PUT the bytes, deploy, poll — in the order it happened. */ -export function mockComputeHttp(routes: ComputeHttpRoutes) { +function mockComputeHttp(routes: ComputeHttpRoutes) { const requests: Array = []; const remaining = new Map>( Object.entries(routes).map(([route, handler]) => [ diff --git a/apps/cli/tests/helpers/mocks.ts b/apps/cli/tests/helpers/mocks.ts index d1176dde2a..09451ce931 100644 --- a/apps/cli/tests/helpers/mocks.ts +++ b/apps/cli/tests/helpers/mocks.ts @@ -2,20 +2,14 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import process from "node:process"; import { BunServices } from "@effect/platform-bun"; -import { Deferred, Effect, Layer, Option, Redacted, Stream } from "effect"; +import { Deferred, Effect, Layer, Option, Stream } from "effect"; import type { CliProjectEnvironment, CliProjectPaths } from "@supabase/config"; -import { Api } from "../../src/shared/auth/api.service.ts"; -import type { LoginSessionResponse, ProfileResponse } from "../../src/shared/auth/api.service.ts"; -import { Credentials } from "../../src/shared/auth/credentials.service.ts"; -import { Crypto } from "../../src/shared/auth/crypto.service.ts"; -import { ApiError } from "../../src/shared/auth/errors.ts"; import { cliSettingsLayer } from "../../src/shared/config/cli-settings.layer.ts"; import { CliProjectHome } from "../../src/shared/config/cli-project-home.service.ts"; import { CliProjectLocalServiceVersions, type LocalServiceVersionsState, } from "../../src/shared/config/cli-project-local-service-versions.service.ts"; -import { ProjectLinkRemote } from "../../src/shared/config/project-link-remote.service.ts"; import { ProjectLinkState, type ProjectLinkStateValue, @@ -70,18 +64,6 @@ export function mockBrowser(): Layer.Layer { }); } -export function mockCrypto(token = "sbp_" + "a".repeat(40)): Layer.Layer { - return Layer.succeed(Crypto, { - generateKeyPair: Effect.sync(() => ({ - ecdh: {} as import("node:crypto").ECDH, - publicKeyHex: "04abcd", - })), - generateSessionId: Effect.sync(() => "test-session-id"), - defaultTokenName: Effect.sync(() => "cli_test@host_123"), - decryptToken: () => Effect.succeed(token), - }); -} - export function mockStdin(isTTY: boolean, pipedInput?: string | Uint8Array): Layer.Layer { const pipedBytes = pipedInput === undefined @@ -200,33 +182,6 @@ export function mockProcessControl( }; } -export function mockCredentials(opts: { existingToken?: string } = {}) { - let savedToken: string | undefined; - let deleteWasCalled = false; - return { - layer: Layer.succeed(Credentials, { - getAccessToken: Effect.sync(() => { - const token = opts.existingToken ?? savedToken; - return token ? Option.some(Redacted.make(token)) : Option.none(); - }), - saveAccessToken: (token: string | Redacted.Redacted) => - Effect.sync(() => { - savedToken = typeof token === "string" ? token : Redacted.value(token); - }), - deleteAccessToken: Effect.sync(() => { - deleteWasCalled = true; - return !!(opts.existingToken ?? savedToken); - }), - }), - get savedToken() { - return savedToken; - }, - get deleteWasCalled() { - return deleteWasCalled; - }, - }; -} - export function mockOutput( opts: { format?: OutputFormat; @@ -470,56 +425,6 @@ export function mockOutput( }; } -export function mockApi( - opts: { - failTimes?: number; - response?: Partial; - profileResponse?: Partial; - profileError?: ApiError; - } = {}, -) { - let callCount = 0; - let profileCallCount = 0; - const failTimes = opts.failTimes ?? 0; - const response: LoginSessionResponse = { - access_token: "encrypted", - public_key: "abcd", - nonce: "1234", - ...opts.response, - }; - const profileResponse: ProfileResponse = { - gotrue_id: "user-123", - primary_email: "test@example.com", - username: "tester", - ...opts.profileResponse, - }; - - return { - layer: Layer.succeed(Api, { - fetchLoginSession: () => { - callCount++; - if (callCount <= failTimes) { - return Effect.fail(new ApiError({ detail: "network error" })); - } - return Effect.succeed(response); - }, - fetchProfile: () => { - profileCallCount++; - if (opts.profileError !== undefined) { - return Effect.fail(opts.profileError); - } - return Effect.succeed(profileResponse); - }, - }), - get callCount() { - return callCount; - }, - get profileCallCount() { - return profileCallCount; - }, - }; -} - /** * Like `mockAnalytics()`, but merges `CurrentAnalyticsContext` into captured event * properties. Use it when asserting on context-carried fields (`flags`, `groups`). @@ -720,7 +625,7 @@ function mockCliProjectHome( ); } -export function mockProjectLinkState( +function mockProjectLinkState( initialState?: ProjectLinkStateValue, ): Layer.Layer { let state = initialState; @@ -751,62 +656,7 @@ export function mockProjectLinkState( ); } -export function mockProjectLinkRemote( - opts: { - projects?: ReadonlyArray<{ - ref: string; - name: string; - region: string; - status: string; - organizationId?: string; - organizationSlug?: string; - }>; - linkedProject?: { - ref: string; - name: string; - region: string; - status: string; - organizationId?: string; - organizationSlug?: string; - versions: { - postgres?: string; - postgrest?: string; - auth?: string; - storage?: string; - }; - unavailableServices?: ReadonlyArray<"postgres" | "postgrest" | "auth" | "storage">; - }; - } = {}, -): Layer.Layer { - const projects = opts.projects ?? []; - const linkedProject = opts.linkedProject; - return Layer.succeed( - ProjectLinkRemote, - ProjectLinkRemote.of({ - listAccessibleProjects: Effect.succeed( - projects.map((project) => ({ - ...project, - organizationId: project.organizationId ?? "org_123", - organizationSlug: project.organizationSlug ?? "supabase", - })), - ), - fetchLinkedProject: (projectRef: string) => - Effect.gen(function* () { - if (linkedProject === undefined) { - return yield* Effect.fail(new Error(`No linked project mock for ${projectRef}`)); - } - return { - ...linkedProject, - organizationId: linkedProject.organizationId ?? "org_123", - organizationSlug: linkedProject.organizationSlug ?? "supabase", - unavailableServices: linkedProject.unavailableServices ?? [], - }; - }), - }), - ); -} - -export function mockCliProjectLocalServiceVersions( +function mockCliProjectLocalServiceVersions( initialState?: LocalServiceVersionsState, ): Layer.Layer { let state = initialState; @@ -843,23 +693,3 @@ export function emptyEnv() { cliSettingsLayer.pipe(Layer.provide(runtimeInfoLayer), Layer.provide(cliProjectContextLayer)), ); } - -export function withEnv(env: Record) { - const runtimeInfoLayer = mockRuntimeInfo(); - const cliProjectContextLayer = mockCliProjectContext(); - const envLayer = processEnvLayer(env); - const cliProjectHomeLayer = mockCliProjectHome(); - const analytics = mockAnalytics(); - return Layer.mergeAll( - BunServices.layer, - runtimeInfoLayer, - cliProjectContextLayer, - cliProjectHomeLayer, - analytics.layer, - mockTelemetryRuntime(), - envLayer, - mockTty(), - mockProcessControl().layer, - cliSettingsLayer.pipe(Layer.provide(runtimeInfoLayer), Layer.provide(cliProjectContextLayer)), - ); -} From 86af1f4d75b4cc776a5d140b4635040efeeb23fe Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:44:53 -0700 Subject: [PATCH 07/12] chore(cli): remove source exports that only test mocks consumed Deleting the unused test mocks left three production symbols with no consumer: - ProjectLinkRemote in project-link-remote.service.ts was only ever referenced by mockProjectLinkRemote; no command or layer imports the service, so the file is removed. - LinkedServiceVersions in project-link-state.service.ts was only used by that service. The schema it derived from stays, since the state schema still embeds it. - ProfileResponse in api.service.ts is still used by the Api service signature but was only imported by mockApi, so it is no longer exported. Co-Authored-By: Claude Fable 5.1 --- apps/cli/src/shared/auth/api.service.ts | 2 +- .../config/project-link-remote.service.ts | 32 ------------------- .../config/project-link-state.service.ts | 2 -- 3 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 apps/cli/src/shared/config/project-link-remote.service.ts diff --git a/apps/cli/src/shared/auth/api.service.ts b/apps/cli/src/shared/auth/api.service.ts index 5ecb0466fc..743ce06f23 100644 --- a/apps/cli/src/shared/auth/api.service.ts +++ b/apps/cli/src/shared/auth/api.service.ts @@ -9,7 +9,7 @@ export type LoginSessionResponse = { nonce: string; }; -export type ProfileResponse = { +type ProfileResponse = { gotrue_id: string; primary_email: string; username: string; diff --git a/apps/cli/src/shared/config/project-link-remote.service.ts b/apps/cli/src/shared/config/project-link-remote.service.ts deleted file mode 100644 index bc65400208..0000000000 --- a/apps/cli/src/shared/config/project-link-remote.service.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Effect } from "effect"; -import { Context } from "effect"; -import type { LinkedServiceVersions } from "./project-link-state.service.ts"; - -const linkedProjectVersionServices = ["postgres", "postgrest", "auth", "storage"] as const; - -type LinkedProjectVersionService = (typeof linkedProjectVersionServices)[number]; - -interface AccessibleProject { - readonly ref: string; - readonly name: string; - readonly region: string; - readonly status: string; - readonly organizationId: string; - readonly organizationSlug: string; -} - -interface LinkedProjectSnapshot extends AccessibleProject { - readonly versions: LinkedServiceVersions; - readonly unavailableServices: ReadonlyArray; -} - -interface ProjectLinkRemoteShape { - readonly listAccessibleProjects: Effect.Effect, unknown>; - readonly fetchLinkedProject: ( - projectRef: string, - ) => Effect.Effect; -} - -export class ProjectLinkRemote extends Context.Service()( - "supabase/cli/ProjectLinkRemote", -) {} diff --git a/apps/cli/src/shared/config/project-link-state.service.ts b/apps/cli/src/shared/config/project-link-state.service.ts index b78b834fb6..363de89e71 100644 --- a/apps/cli/src/shared/config/project-link-state.service.ts +++ b/apps/cli/src/shared/config/project-link-state.service.ts @@ -13,8 +13,6 @@ const LinkedServiceVersionsSchema = Schema.Struct({ storage: Schema.optionalKey(Schema.String), }); -export type LinkedServiceVersions = Schema.Schema.Type; - const ActiveBranchSchema = Schema.Struct({ ref: Schema.String, name: Schema.String, From 4357f4dfb9c5369c8512408f7580c58999aac368 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:44:53 -0700 Subject: [PATCH 08/12] test(cli): resolve sync-versions.ts relative to the npm registry helper The local-registry smoke helper invoked sync-versions.ts through `pnpm exec bun apps/cli/scripts/...` with the repo root as cwd. knip resolves Bun shell commands from the directory of the file that contains them, so it reported the script path as unresolved. Running `bun` with a path relative to the helper and cwd set to the helper's own directory keeps the behaviour (sync-versions.ts locates the repo root from import.meta.dir, not from cwd) and lets knip follow the reference. Co-Authored-By: Claude Fable 5.1 --- apps/cli/tests/helpers/npm-registry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli/tests/helpers/npm-registry.ts b/apps/cli/tests/helpers/npm-registry.ts index 0d2f30a4af..bc9f36015f 100644 --- a/apps/cli/tests/helpers/npm-registry.ts +++ b/apps/cli/tests/helpers/npm-registry.ts @@ -266,7 +266,7 @@ listen: 0.0.0.0:${PORT} // Sync versions across all packages console.log(`Syncing versions to ${version}...`); - await $`pnpm exec bun apps/cli/scripts/sync-versions.ts --version ${version}`.cwd(root).quiet(); + await $`bun ../../scripts/sync-versions.ts --version ${version}`.cwd(import.meta.dir).quiet(); console.log("Starting local npm registry..."); await using registry = await startVerdaccio(configPath, PORT); From 04dd242e0b0832bbc344de981a8c0ae660d0b272 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 16:44:54 -0700 Subject: [PATCH 09/12] chore(repo): rewrite knip config to lean on plugin defaults With an empty config on develop, knip's plugins already find every test file through each workspace's vitest config, and the GitHub Actions and package.json script parsers find most scripts. Nearly everything in knip.json was either redundant with those defaults or a blanket ignore hiding real findings, and the implicit script discovery broke as soon as a root plugin (vitest at the root) contributed a `**/` pattern, because knip then drops cross-workspace workflow entries. The config becomes knip.jsonc so every remaining exception carries the reason it exists: - `ignoreWorkspaces` replaces the `apps/cli-go/**` file ignore; the Go workspace only has a package.json for turbo. - One top-level `ignoreBinaries` lists tools the machine provides, each with the file that shells out to it. - `verdaccio` and `prettier` stay ignored because they are spawned through a node_modules/.bin path knip cannot trace. - `apps/cli` declares only the entries knip cannot infer: bin.ts (bundled by `bun build`), the scripts directory, the semantic-release plugin file (the knip plugin is root-only), and the platform smoke tests (string dispatched). Only the generated feedback types and the macro-read compute templates remain ignored. - `packages/config` declares its scripts directory for the same reason. - `packages/stack` keeps the `ws` ignore with a TODO, since knip does not credit the import in the e2e test and the cause is not yet known. - `exclude: ["catalogReferences"]`, the `.repos/**` ignore (submodules knip already skips), the `tools/release` and `.github/scripts` root entries, every `src/**/*.test.ts` entry, and the `undici` ignore are removed. Co-Authored-By: Claude Fable 5.1 --- knip.json | 49 ------------------------------------------ knip.jsonc | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 49 deletions(-) delete mode 100644 knip.json create mode 100644 knip.jsonc diff --git a/knip.json b/knip.json deleted file mode 100644 index 72207b2ce5..0000000000 --- a/knip.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "./node_modules/knip/schema.json", - "exclude": ["catalogReferences"], - "workspaces": { - ".": { - "entry": [".github/scripts/**/*.ts", "tools/release/*.ts"], - "ignore": [".repos/**", "apps/cli-go/**"], - "ignoreBinaries": ["go"], - "ignoreDependencies": ["verdaccio"] - }, - "apps/cli": { - "entry": [ - "src/shared/cli/bin.ts", - "src/**/*.test.ts", - "src/**/*.e2e.test.ts", - "src/**/*.live.test.ts", - "scripts/analyze-commits-title.js" - ], - "ignore": [ - "scripts/*.ts", - "tests/**/*.ts", - "src/shared/feedback/database.types.ts", - "src/shared/telemetry/event-catalog.ts", - "src/shared/compute/stacks/**" - ], - "ignoreBinaries": ["mkfifo"], - "ignoreDependencies": ["prettier"] - }, - "apps/cli-e2e": { - "entry": ["src/**/*.e2e.test.ts", "tests/**/*.ts"] - }, - "apps/docs": { - "entry": ["mdx-components.tsx"] - }, - "packages/api": { - "entry": ["src/**/*.test.ts"], - "ignoreDependencies": ["undici"] - }, - "packages/config": { - "entry": ["src/**/*.test.ts"] - }, - "packages/stack": { - "ignoreDependencies": ["@types/ws", "ws"] - }, - "packages/cli-test-helpers": { - "entry": ["src/**/*.test.ts"] - } - } -} diff --git a/knip.jsonc b/knip.jsonc new file mode 100644 index 0000000000..22751c8ab4 --- /dev/null +++ b/knip.jsonc @@ -0,0 +1,63 @@ +{ + "$schema": "./node_modules/knip/schema.json", + "ignoreWorkspaces": ["apps/cli-go"], + // Binaries the machine provides rather than npm. `go` comes from mise. + "ignoreBinaries": [ + "go", + // apps/cli/tests/smoke-test-macos.ts + "brew", + // apps/cli/tests/smoke-test-windows.ts + "cygpath", + // apps/cli/src/commands/seed/buckets/buckets.integration.test.ts + "mkfifo", + // apps/cli/scripts/build.ts + "nfpm", + // apps/cli/scripts/build.ts + "rcodesign", + // apps/cli/tests/smoke-test-windows.ts + "scoop", + // The CLI under test in apps/cli/tests/smoke-test-{macos,windows}.ts. + "supabase", + ], + // Spawned through a node_modules/.bin path in tools/release/local-registry.ts and + // apps/cli/tests/helpers/npm-registry.ts, which knip cannot trace. + "ignoreDependencies": ["verdaccio"], + "workspaces": { + "apps/cli": { + "entry": [ + // Bundled by `bun build` in the build:shim script; knip's bun resolver derives no + // entry from `bun build`. + "src/shared/cli/bin.ts", + // Run from workflow steps. Declared because publish-docs-spec.ts is pipe-invoked and + // because a root plugin with a `**/` pattern makes knip drop cross-workspace workflow + // entries. + "scripts/*.ts", + // Referenced from package.json#release.plugins; knip's Semantic Release plugin only + // reads the root manifest. + "scripts/analyze-commits-title.js", + // tests/smoke-test.ts picks the platform file from a string map and spawns it. + "tests/smoke-test-*.ts", + ], + "ignore": [ + // Generated by the gen:feedback-types script. + "src/shared/feedback/database.types.ts", + // Starter templates read as text by compute-stacks.macro.ts, never imported. + "src/shared/compute/stacks/**", + ], + // TODO(CLI-2424): spawned through a node_modules/.bin path in scripts/publish-docs-spec.ts. + "ignoreDependencies": ["prettier"], + }, + "apps/docs": { + // Next.js convention file that knip's Next.js plugin does not list. + "entry": ["mdx-components.tsx"], + }, + "packages/config": { + // Run from workflow steps; declared for the same cross-workspace reason as apps/cli. + "entry": ["scripts/*.ts"], + }, + "packages/stack": { + // TODO(CLI-2423): knip does not credit the ws import in src/public/whole-stack.e2e.test.ts. + "ignoreDependencies": ["@types/ws", "ws"], + }, + }, +} From 1204c69d3249d22df64563a0a2d529d62baf6a75 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 17:30:32 -0700 Subject: [PATCH 10/12] chore: wtf --- knip.jsonc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/knip.jsonc b/knip.jsonc index 22751c8ab4..0710de7ca1 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -1,6 +1,7 @@ { "$schema": "./node_modules/knip/schema.json", - "ignoreWorkspaces": ["apps/cli-go"], + // Required in the event that git submodules are present. Do not remove this, despite knip warnings. + "ignore": [".repos/**"], // Binaries the machine provides rather than npm. `go` comes from mise. "ignoreBinaries": [ "go", @@ -22,6 +23,7 @@ // Spawned through a node_modules/.bin path in tools/release/local-registry.ts and // apps/cli/tests/helpers/npm-registry.ts, which knip cannot trace. "ignoreDependencies": ["verdaccio"], + "ignoreWorkspaces": ["apps/cli-go"], "workspaces": { "apps/cli": { "entry": [ From 527a0e3ac1d6e8d37b3c46905bbe9d53c98ce1db Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 17:34:41 -0700 Subject: [PATCH 11/12] chore: tweaks based on feedback --- knip.jsonc | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/knip.jsonc b/knip.jsonc index 0710de7ca1..c1f1a81f9a 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -2,24 +2,8 @@ "$schema": "./node_modules/knip/schema.json", // Required in the event that git submodules are present. Do not remove this, despite knip warnings. "ignore": [".repos/**"], - // Binaries the machine provides rather than npm. `go` comes from mise. - "ignoreBinaries": [ - "go", - // apps/cli/tests/smoke-test-macos.ts - "brew", - // apps/cli/tests/smoke-test-windows.ts - "cygpath", - // apps/cli/src/commands/seed/buckets/buckets.integration.test.ts - "mkfifo", - // apps/cli/scripts/build.ts - "nfpm", - // apps/cli/scripts/build.ts - "rcodesign", - // apps/cli/tests/smoke-test-windows.ts - "scoop", - // The CLI under test in apps/cli/tests/smoke-test-{macos,windows}.ts. - "supabase", - ], + // `go` comes from mise. + "ignoreBinaries": ["go"], // Spawned through a node_modules/.bin path in tools/release/local-registry.ts and // apps/cli/tests/helpers/npm-registry.ts, which knip cannot trace. "ignoreDependencies": ["verdaccio"], @@ -46,6 +30,23 @@ // Starter templates read as text by compute-stacks.macro.ts, never imported. "src/shared/compute/stacks/**", ], + // Binaries the machine provides rather than npm. + "ignoreBinaries": [ + // apps/cli/tests/smoke-test-macos.ts + "brew", + // apps/cli/tests/smoke-test-windows.ts + "cygpath", + // apps/cli/src/commands/seed/buckets/buckets.integration.test.ts + "mkfifo", + // apps/cli/scripts/build.ts + "nfpm", + // apps/cli/scripts/build.ts + "rcodesign", + // apps/cli/tests/smoke-test-windows.ts + "scoop", + // The CLI under test in apps/cli/tests/smoke-test-{macos,windows}.ts. + "supabase", + ], // TODO(CLI-2424): spawned through a node_modules/.bin path in scripts/publish-docs-spec.ts. "ignoreDependencies": ["prettier"], }, From c9aa3b785d4e724f695efe65164ed387abaf87eb Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 11 Sep 2026 17:36:11 -0700 Subject: [PATCH 12/12] chore: more copy edits based on feedback --- knip.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knip.jsonc b/knip.jsonc index c1f1a81f9a..2d52beafc1 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -5,7 +5,7 @@ // `go` comes from mise. "ignoreBinaries": ["go"], // Spawned through a node_modules/.bin path in tools/release/local-registry.ts and - // apps/cli/tests/helpers/npm-registry.ts, which knip cannot trace. + // bunx in apps/cli/tests/helpers/npm-registry.ts, which knip cannot trace. "ignoreDependencies": ["verdaccio"], "ignoreWorkspaces": ["apps/cli-go"], "workspaces": {