React Router v7 (Netlify)
- The CLI sets
[build] publish = "build/client"
and a safecommand
based on detection. - Add a SPA fallback redirect to handle client-side routing:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
- SSR for some frameworks may require a framework adapter or a serverless build; static is supported out of the box.
React Router v7 (Vercel)
- Static preview/prod deploys are supported by setting
outputDirectory
tobuild/client
(the provider plugin writes this invercel.json
). - The CLI detects these projects and writes
vercel.json
idempotently. - SSR requires a framework/serverless adapter and is not provided by default by OpenDeploy.
Config generation (idempotent)
OpenDeploy writes a minimal netlify.toml
using the Detection Engine v2:
- Keeps your existing file unless
--overwrite
is passed. - Next.js: uses the official Next Runtime when installed; otherwise falls back to
@netlify/plugin-nextjs
. - Other frameworks (Astro, SvelteKit, Remix, Nuxt, Expo): uses detected
buildCommand
andpublishDir
to set[build]
. - Prisma commands are sanitized from the build command to avoid DB operations during Netlify builds.
The start
wizard ensures netlify.toml
and prints recommended netlify deploy
commands using the detected publish directory.
Vercel Config Generation
OpenDeploy writes a minimal vercel.json
using the Detection Engine v2:
- Keeps your existing config unless
--overwrite
is passed. - Ensures safe defaults for Next.js and other supported frameworks.
- The
start
wizard andgenerate vercel
use the same provider plugin logic.
When editing manually, see Vercel docs for advanced options (headers, redirects, images, i18n).
Nuxt (Netlify)
For Nuxt, OpenDeploy writes a minimal netlify.toml
that builds with npx nuxi build
and publishes .output/public
.
# Auto-generated by OpenDeploy CLI (Nuxt)
[build]
command = "npx nuxi build"
publish = ".output/public"
This matches Nitro's default output directories. If you are using SSR with Netlify Functions, consult Nitro presets; the CLI focuses on the static output path above.
Turborepo config generation
OpenDeploy can generate a minimal turbo.json
to cache build artifacts for Next.js and libraries by default.
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
}
}
}
Notes:
- Use
TURBO_TOKEN
andTURBO_TEAM
in CI to enable remote caching. - You can extend per-app outputs (e.g., Vite
dist/**
).
SSR Adapters: SvelteKit and Remix
Warning: For server-side rendering (SSR), you must install the official framework adapters. OpenDeploy does not automatically modify your framework config to enable SSR.
-
SvelteKit
- Vercel:
@sveltejs/adapter-vercel
- Docs: https://github.com/sveltejs/kit/blob/main/documentation/docs/25-build-and-deploy/90-adapter-vercel.md
- Netlify:
@sveltejs/adapter-netlify
- Docs: https://github.com/sveltejs/kit/blob/main/documentation/docs/25-build-and-deploy/80-adapter-netlify.md
- Vercel:
-
Remix
- Vercel:
@remix-run/vercel
- Docs: https://github.com/remix-run/remix/tree/main/packages/remix-vercel
- Netlify:
@remix-run/netlify
- Docs: https://github.com/remix-run/remix/tree/main/packages/remix-netlify
- Vercel:
If you are deploying static-only versions, the generated vercel.json
and netlify.toml
are sufficient; SSR may require framework-specific adapters (e.g., SvelteKit adapters) within your application, but OpenDeploy itself now uses provider plugins.
Key types:
DetectionResult
: result of stack detection (rootDir, buildCommand, etc.)DeployInputs
: provider, detection, env, project/org IDs, dryRun, envVarsDeployResult
: URL, projectId, provider, target, optional logsUrl, durationMs
Responsibilities
-
validateAuth()
- Ensure the provider CLI is installed and the user is authenticated.
- Throw with a helpful message if not authenticated.
-
generateConfig()
- Create a minimal provider config file when missing (idempotent).
- For Netlify, we write a safe
netlify.toml
. For Vercel, a minimalvercel.json
.
-
deploy()
- Perform the provider-specific deployment.
- Respect
inputs.env
(prod
|preview
) and IDs for non-interactive linking. - Return a structured
DeployResult
.
-
open()
- Open the provider dashboard for the linked/current project.
- Providers may optionally attempt non-interactive linking if a
projectId
is passed.
-
logs()
- Print or stream provider logs. The CLI handles UX/NDJSON; adapters should execute the provider CLI/API.
- Arguments include
env
,projectId
,orgId
,cwd
,follow
,since
.
CLI vs Provider Plugin Responsibilities
-
CLI (
src/commands/deploy.ts
)- UX, flags, NDJSON/human output, spinners, summaries.
- Monorepo cwd selection and link hints.
-
Provider Plugin (
src/core/provider-system/providers/*
)- Low-level provider operations (spawn provider CLI, read/write config files).
- Keep logic small, testable, and side-effect aware.
Minimal Provider Plugin Skeleton
import type { Provider } from '../../src/core/provider-system/provider-interface'
export class ExampleProvider implements Provider {
public readonly id = 'example'
getCapabilities() { return { /* ... */ } as any }
async detect(cwd: string) { void cwd; return {} }
async validateAuth(cwd: string) { void cwd }
async link(cwd: string, project: { projectId?: string; orgId?: string }) { void cwd; return project }
async build(args: any) { void args; return { ok: true } }
async deploy(args: any) { void args; return { ok: true, url: 'https://ex.example.com', logsUrl: undefined, durationMs: 0 } }
async open(project: { projectId?: string; orgId?: string }) { void project }
async envList(project: any) { void project; return {} }
async envSet(project: any, kv: Record<string, string>) { void project; void kv }
async logs(project: any, options?: { follow?: boolean }) { void project; void options }
async generateConfig(args: { detection: any; cwd: string; overwrite: boolean }) { void args; return 'config-path' }
}
Testing Provider Plugins
- Unit test provider plugin methods by mocking
proc.run
/proc.spawnStream
fromsrc/utils/process
. - Add integration tests at CLI level to ensure providers are invoked (mock
loadProvider()
and assert calls).
Best Practices
- Avoid parsing large provider outputs when a JSON flag exists.
- Include helpful error messages. The CLI maps errors via
src/utils/errors.ts
. - Keep deploy logs short and rely on NDJSON/human streaming at CLI layer.
Wizard (start) behavior by provider
- Vercel: the
start
wizard performs the deploy (preview/prod) and printsurl
/logsUrl
. When--alias
is provided, the wizard attempts to set an alias after deploy. - Netlify: the
start
wizard is prepare‑only by default. It generates a safenetlify.toml
(uses the provider plugin to apply Next.js runtime/plugin when appropriate) and prints recommendednetlify deploy
commands with inferred--dir
. Optionally, pass--deploy
to execute a real deploy (supports--no-build
to deploy prebuilt artifacts). Summaries includelogsUrl
pointing to the Netlify dashboard.
See docs/commands.md#start
for details.
Contributor Guide: Building a Provider Plugin
- Create provider plugin file
- Path:
src/core/provider-system/providers/<name>.ts
- Implement
Provider
interface methods:validateAuth
,generateConfig
,deploy
,open
,logs
. - Keep each method short and single-purpose; prefer small helpers.
- Use
proc.run
andproc.spawnStream
- Import from
src/utils/process
. - Always return actionable errors; do not assume the provider CLI is installed or linked.
- Configuration helpers
- Write provider files with idempotency; do not overwrite user content without
overwrite: true
. - For monorepos, prefer linked app directory and fall back to root.
- Testing
- Use Vitest with
vi.mock
to replace provider plugins (mockloadProvider
) or process helpers. - Example pattern:
vi.mock('../../utils/process', async (orig) => {
const real = await orig<any>()
return { ...real, proc: { ...real.proc, run: vi.fn(async () => ({ ok: true, exitCode: 0, stdout: '', stderr: '' })) } }
})
- Add one or two integration tests at CLI level (
src/__tests__
) to ensure the adapter is reached.
- Logging and output
- Human mode: short, colorful, helpful messages. Use
logger.section
,logger.success
,logger.warn
. - JSON/NDJSON mode: emit machine objects and include
final: true
for summaries.
- CI and Exit Codes
- Respect
--ci
conventions: never prompt; return consistent exit codes. - Use
mapProviderError()
to translate raw provider errors into stable codes/messages/remedies.
- Provider Plugins
- See
src/core/provider-system/providers/
for provider plugin implementations and patterns.