From 605e72d2e85a5ed22bbaa3010c62c465a8bd6cbf Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:38:25 +0000 Subject: [PATCH 1/3] refactor(cli): cover `go-jwt` and `config-pull-run` with effect lint (CLI-2651) (#7174) ## TL;DR brings `go-jwt` and `config-pull-run` under the effect lint, with the `config-validate` missing-path test ## whats introduced? - `go-jwt.*`, `config-pull-run.*` and `config-validate.*` join the allow list - `go-jwt` reads the clock through `DateTime` instead of `Date.now`, with the same value, and its tests drop `async` for the same promise chains - `local-config-values`, `path-containment`, `functions-go-config`, `config-validate.ts` and two of its tests stay out, since their sync filesystem access, live `process.env` reads and native error passthrough have no behaviour-identical effect form ## ref - related: CLI-2651 --- .oxlintrc.effect.json | 6 +++ ...ig-validate.deep-missing-path.unit.test.ts | 31 ++++++------ apps/cli/src/command-internal/go-jwt.ts | 5 +- .../src/command-internal/go-jwt.unit.test.ts | 49 +++++++++++-------- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/.oxlintrc.effect.json b/.oxlintrc.effect.json index 5499d9f16a..f2e7aa22cd 100644 --- a/.oxlintrc.effect.json +++ b/.oxlintrc.effect.json @@ -28,6 +28,8 @@ "!apps/cli/src/command-internal/branch-target.*", "!apps/cli/src/command-internal/bundled-postgres-client.*", "!apps/cli/src/command-internal/colors.*", + "!apps/cli/src/command-internal/config-pull-run.*", + "!apps/cli/src/command-internal/config-validate.*", "!apps/cli/src/command-internal/connect-errors.*", "!apps/cli/src/command-internal/container-cli.*", "!apps/cli/src/command-internal/db-*.ts", @@ -52,6 +54,7 @@ "!apps/cli/src/command-internal/go-duration.*", "!apps/cli/src/command-internal/go-float.*", "!apps/cli/src/command-internal/go-json.*", + "!apps/cli/src/command-internal/go-jwt.*", "!apps/cli/src/command-internal/go-output-flag.*", "!apps/cli/src/command-internal/go-output.encoders.*", "!apps/cli/src/command-internal/go-quote.ts", @@ -120,6 +123,9 @@ "!apps/cli/src/command-internal/workdir-project.*", "!apps/cli/src/command-internal/workdir-search.ts", "!apps/cli/src/command-internal/workdir-validation.ts", + "apps/cli/src/command-internal/config-validate.parity.unit.test.ts", + "apps/cli/src/command-internal/config-validate.ts", + "apps/cli/src/command-internal/config-validate.unit.test.ts", "apps/cli/src/command-internal/db-bootstrap/local-db-running.integration.test.ts", "apps/cli/src/command-internal/db-bootstrap/local-db-running.ts" ] diff --git a/apps/cli/src/command-internal/config-validate.deep-missing-path.unit.test.ts b/apps/cli/src/command-internal/config-validate.deep-missing-path.unit.test.ts index 66bb04299c..d619b6b5e3 100644 --- a/apps/cli/src/command-internal/config-validate.deep-missing-path.unit.test.ts +++ b/apps/cli/src/command-internal/config-validate.deep-missing-path.unit.test.ts @@ -1,4 +1,5 @@ -import { join } from "node:path"; +import * as BunPath from "@effect/platform-bun/BunPath"; +import { Effect, Path } from "effect"; import { describe, expect, it, vi } from "vitest"; /** @@ -25,22 +26,22 @@ vi.mock("node:fs", () => ({ })); const FAKE_EXISTING_BASE = "/fake/project-root"; +const path = Effect.runSync(Effect.provide(Path.Path, BunPath.layer)); describe("canonicalPathForContainment (via resolveEmailTemplateContentPath)", () => { - it("resolves a 5,000-component-deep missing content_path without blowing the call stack", async () => { - const { resolveEmailTemplateContentPath } = await import("./config-validate.ts"); + it("resolves a 5,000-component-deep missing content_path without blowing the call stack", () => + import("./config-validate.ts").then(({ resolveEmailTemplateContentPath }) => { + const missingSegments = Array.from({ length: 5000 }, (_, i) => `missing-${i}`); + const contentPath = `${missingSegments.join("/")}/invite.html`; - const missingSegments = Array.from({ length: 5000 }, (_, i) => `missing-${i}`); - const contentPath = `${missingSegments.join("/")}/invite.html`; + const resolved = resolveEmailTemplateContentPath({ + section: "template", + name: "invite", + contentPath, + contentPresent: false, + base: FAKE_EXISTING_BASE, + }); - const resolved = resolveEmailTemplateContentPath({ - section: "template", - name: "invite", - contentPath, - contentPresent: false, - base: FAKE_EXISTING_BASE, - }); - - expect(resolved).toBe(join(FAKE_EXISTING_BASE, contentPath)); - }); + expect(resolved).toBe(path.join(FAKE_EXISTING_BASE, contentPath)); + })); }); diff --git a/apps/cli/src/command-internal/go-jwt.ts b/apps/cli/src/command-internal/go-jwt.ts index 175e399816..926569386d 100644 --- a/apps/cli/src/command-internal/go-jwt.ts +++ b/apps/cli/src/command-internal/go-jwt.ts @@ -1,4 +1,5 @@ import { createHmac, createPrivateKey, createSign } from "node:crypto"; +import { DateTime } from "effect"; import { encodeGoJsonCompact } from "./go-json.ts"; export { DEFAULT_SIGNING_KEY } from "@supabase/stack/defaults"; @@ -251,6 +252,8 @@ export function signJwtWithJwk(jwk: Jwk, payloadJson: string): string { * shape as {@link generateGoJwt} (`iss`/`role`/`exp`), except the expiry is 10 years from now. */ export function generateAsymmetricGoJwt(jwk: Jwk, role: "anon" | "service_role"): string { - const expiresAt = Math.floor(Date.now() / 1000) + GO_JWT_ASYMMETRIC_EXPIRY_SECONDS; + const expiresAt = + Math.floor(DateTime.toEpochMillis(DateTime.nowUnsafe()) / 1000) + + GO_JWT_ASYMMETRIC_EXPIRY_SECONDS; return signJwtWithJwk(jwk, JSON.stringify({ iss: GO_JWT_ISSUER, role, exp: expiresAt })); } diff --git a/apps/cli/src/command-internal/go-jwt.unit.test.ts b/apps/cli/src/command-internal/go-jwt.unit.test.ts index c67c88212f..0fa779aa94 100644 --- a/apps/cli/src/command-internal/go-jwt.unit.test.ts +++ b/apps/cli/src/command-internal/go-jwt.unit.test.ts @@ -1,5 +1,6 @@ import { createHmac, generateKeyPairSync } from "node:crypto"; import { importJWK, jwtVerify } from "jose"; +import { DateTime } from "effect"; import { describe, expect, it } from "vitest"; import { @@ -85,32 +86,38 @@ describe("generateGoJwt", () => { }); describe("generateAsymmetricGoJwt", () => { - it("signs and verifies an RS256 token from an RSA JWK", async () => { + it("signs and verifies an RS256 token from an RSA JWK", () => { const jwk = generateRsaJwk("rsa-kid"); const token = generateAsymmetricGoJwt(jwk, "anon"); - const publicKey = await importJWK(publicJwkOf(jwk), "RS256"); - const { payload, protectedHeader } = await jwtVerify(token, publicKey); - expect(payload).toMatchObject({ iss: "supabase-demo", role: "anon" }); - expect(protectedHeader).toEqual({ alg: "RS256", kid: "rsa-kid", typ: "JWT" }); + return importJWK(publicJwkOf(jwk), "RS256") + .then((publicKey) => jwtVerify(token, publicKey)) + .then(({ payload, protectedHeader }) => { + expect(payload).toMatchObject({ iss: "supabase-demo", role: "anon" }); + expect(protectedHeader).toEqual({ alg: "RS256", kid: "rsa-kid", typ: "JWT" }); + }); }); - it("signs an RS256 token from an RSA JWK missing CRT exponents (dp/dq/qi), matching Go", async () => { + it("signs an RS256 token from an RSA JWK missing CRT exponents (dp/dq/qi), matching Go", () => { const jwk = generateRsaJwk("rsa-kid"); const { dp: _dp, dq: _dq, qi: _qi, ...jwkWithoutCrtParams } = jwk; const token = generateAsymmetricGoJwt(jwkWithoutCrtParams, "anon"); - const publicKey = await importJWK(publicJwkOf(jwk), "RS256"); - const { payload, protectedHeader } = await jwtVerify(token, publicKey); - expect(payload).toMatchObject({ iss: "supabase-demo", role: "anon" }); - expect(protectedHeader).toEqual({ alg: "RS256", kid: "rsa-kid", typ: "JWT" }); + return importJWK(publicJwkOf(jwk), "RS256") + .then((publicKey) => jwtVerify(token, publicKey)) + .then(({ payload, protectedHeader }) => { + expect(payload).toMatchObject({ iss: "supabase-demo", role: "anon" }); + expect(protectedHeader).toEqual({ alg: "RS256", kid: "rsa-kid", typ: "JWT" }); + }); }); - it("signs and verifies an ES256 token from an EC JWK", async () => { + it("signs and verifies an ES256 token from an EC JWK", () => { const jwk = generateEcJwk("ec-kid"); const token = generateAsymmetricGoJwt(jwk, "service_role"); - const publicKey = await importJWK(publicJwkOf(jwk), "ES256"); - const { payload, protectedHeader } = await jwtVerify(token, publicKey); - expect(payload).toMatchObject({ iss: "supabase-demo", role: "service_role" }); - expect(protectedHeader).toEqual({ alg: "ES256", kid: "ec-kid", typ: "JWT" }); + return importJWK(publicJwkOf(jwk), "ES256") + .then((publicKey) => jwtVerify(token, publicKey)) + .then(({ payload, protectedHeader }) => { + expect(payload).toMatchObject({ iss: "supabase-demo", role: "service_role" }); + expect(protectedHeader).toEqual({ alg: "ES256", kid: "ec-kid", typ: "JWT" }); + }); }); it("omits the kid header entirely when the JWK has no kid", () => { @@ -123,7 +130,7 @@ describe("generateAsymmetricGoJwt", () => { it("sets a ~10-year expiry computed from the current time, not a fixed timestamp", () => { const jwk = generateRsaJwk(); - const before = Math.floor(Date.now() / 1000); + const before = Math.floor(DateTime.toEpochMillis(DateTime.nowUnsafe()) / 1000); const token = generateAsymmetricGoJwt(jwk, "anon"); const [, payload] = token.split("."); const decoded = JSON.parse(Buffer.from(payload ?? "", "base64url").toString()); @@ -202,7 +209,7 @@ describe("generateAsymmetricGoJwt", () => { }); describe("signJwtWithJwk", () => { - it("signs the caller's exact pre-encoded payload string verbatim (no re-serialization)", async () => { + it("signs the caller's exact pre-encoded payload string verbatim (no re-serialization)", () => { const jwk = generateEcJwk("ec-kid"); // Unsorted and containing `&`, which Go's encoder would normally HTML-escape: this function // must sign exactly the bytes it's given. @@ -211,9 +218,11 @@ describe("signJwtWithJwk", () => { const [, payload] = token.split("."); expect(decodeSegment(payload ?? "")).toBe(payloadJson); - const publicKey = await importJWK(publicJwkOf(jwk), "ES256"); - const { payload: verified } = await jwtVerify(token, publicKey); - expect(verified).toEqual({ role: "postgres", "sb-role": "mgmt-api & co" }); + return importJWK(publicJwkOf(jwk), "ES256") + .then((publicKey) => jwtVerify(token, publicKey)) + .then(({ payload: verified }) => { + expect(verified).toEqual({ role: "postgres", "sb-role": "mgmt-api & co" }); + }); }); it("HTML-escapes the kid in the header like Go's json.Marshal, unlike JSON.stringify (CLI-1961 Codex review finding)", () => { From 325d0f608b8da22e5642d3fe4a1563e341c550e6 Mon Sep 17 00:00:00 2001 From: "supabase-cli-releaser[bot]" <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:41:09 +0000 Subject: [PATCH 2/3] chore(stack): bump realtime to v2.144.3-r0 (#7203) This bumps realtime from v2.144.2-r0 to v2.144.3-r0. https://github.com/supabase/slim-services/releases/tag/realtime-v2.144.3-r0 Planned after supabase/slim-services published v2.144.3-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 2 +- apps/cli/src/shared/services/Dockerfile | 2 +- packages/stack/src/Artifacts.ts | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index b41496e918..45a985ad01 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -33,7 +33,7 @@ FROM supabase/supavisor:2.9.13 AS supavisor FROM supabase/gotrue:v2.197.0 AS gotrue -FROM supabase/realtime:v2.144.2 AS realtime +FROM supabase/realtime:v2.144.3 AS realtime FROM supabase/storage-api:v1.83.5 AS storage diff --git a/apps/cli/src/shared/services/Dockerfile b/apps/cli/src/shared/services/Dockerfile index b41496e918..45a985ad01 100644 --- a/apps/cli/src/shared/services/Dockerfile +++ b/apps/cli/src/shared/services/Dockerfile @@ -33,7 +33,7 @@ FROM supabase/supavisor:2.9.13 AS supavisor FROM supabase/gotrue:v2.197.0 AS gotrue -FROM supabase/realtime:v2.144.2 AS realtime +FROM supabase/realtime:v2.144.3 AS realtime FROM supabase/storage-api:v1.83.5 AS storage diff --git a/packages/stack/src/Artifacts.ts b/packages/stack/src/Artifacts.ts index ac1458b3a4..ec7c4fd686 100644 --- a/packages/stack/src/Artifacts.ts +++ b/packages/stack/src/Artifacts.ts @@ -241,23 +241,23 @@ const definitions: Readonly> = { realtime: definition( "realtime", { - upstreamVersion: "v2.144.2", + upstreamVersion: "v2.144.3", revision: 0, image: - "ghcr.io/supabase/cli/realtime:v2.144.2-r0@sha256:1286f5ef0cb3277153505655173b3bb56eb5b10b22eaf783b5a7b132a0535da4", - upstreamImage: "supabase/realtime:v2.144.2", + "ghcr.io/supabase/cli/realtime:v2.144.3-r0@sha256:06902c6205b9f8a8105d274ff806376f6463a717893f40a4c48e7c6642a892b0", + upstreamImage: "supabase/realtime:v2.144.3", natives: { "darwin-arm64": { - archive: "49bf73501f8725394f90a6b97110267d698a9dd9470474793ff88f8de2194280", - manifest: "ff21521edc8b66fc9703e843f54945dc1f29d128011c0c24a0801f23b07f8667", + archive: "b89bb6f01af36639fdbc0cdee176a330f43103f99d03470ba4c13367a26167ba", + manifest: "ecc29ce5e3802c2dc7b9fa494e597d5ffe89a8656650bf73226cfe5207704a43", }, "linux-amd64": { - archive: "8e0c024828bfb0ba835ae5d78907529edc84a911c7699bf0efa05bf78c7c391f", - manifest: "91f8365ad24506f6bf37be964263f0caa01554b1f4550371891334997e772384", + archive: "5601297a2523b60cd5902ffda214d66a31c9b921d05d0a39704f4411bf05df58", + manifest: "fb8c4028ebeb8aac4abcef8eb6a73a6828668d225845dd70bbb8644a102575ed", }, "linux-arm64": { - archive: "39134ec990b5c0469c79b69c1c1969f538f8d07edc7e2956afe0654b6b01d1f7", - manifest: "b447d793963a9051ab59435d298f1f627e598e397467e1817d5fd9de6c5ab0f9", + archive: "15f411c9fc4ecfe724571ccc016f0276d953af93e92a6a9173ffdd06f83e5079", + manifest: "2b171e3a171baa7444c79f582c104a6cac7430a605d5c61a151a70c099e0840d", }, }, }, From 3f3cdfa36f17e461bb45b973d5ab2b15cb6fda1a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:53:37 +0000 Subject: [PATCH 3/3] chore(repo): repair the develop-into-next sync --- .oxlintrc.effect.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.oxlintrc.effect.json b/.oxlintrc.effect.json index 1e7aa296d3..141325fc4c 100644 --- a/.oxlintrc.effect.json +++ b/.oxlintrc.effect.json @@ -26,9 +26,9 @@ "!apps/cli/src/command-internal/argv-flag-reconcile.*", "!apps/cli/src/command-internal/bitbucket-pipeline.ts", "!apps/cli/src/command-internal/branch-ref.*", - "!apps/cli/src/command-internal/byte-quote.ts", "!apps/cli/src/command-internal/branch-target.*", "!apps/cli/src/command-internal/bundled-postgres-client.*", + "!apps/cli/src/command-internal/byte-quote.ts", "!apps/cli/src/command-internal/colors.*", "!apps/cli/src/command-internal/config-pull-run.*", "!apps/cli/src/command-internal/config-validate.*",