From 40730f853b84b960f37682e0d2863b12875d780f Mon Sep 17 00:00:00 2001 From: Ben Kalsky Date: Fri, 3 Jul 2026 01:49:36 +0300 Subject: [PATCH 1/3] fix: export SumitChargeMode and validate mode at route creation Codex findings on #12: the 0.2.0 changelog advertised the SumitChargeMode type export but the ./next barrel never re-exported it, and an unknown mode from an untyped consumer resolved DEFAULT_PATHS[mode] to undefined, sending requests to https://api.sumit.co.ilundefined. Export the type and fail fast with a clear configuration error. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 8 ++++++++ src/next/createChargeRoute.test.ts | 6 ++++++ src/next/createChargeRoute.ts | 5 +++++ src/next/index.ts | 1 + 4 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc4e1d..b6c571a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this package are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- `SumitChargeMode` is now actually exported from `sumit-react/next` — 0.2.0 advertised the type but the barrel never re-exported it. +- `createSumitChargeRoute` validates `mode` at creation time. An unknown mode (e.g. `"oneoff"` from an untyped consumer) now throws a clear configuration error instead of sending requests to `https://api.sumit.co.ilundefined`. +- Publish workflows: only `main` may publish (`workflow_dispatch` was unrestricted), the stale `sumit-api` sibling checkout/build is gone (the dependency installs from npm), and the GitHub Packages workflow no longer points installs at `npm.pkg.github.com` — the publish step configures that registry itself. + ## [0.2.0] - 2026-05-02 ### Added diff --git a/src/next/createChargeRoute.test.ts b/src/next/createChargeRoute.test.ts index 265619a..ebd8fb4 100644 --- a/src/next/createChargeRoute.test.ts +++ b/src/next/createChargeRoute.test.ts @@ -27,6 +27,12 @@ function jsonRequest(body: unknown): Request { } describe("createSumitChargeRoute", () => { + it("throws at creation time for an unknown mode", () => { + expect(() => createSumitChargeRoute({ companyId: 1, apiKey: "k", mode: "oneoff" as never })).toThrow( + /unknown charge mode "oneoff"/, + ); + }); + it("returns 400 when body is invalid JSON", async () => { const handler = createSumitChargeRoute({ companyId: 1, apiKey: "k" }); const response = await handler( diff --git a/src/next/createChargeRoute.ts b/src/next/createChargeRoute.ts index 0410535..3a1f81f 100644 --- a/src/next/createChargeRoute.ts +++ b/src/next/createChargeRoute.ts @@ -59,6 +59,11 @@ export type SumitChargeRouteHandler = (request: Request) => Promise; export function createSumitChargeRoute(config: SumitChargeRouteConfig): SumitChargeRouteHandler { const mode: SumitChargeMode = config.mode ?? "recurring"; + // JS consumers can pass any string; an unknown mode would otherwise resolve + // `DEFAULT_PATHS[mode]` to undefined and target `…co.ilundefined` at runtime. + if (!(mode in DEFAULT_PATHS)) { + throw new Error(`sumit-react: unknown charge mode "${String(mode)}" — expected "recurring" or "oneOff".`); + } const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ""); const path = config.path ?? DEFAULT_PATHS[mode]; const upstreamFetch = config.fetch ?? fetch; diff --git a/src/next/index.ts b/src/next/index.ts index 6b21c1c..444c7f0 100644 --- a/src/next/index.ts +++ b/src/next/index.ts @@ -1,5 +1,6 @@ export { createSumitChargeRoute } from "./createChargeRoute.js"; export type { + SumitChargeMode, SumitChargeRouteConfig, SumitChargeRouteHandler, SumitChargeRequestBody, From 1cb5fc0248f62849c876af8bc0bc1ba0307beaf9 Mon Sep 17 00:00:00 2001 From: Ben Kalsky Date: Fri, 3 Jul 2026 01:49:36 +0300 Subject: [PATCH 2/3] ci: harden publish workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex findings on #4 and #7: workflow_dispatch could publish from any ref (now main-only), the sumit-api sibling checkout was unpinned and is now unnecessary (the dependency installs from npm), and the GitHub Packages workflow pointed installs at npm.pkg.github.com where public packages don't resolve — the publish step configures that registry and its auth itself. Co-Authored-By: Claude Fable 5 --- .github/workflows/publish-github-packages.yml | 35 ++++--------------- .github/workflows/publish-npm.yml | 31 +++------------- 2 files changed, 11 insertions(+), 55 deletions(-) diff --git a/.github/workflows/publish-github-packages.yml b/.github/workflows/publish-github-packages.yml index 7d11926..7d357ab 100644 --- a/.github/workflows/publish-github-packages.yml +++ b/.github/workflows/publish-github-packages.yml @@ -22,60 +22,39 @@ concurrency: jobs: publish: + # workflow_dispatch can be launched from any ref — only main may publish. + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - - name: Checkout sumit-react - uses: actions/checkout@v4 - with: - path: sumit-react - - - name: Checkout sumit-api peer package - uses: actions/checkout@v4 - with: - repository: Digitizers/sumit-api - path: sumit-api + - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: 10.26.0 + # No registry-url here: install must resolve from npmjs. The publish step + # below configures the GitHub Packages registry and auth itself. - uses: actions/setup-node@v4 with: node-version: 22 cache: pnpm - cache-dependency-path: | - sumit-react/pnpm-lock.yaml - sumit-api/pnpm-lock.yaml - registry-url: https://npm.pkg.github.com - - - name: Build sumit-api peer package - working-directory: sumit-api - run: | - pnpm install --frozen-lockfile - pnpm build - - name: Install sumit-react - working-directory: sumit-react + - name: Install run: pnpm install --frozen-lockfile - name: Typecheck - working-directory: sumit-react run: pnpm typecheck - name: Test - working-directory: sumit-react run: pnpm test - name: Build - working-directory: sumit-react run: pnpm build - name: Pack check - working-directory: sumit-react run: npm pack --dry-run - name: Build GitHub Packages tarball directory - working-directory: sumit-react run: | set -euo pipefail PACK_JSON=$(npm pack --json --ignore-scripts) @@ -96,7 +75,7 @@ jobs: NODE - name: Publish scoped package to GitHub Packages - working-directory: sumit-react/.publish-ghpkg + working-directory: .publish-ghpkg env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index a7b1e40..b1b8e2f 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -21,18 +21,11 @@ concurrency: jobs: publish: + # workflow_dispatch can be launched from any ref — only main may publish. + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - - name: Checkout sumit-react - uses: actions/checkout@v4 - with: - path: sumit-react - - - name: Checkout sumit-api peer package - uses: actions/checkout@v4 - with: - repository: Digitizers/sumit-api - path: sumit-api + - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: @@ -43,44 +36,28 @@ jobs: node-version: 22 registry-url: https://registry.npmjs.org cache: pnpm - cache-dependency-path: | - sumit-react/pnpm-lock.yaml - sumit-api/pnpm-lock.yaml - - - name: Build sumit-api peer package - working-directory: sumit-api - run: | - pnpm install --frozen-lockfile - pnpm build - - name: Install sumit-react - working-directory: sumit-react + - name: Install run: pnpm install --frozen-lockfile - name: Typecheck - working-directory: sumit-react run: pnpm typecheck - name: Test - working-directory: sumit-react run: pnpm test - name: Build - working-directory: sumit-react run: pnpm build - name: Pack check - working-directory: sumit-react run: npm pack --dry-run - name: Verify npm token - working-directory: sumit-react run: npm whoami env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish public package - working-directory: sumit-react run: | if [ "${{ inputs.dry_run }}" = "true" ]; then npm publish --access public --dry-run From 301c9fb16862ef6c39e40c393b1fa115b5d9cebf Mon Sep 17 00:00:00 2001 From: Ben Kalsky Date: Fri, 3 Jul 2026 01:54:02 +0300 Subject: [PATCH 3/3] fix: reject prototype keys as charge modes Codex follow-up on the new mode guard: `in` walks the prototype chain, so mode: "toString" passed validation and resolved DEFAULT_PATHS to an inherited function. Compare against the two known modes explicitly. Co-Authored-By: Claude Fable 5 --- src/next/createChargeRoute.test.ts | 6 ++++++ src/next/createChargeRoute.ts | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/next/createChargeRoute.test.ts b/src/next/createChargeRoute.test.ts index ebd8fb4..810691b 100644 --- a/src/next/createChargeRoute.test.ts +++ b/src/next/createChargeRoute.test.ts @@ -33,6 +33,12 @@ describe("createSumitChargeRoute", () => { ); }); + it("rejects prototype keys as modes", () => { + expect(() => createSumitChargeRoute({ companyId: 1, apiKey: "k", mode: "toString" as never })).toThrow( + /unknown charge mode "toString"/, + ); + }); + it("returns 400 when body is invalid JSON", async () => { const handler = createSumitChargeRoute({ companyId: 1, apiKey: "k" }); const response = await handler( diff --git a/src/next/createChargeRoute.ts b/src/next/createChargeRoute.ts index 3a1f81f..888c2da 100644 --- a/src/next/createChargeRoute.ts +++ b/src/next/createChargeRoute.ts @@ -61,7 +61,9 @@ export function createSumitChargeRoute(config: SumitChargeRouteConfig): SumitCha const mode: SumitChargeMode = config.mode ?? "recurring"; // JS consumers can pass any string; an unknown mode would otherwise resolve // `DEFAULT_PATHS[mode]` to undefined and target `…co.ilundefined` at runtime. - if (!(mode in DEFAULT_PATHS)) { + // Explicit comparison rather than `in`, which would accept prototype keys + // like "toString". + if (mode !== "recurring" && mode !== "oneOff") { throw new Error(`sumit-react: unknown charge mode "${String(mode)}" — expected "recurring" or "oneOff".`); } const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");