Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI Checks

# Lightweight, portable PR gate: a TypeScript typecheck via the repo's own
# `typecheck` turbo tasks (tsc --noEmit).
#
# It intentionally does NOT use `vp check` / `vp test`: those load
# vite.config.ts, which imports the `vite-plus` package. `vite-plus` is
# provisioned by the local `vp` toolchain but is not a declared dependency and
# is not resolvable in a clean CI checkout, so any vite-config-loading command
# fails there. This gate is also separate from the heavy Nix `om ci` workflow.
#
# `@stackpanel/sdk` is excluded because its typecheck has a pre-existing failure
# (its tsconfig does not pull in node types) unrelated to this gate. Remove the
# `--filter` exclusion once that's fixed so the gate covers every package that
# defines a `typecheck` script.
on:
pull_request:
push:
branches: [main]

# Least-privilege GITHUB_TOKEN (CodeQL: workflows should set explicit permissions).
permissions:
contents: read

concurrency:
group: ci-checks-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
typecheck:
name: typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.2"

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Typecheck (tsc --noEmit)
run: bunx turbo run typecheck --filter='!@stackpanel/sdk'
6 changes: 6 additions & 0 deletions nix/stackpanel/lib/containers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,13 @@ let
else
let
spec = defaultBaseImageSpecs.${type} or defaultBaseImageSpecs.alpine;
# Refuse to build from an unpinned base image: the unpinned specs carry
# lib.fakeSha256, which would otherwise fail later as a cryptic hash
# mismatch. Pin with `nix-prefetch-docker` (set imageDigest + sha256).
isPinned = (spec.sha256 or lib.fakeSha256) != lib.fakeSha256;
in
assert lib.assertMsg isPinned
"stackpanel: base image '${spec.imageName}' is not pinned (still lib.fakeSha256); pin it with nix-prefetch-docker before building a container from it.";
nix2containerLib.pullImage spec;

# ---------------------------------------------------------------------------
Expand Down
4 changes: 3 additions & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
}
},
"type": "module",
"scripts": {},
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@stackpanel/config": "workspace:*"
},
Expand Down
24 changes: 18 additions & 6 deletions packages/auth/src/lib/polar-products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,25 @@ const SANDBOX_FREE = "6abf1427-cf83-4251-90d7-8852e7e5ca21";
*/
export function polarProducts(
env: DeployEnv = resolveDeployEnv(),
{ strict = true }: { strict?: boolean } = {},
): Record<PlanId, string> {
switch (env) {
case "production":
return {
pro: process.env.POLAR_PRO_PRODUCT_ID_PRODUCTION ?? SANDBOX_PRO,
free: process.env.POLAR_FREE_PRODUCT_ID_PRODUCTION ?? SANDBOX_FREE,
};
case "production": {
const pro = process.env.POLAR_PRO_PRODUCT_ID_PRODUCTION;
const free = process.env.POLAR_FREE_PRODUCT_ID_PRODUCTION;
if (pro && free) return { pro, free };
if (strict) {
throw new Error(
"Polar production product IDs are not configured. Set " +
"POLAR_PRO_PRODUCT_ID_PRODUCTION and POLAR_FREE_PRODUCT_ID_PRODUCTION. " +
"Refusing to silently fall back to sandbox products in production — " +
"that would serve sandbox checkouts to real users and never charge real cards.",
);
}
// Non-strict callers (e.g. the webhook inverse-lookup, which may run
// outside a production runtime) only need id->plan resolution.
return { pro: pro ?? SANDBOX_PRO, free: free ?? SANDBOX_FREE };
}
case "preview":
case "dev":
default:
Expand Down Expand Up @@ -65,7 +77,7 @@ export function planForProduct(productId: string | null | undefined): PlanId | "
if (!productId) return "unknown";
const envs: DeployEnv[] = ["production", "preview", "dev"];
for (const env of envs) {
const products = polarProducts(env);
const products = polarProducts(env, { strict: false });
for (const [plan, id] of Object.entries(products) as [PlanId, string][]) {
if (id === productId) return plan;
}
Expand Down
Loading