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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ The eight pages under `pages/dashboard/project/[projectId]/environment/[environm

## Stripe in the browser

A panel contacts Stripe's hosts only after a click on Subscribe now in the onboarding plan step: `plan-step.tsx` imports `@stripe/stripe-js/pure` and calls `loadStripe` in that handler. The package's main entry injects `js.stripe.com/v3` as soon as the module runs, even with no key, and the script then calls `m.stripe.network` and `m.stripe.com` by itself. The pages router's prefetch runs a page's module on idle, so an import anywhere under a page linked from the sidebar reaches every panel page. The billing page needs no browser SDK: it redirects to the URL of the checkout session. `__test__/billing/stripe-script-on-demand.test.ts` fails on the main entry; after an upstream merge, check that it still passes.
The browser never loads Stripe's script. Both places that start a checkout, the billing page and the onboarding plan step, redirect to the `url` of the checkout session that `stripe.createCheckoutSession` returns, so `@stripe/stripe-js` is not a dependency (its 10.x removed `redirectToCheckout`, the only call the plan step used). The package's main entry used to inject `js.stripe.com/v3` as soon as the module ran, even with no key, and the pages router's prefetch runs a page's module on idle, so an import anywhere under a page linked from the sidebar reached every panel page. `__test__/billing/stripe-script-on-demand.test.ts` fails on any import of the package and on the dependency itself; after an upstream merge, check that it still passes. `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` stays in `Dockerfile.cloud` and `deploy.yml` unused (the token cannot change workflows).

## Server runtime

Expand Down Expand Up @@ -187,6 +187,7 @@ One GitHub release `agenthits-vX.Y.Z` covers a batch of work, not a single PR (o
- `apps/schedules` declares no `ioredis` of its own: the queues pass `connection` as plain options, and `bullmq` brings `ioredis` as an exact version. A direct `ioredis` at any other version leaves two copies in the lockfile.
- `bullmq` stays on 5.x (owner's decision 2026-11-09): 6.0 removed the legacy repeatable jobs (`repeat` in `Queue#add`, `getRepeatableJobs`, `removeRepeatable`, `removeRepeatableByKey`) that `apps/schedules/src/queue.ts` is built on, made `ioredis` an optional peer and replaced `connection` with a `BackendFactory`. `apps/schedules` is upstream's cloud scheduler, which the fork's panel does not run (its schedules use node-schedule), and upstream itself is on bullmq 5.67, so a rewrite to job schedulers would only bring conflicts at each upstream sync. The `^5` range keeps minor and patch updates coming.
- `inngest` stays at 4.26.1: 4.26.2 ships `sandbox-tool/dist/inngest-tool-linux-amd64`, built with Go 1.22, and the image scan fails on its Go stdlib findings. Move it when a release drops that binary or builds it with a current Go; do not add these findings to `.trivyignore.yaml`.
- `stripe` stays at 17.2.1 with `apiVersion: "2024-09-30.acacia"` in `billing.ts`, `routers/stripe.ts` and `pages/api/stripe/webhook.ts`: 23.1.0 only types its own newest API version (`2026-10-28.endive`) and has no `Invoice.subscription` (it moved under `parent.subscription_details`), so a bump means moving the code to ~two years of changed object shapes (`current_period_*` on items, invoice parents, and more) and the Stripe account's webhook endpoint to the same version in one cutover. That code runs only with `IS_CLOUD`, no test keys exist here, and a real checkout was never exercised; move it with the cloud billing owner, against a Stripe test account. (Checked 2026-11-11 on the VM: 5 type errors in 4 files.)
- `ssh2` stays at 1.16.0: 1.17.0's native crypto module (`lib/protocol/crypto/src/binding.cc`) calls `v8::Context::GetIsolate()`, which Node 26's V8 no longer has, so `node-gyp` fails at install and ssh2 falls back to its JavaScript crypto. On Node that made 20 terminal open-and-close cycles of `__test__/wss/terminal-ssh-release.test.ts` take over 5 s instead of 1.9 s. Bun never loads the native module, 1.16.0 included, so the panel itself ran the same on both. Move it when a release builds on Node 26: `bindingAvailable` from `ssh2/lib/protocol/crypto.js` under `node` must be `true`.
- Everything else takes a `^` range in every `package.json` that declares it, so the daily update moves it. A new exact version needs a line here saying why. On `0.x`, `^` narrows to patch (`^0.12.0` is `<0.13.0`), so a minor of `@xterm/addon-*`, `drizzle-zod` or another `0.x` package moves by hand.
- Every other root override is a range `>=<fixed version> <next major>` (for `0.x`, `<next minor>`), so the override keeps the fixed floor and the workflow still moves the package. A security override follows the same form; an exact version there freezes the package and can silently override a newer direct dependency.
Expand Down
17 changes: 11 additions & 6 deletions apps/dokploy/__test__/billing/stripe-script-on-demand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ const clientSources = ["components", "hooks", "lib", "pages", "utils"].flatMap(
.map((file) => path.join(dir, file)),
);

describe("Stripe's browser script", () => {
it("is reached only through the entry that waits for loadStripe", () => {
describe("Stripe in the browser", () => {
it("is never loaded: checkout redirects to the session URL", () => {
const planStep = "components/dashboard/onboarding/steps/plan-step.tsx";
expect(clientSources).toContain(planStep);
expect(read(planStep)).toContain('from "@stripe/stripe-js/pure"');
expect(read(planStep)).toContain("window.location.href = session.url");

const importsMainEntry = clientSources.filter((file) =>
/["']@stripe\/stripe-js["']/.test(read(file)),
const importsStripeJs = clientSources.filter((file) =>
/["']@stripe\/stripe-js(\/[a-z]+)?["']/.test(read(file)),
);
expect(importsMainEntry).toEqual([]);
expect(importsStripeJs).toEqual([]);
});

it("does not declare the browser SDK", () => {
const manifest = JSON.parse(read("package.json"));
expect(manifest.dependencies["@stripe/stripe-js"]).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Not the main entry: it injects Stripe's script as soon as this module runs, and the
// sidebar's prefetch of the home page runs it on every panel, self-hosted ones included.
import { loadStripe } from "@stripe/stripe-js/pure";
import { ArrowRightIcon, CheckIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
Expand Down Expand Up @@ -72,15 +71,14 @@ export const PlanStep = ({ onNext }: Props) => {
if (!productId) return;
setLoading(`checkout:${tier}`);
try {
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripe = publishableKey ? await loadStripe(publishableKey) : null;
const session = await createCheckoutSession({
tier,
productId,
serverQuantity: tier === "startup" ? STARTUP_SERVERS_INCLUDED : 1,
isAnnual: false,
});
await stripe?.redirectToCheckout({ sessionId: session.sessionId });
if (!session.url) throw new Error("Checkout session has no URL");
window.location.href = session.url;
} catch {
toast.error("Error starting checkout");
setLoading(null);
Expand Down
1 change: 0 additions & 1 deletion apps/dokploy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
"@openpanel/nextjs": "^1.5.4",
"@react-email/components": "^1.0.12",
"@stepperize/react": "8.0.1",
"@stripe/stripe-js": "^4.10.0",
"@tailwindcss/typography": "^0.5.20",
"@tanstack/react-query": "^5.107.1",
"@tanstack/react-table": "^9.3.1",
Expand Down
3 changes: 0 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.