Implement Netlify deployment integration and payment templates#217
Implement Netlify deployment integration and payment templates#217Jackson57279 wants to merge 13 commits intomasterfrom
Conversation
- Added support for deploying projects to Netlify, including authentication and deployment status tracking. - Introduced new API routes for managing Netlify domains, environment variables, and deployment logs. - Created payment integration templates for various frameworks (Angular, React, Next.js, Svelte, Vue) using Autumn and Stripe. - Updated environment example and README to include new Netlify and payment integration configurations. - Enhanced project UI with deployment dashboard and export options to GitHub. This commit significantly expands the deployment capabilities and payment processing features of the application.
- Refactored deployment creation to utilize a project deployment counter for better tracking of deployment numbers. - Updated OAuth token handling to include encryption for access and refresh tokens, improving security. - Modified GitHub export functionality to check for connection existence without requiring an access token. - Improved error handling and validation in various API routes, ensuring more robust interactions with external services. - Enhanced UI components for deployment history and environment variable management, adding loading states and better user feedback. These changes significantly improve the deployment process and security of OAuth tokens, while also enhancing user experience in the application.
Add provider selection/templates and persist choices so generated projects get the right integration rules, plus add color theme selection and refresh the roadmap.
- Added creation timestamps to project deployment counters for better tracking. - Improved GitHub export functionality by integrating a new action to retrieve the current user's GitHub access token. - Updated OAuth handling to ensure required environment variables are set, enhancing security and error management. - Enhanced error handling in various API routes and improved user feedback in UI components. These changes streamline deployment processes and strengthen OAuth security, contributing to a more robust application.
- Revised README to reflect expanded multi-framework support in E2B sandboxes, including Next.js, React, Vue, Angular, and Svelte. - Updated tech stack details, replacing Inngest with Convex for real-time project management and persistence. - Enhanced SEO metadata generation to include structured data for Organization and WebSite types, improving search visibility. - Added new routes to the sitemap and robots.txt for better indexing. - Improved content clarity across various pages, emphasizing AI development solutions and framework comparisons. These changes improve documentation accuracy and enhance the application's SEO performance.
- Updated robots.txt to streamline disallowed paths and improve bot directives. - Enhanced client configuration in agents to include custom headers for better tracking. - Improved error handling in the runCodeAgent function, adding retry logic and detailed error collection for rate limit scenarios. These changes enhance the application's bot management, client tracking, and error resilience, contributing to a more robust and user-friendly experience.
…latform updates - Deleted the Clerk Billing migration summary, progress, quick reference, and setup checklist documents as they are no longer relevant. - Removed the changelog for November & December 2025, which detailed significant platform improvements and changes that have since been superseded. These deletions help streamline the documentation and ensure that only current and relevant information is maintained in the repository.
- Added `react-markdown` and `remark-gfm` to dependencies for improved markdown support. - Updated `robots.txt` to include directives for various AI crawlers, enhancing bot management. - Added a new blog URL to the sitemap and RSS feed for better indexing and visibility. These changes improve the application's dependency management, SEO performance, and bot handling capabilities.
- Added support for comparison pages in the sitemap, including a new `/compare` route and dynamic comparison URLs based on available comparisons. - Updated the homepage and pricing page metadata to better reflect Zapdev's offerings and improve search engine optimization. - Enhanced blog content with updated statistics and comparisons, emphasizing the advantages of using Zapdev over competitors. - Revised framework and solutions pages to provide clearer guidance on framework selection and available AI development solutions. These changes improve the application's SEO performance and enhance user navigation through better content organization.
…rove error handling - Added `react-markdown` and `remark-gfm` to package dependencies for improved markdown rendering. - Refactored `SANDBOX_ROOT` initialization to support environment variable configuration with validation for absolute paths. - Enhanced file path validation in the `createClaudeCodeTools` function to handle invalid paths more gracefully and provide clearer error messages. - Updated the RSS feed to use a fixed publication date for consistency. These changes improve dependency management, enhance path handling, and provide better user feedback in the application.
- Introduced `@inngest/realtime` and `inngest` dependencies to facilitate real-time event subscription and handling. - Updated the agent's `POST` route to generate a unique run ID and send requests to Inngest for processing. - Implemented subscription to Inngest events, allowing for real-time streaming of execution results and error handling. - Enhanced error handling to ensure proper cancellation of subscriptions and graceful error reporting. These changes improve the agent's responsiveness and provide a more interactive user experience during code execution.
|
🔍 Analyzing PR changes and preparing to run tests... |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughComprehensive platform expansion introducing agent orchestration via Inngest, Netlify deployment infrastructure with custom domains and environment variables, GitHub export capability, multi-database templates (Drizzle Neon/Convex), Autumn/Stripe payment integration templates for all frameworks, Claude Code support, color theming, SEO-optimized comparison pages, and blog functionality while removing deprecated billing migration documentation. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Extensive, heterogeneous changes spanning new orchestration layer (Inngest), multiple integration systems (Netlify, GitHub, Anthropic, Autumn), backend schema and Convex operations, payment/database template libraries, agent tooling, SEO/comparison infrastructure, theming system, and numerous API routes. High logic density in GitHub export, deployment management, and token encryption workflows; diverse file types and patterns (backend, frontend, API, utilities, documentation); significant surface area requiring holistic understanding of interconnected systems. Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🚀 Launching Scrapybara desktop... |
|
❌ Something went wrong: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae78f07b3d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const getNetlifyAccessToken = async (token?: string): Promise<string> => { | ||
| const connection = await fetchQuery(api.oauth.getConnection, { | ||
| provider: "netlify", | ||
| }, { token }) as NetlifyConnection | null; | ||
|
|
||
| if (!connection?.accessToken) { | ||
| throw new Error("Netlify connection not found. Please connect your Netlify account."); | ||
| } | ||
|
|
||
| return connection.accessToken; |
There was a problem hiding this comment.
Decrypt stored Netlify tokens before API calls
All Netlify API routes pull connection.accessToken from api.oauth.getConnection, but storeConnection encrypts access tokens before persisting them in convex/oauth.ts. That means getNetlifyAccessToken() returns an encrypted blob, so createNetlifyClient() will send an invalid bearer token and Netlify will reject every deploy/status/env/domain request with 401 after a successful OAuth connect. This needs a decrypting query/action (like the GitHub path uses) before passing the token to Netlify.
Useful? React with 👍 / 👎.
| try { | ||
| const decodedState = JSON.parse(Buffer.from(state, "base64").toString()); | ||
| if (decodedState.userId !== userId) { | ||
| throw new Error("State token mismatch"); |
There was a problem hiding this comment.
Validate Anthropic OAuth state with a signature/nonce
The callback only base64-decodes state and checks decodedState.userId, with no signature, nonce, or timestamp validation. If an attacker can learn/guess a user’s ID, they can forge state and supply their own OAuth code to bind the victim’s account to the attacker’s Anthropic token (CSRF/replay). The state needs to be unguessable and verified (e.g., HMAC + TTL like the Netlify flow).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
31 issues found across 111 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/app/api/deploy/netlify/callback/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/callback/route.ts:108">
P2: `crypto.timingSafeEqual()` throws an error when buffers have different lengths. Since `decodedState.signature` comes from user input, an attacker could trigger an exception by providing a signature of unexpected length. Add a length check before comparison, similar to other usages in this codebase.</violation>
</file>
<file name="src/app/api/auth/anthropic/callback/route.ts">
<violation number="1" location="src/app/api/auth/anthropic/callback/route.ts:47">
P1: **Security: OAuth state token lacks cryptographic signature (CSRF vulnerability)**
The state parameter is only base64-encoded JSON with no cryptographic signature, making it trivially forgeable. An attacker can craft a valid-looking state for any user by simply base64-encoding `{"userId": "targetId", "timestamp": ...}`.
This defeats the purpose of OAuth state validation (CSRF protection). The Netlify OAuth implementation in this codebase correctly uses HMAC-SHA256 signing with `NETLIFY_OAUTH_STATE_SECRET`.
Recommendation: Add HMAC-SHA256 signature verification similar to the Netlify implementation, or use a cryptographically random token stored server-side.</violation>
<violation number="2" location="src/app/api/auth/anthropic/callback/route.ts:48">
P2: **Security: OAuth state timestamp is not validated**
The state includes a `timestamp` field but it's never checked. This allows state tokens to be valid indefinitely. Consider adding TTL validation similar to the Netlify implementation (`STATE_TTL_MS = 10 * 60 * 1000`).</violation>
</file>
<file name="src/components/color-theme-provider.tsx">
<violation number="1" location="src/components/color-theme-provider.tsx:81">
P2: Context value object should be memoized with `useMemo` to prevent unnecessary re-renders of all consuming components. Without memoization, a new object reference is created on every render, causing all `useColorTheme()` consumers to re-render even when theme values haven't changed.</violation>
</file>
<file name="src/lib/github-api.ts">
<violation number="1" location="src/lib/github-api.ts:236">
P1: Path traversal vulnerability: `sanitizePath` does not block `..` sequences, null bytes, or control characters. An attacker could use paths like `../../../.git/hooks/pre-commit` to write files outside the intended directory. Consider reusing `isValidFilePath` from `sandbox-utils.ts` or adding validation for path traversal patterns.</violation>
</file>
<file name="src/app/api/deploy/netlify/auth/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/auth/route.ts:6">
P1: Hardcoded fallback secret undermines OAuth CSRF protection. If `NETLIFY_OAUTH_STATE_SECRET` is not set, attackers can forge valid state tokens using this public fallback value. Handle the missing secret like `NETLIFY_CLIENT_ID` by returning an error when not configured.</violation>
</file>
<file name="src/app/compare/[slug]/page.tsx">
<violation number="1" location="src/app/compare/[slug]/page.tsx:260">
P1: CTA button is non-functional - missing `Link` wrapper or `onClick` handler. This "Get Started Free" button won't navigate anywhere when clicked. Based on `src/app/compare/page.tsx`, this should be wrapped in a `<Link href="/">` component.</violation>
</file>
<file name="src/app/api/deploy/netlify/sites/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/sites/route.ts:36">
P2: Missing Netlify OAuth connection returns 500 instead of 401/403. When `getNetlifyAccessToken()` throws "Netlify connection not found", it's caught by the generic error handler and returned as a 500 server error. This is semantically incorrect—a missing OAuth connection is an authorization issue, not a server error. Consider checking for this specific error and returning an appropriate status code.</violation>
</file>
<file name="convex/deployments.ts">
<violation number="1" location="convex/deployments.ts:189">
P2: Query returns unbounded data. Consider adding a `limit` argument with `.take(limit)` to prevent performance issues as deployments accumulate. See `convex/webhooks.ts` `getRecentWebhookEvents` for the recommended pattern.</violation>
</file>
<file name="src/app/api/github/repositories/route.ts">
<violation number="1" location="src/app/api/github/repositories/route.ts:15">
P1: This calls an `internalQuery` which is not accessible from the public API. `getGithubAccessToken` is an internal Convex query that requires a `userId` argument. Use `getGithubAccessTokenForCurrentUser` action instead, which handles authentication internally.</violation>
</file>
<file name="src/app/api/deploy/netlify/preview/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/preview/route.ts:32">
P1: Missing authorization check before deleting deployment. The `deployId` is taken directly from user input without verifying it belongs to a project the user has access to. Query the `deployments` table to validate ownership before calling the Netlify API.</violation>
</file>
<file name="src/app/api/deploy/netlify/logs/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/logs/route.ts:43">
P2: Authorization errors returned as 500 status. When `getNetlifyAccessToken()` throws "Netlify connection not found" (user hasn't connected Netlify), it's caught here and returned as 500 instead of 401/403. This prevents the frontend from properly prompting users to connect their Netlify account.</violation>
</file>
<file name="src/app/api/deploy/netlify/rollback/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/rollback/route.ts:46">
P2: The catch block returns HTTP 500 for all errors, but "Netlify connection not found" is an authorization error and should return 401 or 403. Consider checking for specific error messages and returning appropriate status codes.</violation>
</file>
<file name="src/lib/payment-provider.ts">
<violation number="1" location="src/lib/payment-provider.ts:227">
P2: Unsafe type assertion `null as T` violates project conventions and could cause runtime errors. When the API returns 204, this returns `null` but callers expect non-nullable types like `CheckoutSession` or `SubscriptionSummary`.
Consider either:
1. Making the return type explicitly nullable for methods that could receive 204
2. Throwing an error for unexpected 204 responses
3. Using a type guard or conditional type</violation>
</file>
<file name="src/lib/database-templates/drizzle-neon/nextjs.ts">
<violation number="1" location="src/lib/database-templates/drizzle-neon/nextjs.ts:70">
P2: The `pathname` should be URL-encoded when used as a query parameter to prevent malformed URLs or potential security issues with special characters.</violation>
</file>
<file name="src/lib/payment-templates/angular.ts">
<violation number="1" location="src/lib/payment-templates/angular.ts:304">
P2: Missing `response.ok` check before parsing JSON. If the server returns an error (4xx/5xx), this will silently return `false` instead of handling the error, potentially denying feature access during server issues.</violation>
<violation number="2" location="src/lib/payment-templates/angular.ts:309">
P2: Usage tracking fails silently without any error handling. If the API request fails, the caller has no way to know that usage wasn't recorded.</violation>
</file>
<file name="src/lib/netlify-client.ts">
<violation number="1" location="src/lib/netlify-client.ts:46">
P2: Returning `{} as T` for empty responses is type-unsafe and can cause runtime errors. Callers expecting typed objects (e.g., `NetlifySite` with required `id`, `name`) will get an empty object, leading to `undefined` property access. Consider returning `null` with `Promise<T | null>` signature, or throwing an error for unexpected empty responses.</violation>
</file>
<file name="src/app/compare/page.tsx">
<violation number="1" location="src/app/compare/page.tsx:131">
P2: Avoid nesting a <button> inside <Link>; it produces invalid HTML and accessibility issues. Style the Link as a button instead.</violation>
</file>
<file name="src/app/api/deploy/netlify/domains/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/domains/route.ts:64">
P2: Missing null check on parsed JSON body. If the request body is `null` (valid JSON), accessing `body.siteId` throws a TypeError, returning a 500 error instead of a 400 validation error. Add a null check before accessing properties.</violation>
</file>
<file name="src/lib/database-templates/convex/nextjs.ts">
<violation number="1" location="src/lib/database-templates/convex/nextjs.ts:259">
P2: Dashboard template shows blank page briefly before redirect. When `!session`, it returns `null` while the `useEffect` redirect is in progress. Keep showing the loading indicator until navigation completes to avoid the blank flash.</violation>
</file>
<file name="src/inngest/functions/code-agent.ts">
<violation number="1" location="src/inngest/functions/code-agent.ts:9">
P1: Setting `retries: 3` with `publish()` inside `step.run()` will cause duplicate real-time events on retry. Inngest's realtime documentation recommends `retries: 0` for functions using publish to prevent subscribers from receiving duplicate streaming events when steps retry.</violation>
</file>
<file name="src/app/api/deploy/netlify/status/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/status/route.ts:42">
P2: All errors return 500, but 'Netlify connection not found' from `getNetlifyAccessToken()` is an authorization issue, not a server error. Consider checking for this specific error and returning 401 to help clients differentiate between missing OAuth connections and actual server failures.</violation>
</file>
<file name="src/app/robots.ts">
<violation number="1" location="src/app/robots.ts:10">
P2: The AI crawler rules are less restrictive than the default robots rule, so those bots can crawl sensitive paths like `/admin/` and `/_next/` that are otherwise disallowed. Align the disallow list with the default rule to avoid unintentionally exposing these paths to those crawlers.</violation>
</file>
<file name="src/app/api/deploy/netlify/deploy/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/deploy/route.ts:165">
P2: All errors return HTTP 500, but client errors like 'No AI-generated files' (400) and 'Netlify connection not found' (401/403) should use appropriate 4xx status codes. Consider using custom error classes or checking error messages to return correct status codes.</violation>
</file>
<file name="src/agents/claude-code-tools.ts">
<violation number="1" location="src/agents/claude-code-tools.ts:202">
P1: Missing path validation in `read_files` allows path traversal. Unlike `write_files` which validates paths with `isValidFilePath`, this tool passes paths directly to `readFileFast` which doesn't validate. Add validation before reading each file to prevent reading files outside the sandbox.</violation>
</file>
<file name="src/app/api/deploy/netlify/env-vars/route.ts">
<violation number="1" location="src/app/api/deploy/netlify/env-vars/route.ts:67">
P2: Request body validation doesn't verify that `value` is a string type. The check `body.value == null` only ensures presence, not type correctness. A request with a non-string `value` (e.g., number or object) would pass validation but could cause unexpected behavior when passed to the Netlify API.</violation>
</file>
<file name="README.md">
<violation number="1" location="README.md:55">
P2: The README points to `src/agents/code-agent.ts` for updating the E2B template name, but the actual template mapping is in `src/agents/sandbox-utils.ts` (`getE2BTemplate`). This will mislead contributors and prevent them from updating the template used by sandbox creation.</violation>
</file>
<file name="convex/oauth.ts">
<violation number="1" location="convex/oauth.ts:6">
P0: Missing `"use node"` directive required for Node.js `crypto` module. Convex runs functions in a V8 runtime by default, and using Node.js built-in modules like `crypto` requires adding `"use node"` at the top of the file. Without this, the code will fail at runtime.</violation>
<violation number="2" location="convex/oauth.ts:9">
P1: AES-256-GCM requires a 32-byte key, but the validation only checks for non-empty. If `OAUTH_ENCRYPTION_KEY` is shorter than 32 characters, `slice(0, 32)` will produce an invalid key length causing runtime encryption failures.</violation>
</file>
<file name="src/lib/database-templates/convex/shared.ts">
<violation number="1" location="src/lib/database-templates/convex/shared.ts:118">
P2: Avoid falling back to a placeholder Convex URL. This masks misconfiguration and makes the client connect to an invalid endpoint instead of failing fast with a clear error. Prefer an explicit env check like the server handler uses.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 19
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/app/showcase/page.tsx (1)
128-131: CTA buttons lack navigation or click handlers.The following buttons don't have any functionality:
- "Submit Your Project" (line 128-131)
- "Start Building" (lines 206-208, 249-251)
- "Join Community" (lines 253-256)
Consider adding:
hrefprops to convert to Link components for navigationonClickhandlers to trigger modals or actions- Or wrap with
<Link>if these should navigate to specific routesExample implementation
- <Button size="lg" className="gap-2"> + <Link href="/submit"> + <Button size="lg" className="gap-2"> <Star className="h-5 w-5" /> Submit Your Project + </Button> + </Link> - </Button>Also applies to: 206-208, 249-256
ROADMAP.md (1)
1-179: MoveROADMAP.mdunder/explanations.
Docs placement rules require markdown documentation to live in/explanations(except README/CLAUDE). Consider relocating this file or adding an explicit exception. As per coding guidelines, ...convex/oauth.ts (1)
88-102: getConnection returns encrypted tokens to callers.The
getConnectionquery returns the raw database record containing encrypted tokens. Callers using this query (like the domains route) may expect decrypted tokens.This could cause silent failures where encrypted tokens are used as-is against APIs. Consider either:
- Decrypting in
getConnection(but this exposes tokens in query results)- Documenting clearly that tokens are encrypted
- Creating separate queries for token retrieval vs. connection metadata
#!/bin/bash # Description: Find all usages of api.oauth.getConnection to verify they handle encrypted tokens rg -n "getConnection" --type ts -B 2 -A 5 | rg -A 7 "oauth"
🤖 Fix all issues with AI agents
In `@convex/oauth.ts`:
- Around line 14-21: encryptToken currently derives the AES key by slicing a
UTF-8 string which can yield wrong byte lengths and low entropy; instead,
require ENCRYPTION_KEY to be a 64-character hex string (32 bytes) or derive a
32-byte key via a proper KDF and validate its length. Replace the current key
construction in encryptToken (where Buffer.from(ENCRYPTION_KEY.slice(0, 32),
"utf8") is used) with a validated Buffer.from(ENCRYPTION_KEY, "hex") and throw
an explicit error if keyBuffer.length !== 32; ensure the same validated key
handling is used wherever ALGORITHM/ENCRYPTION_KEY are consumed so encryption
and decryption remain compatible.
- Around line 8-11: Module-level throw for ENCRYPTION_KEY prevents Convex from
initializing; change to defer validation to runtime by replacing the top-level
ENCRYPTION_KEY constant with a function (e.g., getEncryptionKey()) that reads
process.env.OAUTH_ENCRYPTION_KEY, trims/validates it, and throws only when the
value is actually required, or return undefined and let callers handle the
missing key; update all places using ENCRYPTION_KEY to call getEncryptionKey()
(or handle undefined) so the module no longer throws during load.
In `@env.example`:
- Around line 30-38: convex/oauth.ts requires an OAUTH_ENCRYPTION_KEY env var
and currently throws if it is missing, so add a new entry
OAUTH_ENCRYPTION_KEY="" to env.example with a short comment instructing
developers to generate a strong 32+ character key (e.g., use a secure random
generator or openssl/uuid) and note its purpose for encrypting OAuth tokens;
ensure the variable name matches exactly what convex/oauth.ts reads and include
guidance to keep it secret and consistent across deployments.
In `@geo-implementation-prompt.md`:
- Line 1: Move the markdown file geo-implementation-prompt.md into the
explanations/ directory to comply with project documentation rules; rename/move
the file path to explanations/geo-implementation-prompt.md and update any
references or links in the repo (imports, README, docs index) that point to the
old location so they reference explanations/geo-implementation-prompt.md
instead.
In `@public/robots.txt`:
- Line 20: The directive "Disallow: *.json" is invalid for robots.txt; replace
it by either enumerating the specific JSON-containing paths to disallow (e.g.,
list each /path/to/file.json or directory prefixes) or, if you only care about
Googlebot-style pattern matching, use the supported Google-specific pattern
"Disallow: /*.json$"; remove the invalid "Disallow: *.json" line if you cannot
reliably list paths or rely on non-standard patterns.
- Around line 22-78: Per-agent User-agent blocks (e.g., "User-agent: GPTBot",
"User-agent: ClaudeBot", etc.) only disallow "/api/" and "/projects/" and
therefore override the global User-agent: * rules, unintentionally permitting
access to sensitive paths; fix by either removing these per-agent blocks so the
global rules apply, or update each per-agent block to include all global
disallowed paths (add "/_next/", "/admin/", "/monitoring", "/.well-known/"
alongside the existing "/api/" and "/projects/") and apply this change to every
User-agent block in the file.
In `@src/agents/claude-code-tools.ts`:
- Around line 115-213: The tools write_files and read_files accept unvalidated
paths and can escape the sandbox; update both to call validateAndSanitizePath
for each input path (instead of relying only on isValidFilePath) before any IO:
in write_files (the execute handler for write_files) validate and sanitize each
file.path with validateAndSanitizePath(sandbox, path) and skip/collect invalid
ones before building filesToWrite and calling writeFilesBatch; in read_files
(the execute handler for read_files) validate each path with
validateAndSanitizePath(sandbox, path) and only call readFileFast on sanitized
paths, returning errors for invalid inputs. Keep existing usage of getSandbox,
readFileFast, writeFilesBatch, updateFiles, and onFileCreated; ensure error
responses include invalidPaths and preserve current JSON response shapes.
In `@src/agents/client.ts`:
- Around line 24-43: The current OAuth flow is incorrect:
createClaudeCodeClientWithToken and createAnthropicProviderWithToken set apiKey
(which maps to x-api-key) instead of sending an Authorization: Bearer header
through your centralized OpenRouter gateway; also some Claude model IDs in
CLAUDE_CODE_MODEL_MAP are retired. Fix by routing Claude Code requests through
your OpenRouter client (use the existing OpenRouter client factory instead of
instantiating Anthropic directly), ensure the gateway request uses headers: {
Authorization: `Bearer ${accessToken}` } (not apiKey), remove passing
accessToken into apiKey, and replace retired model IDs (e.g.,
claude-3-5-sonnet-20241022 and claude-3-opus-20240229) with their *-latest
aliases (e.g., claude-3-5-sonnet-latest) or current snapshot IDs; update
createClaudeCodeClientWithToken/createAnthropicProviderWithToken to return or
configure the OpenRouter-based client and keep isClaudeCodeFeatureEnabled
unchanged.
In `@src/agents/code-agent.ts`:
- Around line 446-463: The code is calling
internal.oauth.getAnthropicAccessToken via convex.query from the agent (using a
browser client) which can't access internal Convex functions; replace that call
with the public action getAnthropicAccessTokenForCurrentUser (from
convex/oauth.ts) or otherwise obtain the token from the Inngest function context
and pass it into the agent. Specifically, in the isClaudeCodeModel branch where
you currently call convex.query(internal.oauth.getAnthropicAccessToken, {
userId: project.userId }), switch to invoking
getAnthropicAccessTokenForCurrentUser (or use the passed-in token) to retrieve
userAnthropicToken so the agent uses an authenticated, public action instead of
internal.oauth.getAnthropicAccessToken.
In `@src/app/api/deploy/netlify/auth/route.ts`:
- Around line 5-7: Remove the hard-coded fallback for NETLIFY_OAUTH_STATE_SECRET
and make the code fail fast if the environment variable is not set: replace the
current declaration that defaults to "fallback-secret-change-me" with logic that
throws or returns an error when NETLIFY_OAUTH_STATE_SECRET is undefined
(consistent with the existing callback handler behavior). Update any
initialization or export that uses NETLIFY_OAUTH_STATE_SECRET (referencing the
NETLIFY_OAUTH_STATE_SECRET symbol) so callers cannot proceed without a real
secret, and ensure the process logs an explanatory error and exits or returns a
500 response when the variable is missing.
In `@src/app/compare/`[slug]/page.tsx:
- Around line 253-262: The CTA Button is inert and the ArrowRight icon uses the
wrong sizing props; wrap the Button with a next/link Link (import Link from
'next/link') and pass asChild to the Button component so it becomes a clickable
link to the intended route (set Link's href to the desired destination), and
replace the ArrowRight class-based sizing (className="h-5 w-5") with the
lucide-react size prop (e.g., size={16}) to match the size-4 guideline; update
JSX for the Button component usage (Button, ArrowRight, Link references)
accordingly.
In `@src/app/compare/page.tsx`:
- Around line 6-7: The JSX currently nests a native <button> inside a <Link> (an
<a>), which is invalid — replace the nested <button> with the Shadcn Button
component used as a child of Link (i.e., import Button from your shadcn/ui and
use <Button asChild><Link ...>...</Link></Button> or vice‑versa depending on
your Link wrapper) so the rendered element is an anchor without a nested button;
update both occurrences (the block around
Card/CardContent/CardHeader/CardTitle/CardDescription with ArrowRight and the
similar block at lines referenced 130-133) to use the Button asChild pattern and
ensure accessibility and styling remain intact while keeping ArrowRight and Card
components unchanged.
In `@src/lib/netlify-config.ts`:
- Around line 24-27: The ANGULAR block currently sets publishDir to "dist",
which breaks Angular 17+ outputs; update the ANGULAR.publishDir to point to the
new default output "dist/<projectName>/browser" (or make publishDir configurable
from project settings) and ensure buildCommand remains "bun run build";
alternatively add documentation in the ANGULAR block comment explaining that
users must set their angular.json outputPath if they wish a different
publishDir.
In `@src/lib/payment-provider.ts`:
- Around line 204-231: The request<T> helper has two problems: it casts null as
T for 204 responses and lacks a request timeout; replace this with an
AbortController-based timeout (use a configurable default like 10s, attach
controller.signal to fetch, clear the timer, and throw a clear timeout error on
abort) and stop returning `null as T` — either change request<T> to return
Promise<T | undefined> for endpoints that may be 204 or add a dedicated method
(e.g., requestNoContent or requestVoid) for 204 responses; update callers like
createCheckoutSession and updateSubscription to handle undefined or use the new
no-content method accordingly.
In `@src/lib/payment-templates/angular.ts`:
- Around line 7-265: The template literal for "server/routes/billing.ts"
contains unescaped `${encodeURIComponent(...)}'` occurrences which are being
interpreted by the outer template in payment-templates/angular.ts; fix by
escaping the inner template interpolations and any backticks inside that inner
file string: replace `${` with `\${` for the two uses of encodeURIComponent
(used in the router.patch and router.delete request paths) and escape any
backticks within the billing file content so the outer template string remains
valid (look for encodeURIComponent(...) and autumn.request(...) usages in the
billing template).
In `@src/lib/payment-templates/nextjs.ts`:
- Around line 368-372: The code currently calls response.json() twice (once
inside the !response.ok branch and again afterwards), consuming the body stream
and causing an error; fix by awaiting response.json() once into a local variable
(e.g., const data = await response.json()), then check response.ok and throw
using data.error || "Checkout failed" when not ok, otherwise reuse that same
data object for the success path (replace the duplicate response.json() calls in
the function where response and data are used).
In `@src/lib/payment-templates/react.ts`:
- Around line 117-146: The template backticks inside string literals are
breaking TS parsing; locate the autumn.request calls (e.g., the PATCH call using
`/v1/subscriptions/${encodeURIComponent(subscriptionId)}` and the POST cancel
call `/v1/subscriptions/${encodeURIComponent(subscriptionId)}/cancel`) and
replace those inner template literals with safe string construction (either
escape the backticks or switch to single-quoted concatenation like
'/v1/subscriptions/' + encodeURIComponent(subscriptionId) + '/cancel' or
'/v1/subscriptions/' + encodeURIComponent(subscriptionId)); update all
occurrences in this file so no unescaped backticks remain inside outer
backtick-delimited strings.
In `@src/lib/payment-templates/vue.ts`:
- Around line 107-143: The PATCH and DELETE /subscription route handlers
(router.patch("/subscription", ...) and router.delete("/subscription", ...))
lack try/catch around the autumn.request calls so thrown errors are unhandled;
wrap each handler's async body in a try/catch, call autumn.request inside the
try, and in the catch send a consistent JSON error response (e.g.,
res.status(500).json({ error: String(err) }) or include err.message) and
optionally log the error before returning so failures from autumn.request are
handled safely.
🟡 Minor comments (25)
public/llms.txt-1-4 (1)
1-4: Align brand capitalization with existing UI copy (ZapDev).The file uses “Zapdev” while the design system copy calls it “ZapDev.” Consider standardizing capitalization for consistency across public-facing content.
src/lib/database-templates/convex/shared.ts-41-41 (1)
41-41: Inconsistent environment variable naming between templates.
convexAuth(line 41) usesSITE_URLwhileconvexAuthClient(line 79) usesNEXT_PUBLIC_SITE_URL. Both serve the same purpose but the naming inconsistency could cause configuration issues. Consider aligning onNEXT_PUBLIC_SITE_URLsince it needs to be available on the client.Also applies to: 79-79
src/lib/netlify-client.ts-43-49 (1)
43-49: Unsafe type assertion when response body is empty.Returning
{} as Twhen the response is empty can cause runtime errors if the caller expects specific properties onT. Consider returningnulland updating the return type, or throw an error for unexpected empty responses.Proposed fix
-const parseJson = async <T>(response: Response): Promise<T> => { +const parseJson = async <T>(response: Response): Promise<T | null> => { const text = await response.text(); if (!text) { - return {} as T; + return null; } return JSON.parse(text) as T; };Then update callers to handle the
nullcase appropriately, or use a different approach for methods that expect no response body (like DELETE operations).src/app/api/projects/[projectId]/export/github/route.ts-115-125 (1)
115-125:projectIdis fetched but not used in GET handler.The
projectIdis extracted from params but never used for authorization or filtering. Consider either:
- Validating that the export belongs to the specified project for security
- Removing the unused variable if the
exportIdalone is sufficientProposed fix: Add project validation
const { projectId } = await params; const { searchParams } = new URL(request.url); const exportId = searchParams.get("exportId"); if (!exportId) { return NextResponse.json({ error: "Missing exportId" }, { status: 400 }); } const record = await fetchQuery(api.githubExports.get, { exportId: exportId as Id<"githubExports">, }); - if (!record) { + if (!record || record.projectId !== projectId) { return NextResponse.json({ error: "Export not found" }, { status: 404 }); }src/lib/database-templates/convex/shared.ts-111-120 (1)
111-120: Placeholder URL fallback may cause silent failures.The
ConvexReactClientuses"https://placeholder.convex.cloud"as a fallback, which will silently fail at runtime if the environment variable is missing. This is inconsistent withconvexAuthHandler(lines 91-96) which throws an explicit error.Proposed fix for consistency
export const convexProvider = \`"use client"; import { ConvexReactClient } from "convex/react"; import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; import { authClient } from "@/lib/auth-client"; import type { ReactNode } from "react"; +const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL; +if (!convexUrl) { + throw new Error("NEXT_PUBLIC_CONVEX_URL environment variable is required."); +} + const convex = new ConvexReactClient( - process.env.NEXT_PUBLIC_CONVEX_URL || "https://placeholder.convex.cloud" + convexUrl );src/app/rss.xml/route.ts-32-42 (1)
32-42: Avoid a hard-coded blog pubDate that will go stale.A fixed 2025 date will look outdated in 2026 and can suppress updates in feed readers. Consider sourcing the publish/updated date from blog metadata (or at least a constant aligned with the actual post) and, if possible, linking to a specific post URL instead of the blog index.
src/modules/projects/ui/views/project-view.tsx-46-46 (1)
46-46: Remove theastype assertion on line 123 and use a type guard instead.The
asassertion inonValueChange={(value) => setTabState(value as "preview" | "code" | "deploy")}bypasses type safety. Line 46's type annotation is already correct; extract the union type and validate with a type guard:Suggested fix
+ type TabState = "preview" | "code" | "deploy"; + const isTabState = (value: string): value is TabState => + value === "preview" || value === "code" || value === "deploy"; + - onValueChange={(value) => setTabState(value as "preview" | "code" | "deploy")} + onValueChange={(value) => { + if (isTabState(value)) setTabState(value); + }}src/modules/projects/ui/components/deployment-dashboard.tsx-1-15 (1)
1-15: Missing "use client" directive for client component.This component uses
useQueryfrom Convex React, which requires client-side rendering. In Next.js App Router, components using React hooks must be marked as client components.🔧 Proposed fix
+"use client"; + import { useQuery } from "convex/react"; import { api } from "@/convex/_generated/api";src/modules/projects/ui/components/deploy-button.tsx-40-42 (1)
40-42: Handle loading state separately from missing connection.The
useQueryhook returnsundefinedwhile loading. The current checkif (!connection)treats both loading and "no connection" states the same, causing theNetlifyConnectDialogto flash briefly before the actual button appears once the query resolves.🐛 Proposed fix
+ // Show nothing while loading to prevent flash + if (connection === undefined) { + return null; + } + if (!connection) { return <NetlifyConnectDialog />; }Alternatively, show a loading skeleton:
+ if (connection === undefined) { + return <Button size="sm" disabled className="animate-pulse">Loading...</Button>; + } + if (!connection) { return <NetlifyConnectDialog />; }src/app/api/deploy/netlify/sites/route.ts-35-38 (1)
35-38: Differentiate error status codes based on error type.When
getNetlifyAccessTokenthrows "Netlify connection not found", the response should be 401 (unauthorized) rather than 500. Currently all errors return 500, which misrepresents authentication failures as server errors.Proposed fix
} catch (error) { const message = error instanceof Error ? error.message : "Failed to fetch sites"; + const status = message.includes("connection not found") ? 401 : 500; - return NextResponse.json({ error: message }, { status: 500 }); + return NextResponse.json({ error: message }, { status }); }src/modules/projects/ui/components/deployment-history.tsx-33-34 (1)
33-34: URL-encode the deployId parameter.The
deployIdis interpolated directly into the URL without encoding. If the ID contains special characters, this could cause issues.Proposed fix
- const response = await fetch(`/api/deploy/netlify/logs?deployId=${deployId}`); + const response = await fetch(`/api/deploy/netlify/logs?deployId=${encodeURIComponent(deployId)}`);src/lib/payment-templates/nextjs.ts-41-43 (1)
41-43: Unsafe type assertion for 204 responses.Returning
undefined as Tis unsafe when the generic typeTdoesn't includeundefined. This could cause runtime issues if callers expect a defined response.Suggested fix with explicit undefined handling
if (response.status === 204) { - return undefined as T; + return undefined as unknown as T; }Alternatively, consider changing the return type to
Promise<T | undefined>and updating callers to handle the undefined case explicitly.src/inngest/functions/code-agent.ts-5-12 (1)
5-12: Reduce Inngest retries to 0; agent has internal retry loops for transient failures.The agent's internal retry mechanisms (
MAX_STREAM_RETRIES=3for LLM errors,AUTO_FIX_MAX_ATTEMPTS=1for build/lint failures) already handle recoverable failures. Error events are yielded only when permanent failures occur (no files generated, no summary available after all retries), and Inngest'sretries: 3will not resolve these failures. Additionally, each Inngest retry creates a new sandbox and duplicate database records without providing any recovery benefit.Set
retries: 0and rely on the agent's internal retry logic, since transient errors are already handled and permanent errors cannot be fixed by re-executing the entire function.src/modules/projects/ui/components/custom-domain-dialog.tsx-36-88 (1)
36-88: Encode query params and trim the domain input.
siteId/domainIdare interpolated into URLs without encoding, and whitespace-only domains can slip through. Encoding + trimming avoids malformed requests.✅ Suggested fix
- const response = await fetch(`/api/deploy/netlify/domains?siteId=${siteId}`); + const response = await fetch( + `/api/deploy/netlify/domains?siteId=${encodeURIComponent(siteId)}` + );- if (!domainInput || isSubmitting) { - if (!domainInput) { + const domain = domainInput.trim(); + if (!domain || isSubmitting) { + if (!domain) { toast.error("Enter a domain"); } return; }- const response = await fetch("/api/deploy/netlify/domains", { + const response = await fetch("/api/deploy/netlify/domains", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ siteId, domain: domainInput }), + body: JSON.stringify({ siteId, domain }), });- const response = await fetch( - `/api/deploy/netlify/domains?siteId=${siteId}&domainId=${domainId}`, + const response = await fetch( + `/api/deploy/netlify/domains?siteId=${encodeURIComponent( + siteId + )}&domainId=${encodeURIComponent(domainId)}`, { method: "DELETE" } );src/app/api/deploy/netlify/deploy/route.ts-107-112 (1)
107-112: ChangeparseResult.error.errorstoparseResult.error.issuesfor Zod v4 compatibility.Zod v4 uses the
.issuesproperty onZodError; the.errorsproperty was removed in v4. The current code will result inundefineddetails in the error response.🔧 Suggested change
- { error: "Invalid request body", details: parseResult.error.errors }, + { error: "Invalid request body", details: parseResult.error.issues },convex/oauth.ts-23-32 (1)
23-32: Decryption lacks input validation.If
encryptedTokendoesn't contain exactly two colons, the destructuring will fail silently or produce incorrect values. Add validation.🔧 Suggested fix
function decryptToken(encryptedToken: string): string { const [ivHex, authTagHex, encrypted] = encryptedToken.split(":"); + if (!ivHex || !authTagHex || !encrypted) { + throw new Error("Invalid encrypted token format"); + } const iv = Buffer.from(ivHex, "hex"); const authTag = Buffer.from(authTagHex, "hex");src/lib/payment-templates/svelte.ts-253-274 (1)
253-274: Indentation inconsistency and duplicate JSON parsing in CheckoutButton.svelte.The
startCheckoutfunction has inconsistent indentation (extra spaces at Line 253). Also, when the response is not OK, the code parses JSON to get the error, but then parses it again on Line 265 even though the response was already consumed on Line 262.🔧 Proposed fix
- const startCheckout = async () => { + const startCheckout = async () => { loading = true; try { const response = await fetch("/api/billing/checkout", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ productId, customerId, successUrl, cancelUrl }), }); + const data = (await response.json()) as { url?: string; error?: string }; if (!response.ok) { - const data = await response.json(); throw new Error(data.error || "Checkout failed"); } - const data = (await response.json()) as { url?: string }; if (data.url) { window.location.href = data.url; }src/lib/database-templates/drizzle-neon/nextjs.ts-77-81 (1)
77-81: Fix excessive escaping in middleware matcher regex.The matcher pattern uses
\\\\.(quadruple backslash) but should use\\.(double backslash) to correctly match file extensions. The double-backslash escaping is necessary in JavaScript strings to represent a regex literal dot\..Change line 79 from:
"/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",to:
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",This matches the correct escaping used in
src/proxy.tsand aligns with Next.js documentation patterns..cursor/plans/add_inngest_as_agent_orchestration_layer_c89435a8.plan.md-4-22 (1)
4-22: Fix remark-lint front-matter list indentation and add trailing newline.
CI flags the indentedtodoslist and missing final newline. Update the YAML list indentation (no extra two spaces) and ensure the file ends with a newline.🔧 Example fix for the front-matter list
todos: - - id: install-inngest + - id: install-inngest content: "Install Inngest packages: inngest and `@inngest/realtime`" status: completedAlso applies to: 203-208
.cursor/plans/add_inngest_as_agent_orchestration_layer_c89435a8.plan.md-1-3 (1)
1-3: Move plan markdown under/explanations(or document an exemption).
This file is a documentation-style Markdown file outside/explanations. If.cursor/plansis intentionally exempt, please note that explicitly; otherwise relocate it. As per coding guidelines, please keep Markdown docs under/explanations.README.md-20-28 (1)
20-28: Update Inngest statement—it's still in dependencies, and migration is incomplete. Lines 20–27: Next.js 16 and React 19 are correct, but line 27 claims "Custom agent orchestration (replaces Inngest)," whilepackage.jsonincludes"inngest": "^3.49.3". According to the codebase learnings, the migration from Inngest to custom agents is in progress, not complete. Update line 27 to reflect the actual state: either reference Inngest explicitly or clarify that custom orchestration is being implemented alongside it.geo-implementation-prompt.md-17-19 (1)
17-19: Tighten wording per lint feedback.Use hyphenated compound adjectives and proper noun capitalization.
🔧 Suggested change
-### 1. Content Enhancement (High Impact Methods) +### 1. Content Enhancement (High-impact Methods) -**Deliverable:** Spreadsheet or markdown document listing: +**Deliverable:** Spreadsheet or Markdown document listing: -## What to Avoid (Low Impact Methods) +## What to Avoid (Low-impact Methods)Also applies to: 148-148, 304-306
geo-implementation-prompt.md-569-570 (1)
569-570: Avoid bare URLs in Markdown.MD034 flags bare URLs; wrap them in angle brackets or use a proper link.
🔧 Suggested change
-- Schema.org documentation: https://schema.org/ +- Schema.org documentation: <https://schema.org/>geo-implementation-prompt.md-427-446 (1)
427-446: Add a language to the fenced block.markdownlint MD040 expects a language tag on fenced code blocks.
🔧 Suggested change
-``` +```textgeo-implementation-prompt.md-32-58 (1)
32-58: Escape placeholder brackets to avoid undefined reference links.The
[topic]-style placeholders are parsed as reference links; remark-lint flags them as undefined. Use backticks or escape brackets to keep them as placeholders.🔧 Example fix (apply similarly throughout)
-- "What is [topic]?" -- "How does [system] work?" -- "Best [tool] for [use case]" -+ "What is `topic`?" -+ "How does `system` work?" -+ "Best `tool` for `use case`"
Overview
This PR adds comprehensive Netlify deployment capabilities and payment integration templates to the ZapDev platform.
Note: This PR contains all changes from PR #215 (feat/roadmap-completeation) which remains open as requested.
Key Features
1. Netlify Deployment Integration
2. GitHub Export Functionality
3. Payment Integration Templates
4. Additional Features
Files Changed
111 files changed (+12,293 additions, -1,257 deletions)
New API Endpoints
/api/deploy/netlify/*- Complete Netlify deployment API/api/github/repositories- GitHub repository management/api/projects/[projectId]/export/github- GitHub export/api/auth/anthropic/*- Anthropic OAuthNew Libraries & Components
src/lib/netlify-client.ts- Netlify API clientsrc/lib/github-api.ts- GitHub API clientsrc/lib/payment-provider.ts- Payment provider abstractionsrc/lib/payment-templates/*- Framework-specific payment templatessrc/lib/database-templates/*- Database integration templatessrc/modules/projects/ui/components/*- Deployment UI componentsConvex Schema Updates
deploymentstable for tracking Netlify deploymentsgithubExportstable for GitHub export historyoauthtable for multiple providersEnvironment Variables Required
Testing Checklist
Related
Summary by cubic
Adds one-click Netlify deployments and framework-specific payment templates, plus GitHub export and Inngest-powered real-time agent streaming. Also adds Anthropic OAuth, encrypts OAuth tokens, and updates the project UI for deploys, exports, and env management.
New Features
Migration
Written for commit ae78f07. Summary will update on new commits.
Summary by CodeRabbit
Release Notes
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.