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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ npm install -D void
npx void init
```

This scaffolds a starter app with an interactive setup flow. Then:
This scaffolds a starter app with an interactive setup flow and can also log you in and link or create your deploy project. Then:

```sh
npm run dev # local dev with workerd runtime
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Void apps run on Cloudflare's infrastructure but you don't need to use your own
### First deploy

```bash
void auth login # OAuth via GitHub or Google
void init # can handle auth + project setup during onboarding
void deploy # auto-detects app type, builds, deploys
```

On first deploy, the CLI prompts you to create or select a project. The project link is saved to `.void/project.json` for subsequent deploys.
If you skipped Void project setup during `void init`, you can still run `void auth login` manually. On first deploy, the CLI will prompt you to create or select a project if none is linked yet. The project link is saved to `.void/project.json` for subsequent deploys.

### Migrations

Expand Down
10 changes: 8 additions & 2 deletions docs/guide/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ This scaffolds a Pages-mode starter with an interactive setup flow.

As part of `void init`, you'll choose a Pages framework (React, Vue, Svelte, or Solid) and a starter type. D1 is the default top option and scaffolds a DB-backed page loader, schema, migration, and API route. PostgreSQL scaffolds the same starter and writes `"database": "pg"` to `void.json`. Static Pages skips the database and server starter files so you can start with static content and add Void features later.

At the end of the full interactive flow, `void init` can also handle Void project setup by logging you in and linking or creating your Void project. That means the default first-time path is just `void init`, then `void deploy`.

For the database-backed starters, D1 is the zero-config default for prototyping and read-heavy apps, while PostgreSQL is better when you already have Postgres infrastructure or need heavier writes and more complex queries.

<details>
<summary style="cursor:pointer">
💡 <b>Notes on <code>void</code> binary usage</b>
Expand Down Expand Up @@ -141,12 +145,14 @@ Then visit:
- App: `http://localhost:5173`
- API route: `http://localhost:5173/api/hello`

### 3. Log in
### 3. Finish Void project setup if you skipped it during `void init`

```sh
void auth login
```

If you already logged in and linked a project during `void init`, you can skip this step.

### 4. Deploy

Set secrets once before deploying, if any:
Expand Down Expand Up @@ -189,7 +195,7 @@ void deploy
On first deploy, Void will:

- build your app
- create or link a project
- create or link a project if you did not already do that during `void init`
- provision required resources (for example D1/KV/R2 when inferred)
- deploy to `https://<slug>.void.app`

Expand Down
5 changes: 5 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ After that, the full interactive flow walks through:
5. **MCP config:** writes MCP server config using that same agent context.
6. **Demo code:** for existing non-Pages projects, optionally scaffolds a `db/migrations/` directory plus an API route and typed fetch example.
7. **GitHub Actions:** optionally creates `.github/workflows/deploy.yml` with the right package manager commands.
8. **Project setup:** optionally logs you in, lets you select or create a project, and writes `.void/project.json` so your first deploy can just be `void deploy`.

If no agent is detected, `void init` asks you to choose one from a short list (Claude, Cursor, Codex, Gemini CLI, Generic). That single choice is reused across all agent steps.

Expand All @@ -77,6 +78,8 @@ Flags can be combined. When any flag is provided, only the specified steps run a

OAuth login. You choose GitHub or Google at the prompt, and the token is saved to `~/.void/config.json`.

This is optional if you already completed auth during the interactive `void init` flow.

### `void auth logout`

Removes saved credentials.
Expand Down Expand Up @@ -168,6 +171,8 @@ Project resolution precedence:

If no project is linked and no override is provided, CLI prompts to link or create one.

That fallback is mainly for projects that skipped Void project setup during `void init`.

## Database

### `void db status`
Expand Down
2 changes: 1 addition & 1 deletion packages/void/AGENT_PROMPT.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Void

This project uses [Void](https://void.cloud) — a fullstack Vite plugin + deployment platform for Cloudflare. `voidPlugin()` in `vite.config.ts` gives you file-based API routing on Hono (`routes/`), Inertia-inspired server-rendered pages with co-located loaders/actions (`pages/` + `@void/vue` or `@void/react`), auto-provisioned D1/KV/R2 bindings, first-class Drizzle ORM integration (schema in `db/schema.ts` -> `void/db` Drizzle instance -> typed routes -> typed fetch client), built-in auth, queues, cron jobs, edge caching (ISR), and one-command deploys via `npx void deploy`.
This project uses [Void](https://void.cloud) — a fullstack Vite plugin + deployment platform for Cloudflare. `voidPlugin()` in `vite.config.ts` gives you file-based API routing on Hono (`routes/`), Inertia-inspired server-rendered pages with co-located loaders/actions (`pages/` + `@void/vue` or `@void/react`), auto-provisioned D1/KV/R2 bindings, first-class Drizzle ORM integration (schema in `db/schema.ts` -> `void/db` Drizzle instance -> typed routes -> typed fetch client), built-in auth, queues, cron jobs, edge caching (ISR), and one-command deploys via `npx void deploy`. For first-time setup, prefer `npx void init`; the interactive flow can scaffold the app, configure project files, handle auth, and link or create the deploy project before the first deploy.

Database: define Drizzle tables in `db/schema.ts`, import `db` from `void/db` and tables from `@schema`. Use `void db push` for prototyping, `void db generate` for production migrations. `drizzle-orm` and `drizzle-kit` ship with void (no extra install). Migrations live in `db/migrations/`.

Expand Down
2 changes: 2 additions & 0 deletions packages/void/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ npm install -D void
npx void init
```

The full interactive `void init` flow can scaffold a starter, configure local project files, log you in, and link or create your Void project so the next step is just `npx void deploy`.

```ts
// vite.config.ts
import { defineConfig } from "vite";
Expand Down
4 changes: 4 additions & 0 deletions packages/void/getting-started-prompt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ npx void init

During `void init`, first choose a Pages framework (React, Vue, Svelte, or Solid), then choose D1 for a zero-config, fully managed default or PostgreSQL if you already have Postgres infrastructure or expect heavier writes and more complex queries.

At the end of the interactive flow, `void init` can also log you in and link or create your Void project so your first deploy is just:

void deploy

Database workflow: define Drizzle tables in `db/schema.ts`, import `db` from `void/db` and tables from `@schema`. Use `void db push` to prototype (applies schema directly, no migration files). Use `void db generate` when ready to create production migration files in `db/migrations/`. `drizzle-orm` and `drizzle-kit` ship with void — no extra install needed. Scaffold new tables with `void gen model <name> [columns...]`.

Now ask me what I want to build.
4 changes: 1 addition & 3 deletions packages/void/skills/void/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ Docs in this skill are bundled from `docs/` during `void` package build and live

## Command Naming

Use `void` in examples and commands in this skill.

- `void` and `void` are equivalent binaries. Prefer `void`.
Use `void` in examples and commands in this skill. For first-time setup, prefer `void init` followed by `void deploy`; `void init` can handle auth and project linking interactively.

## No-Args Behavior

Expand Down
100 changes: 100 additions & 0 deletions packages/void/src/cli/deploy-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { cancel, confirm, isCancel, log, select, spinner } from "@clack/prompts";
import { PlatformClient } from "./client";
import { getToken, isStagingMode, runLogin, saveToken } from "./auth";
import { promptProjectSelection, promptProjectSlug } from "./prompt";
import { inferDefaultSlug } from "./resolve-project";
import { readProjectConfig, writeProjectConfig, type ProjectConfig } from "./project";

async function promptForLoginToken(): Promise<string> {
const provider = await select({
message: "Choose a login provider:",
options: [
{ value: "github", label: "GitHub" },
{ value: "google", label: "Google" },
],
});

if (isCancel(provider)) {
cancel("Setup cancelled.");
process.exit(0);
}

const providerName = provider === "github" ? "GitHub" : "Google";
const s = spinner();
s.start(`Opening browser for ${providerName} login...`);

try {
const token = await runLogin(provider);
saveToken(token);
s.stop("Logged in!");
log.success(isStagingMode() ? "Staging token saved." : "Token saved.");
return token;
} catch (error) {
s.stop("Login failed");
throw error;
}
}

async function ensureToken(): Promise<string> {
const existing = getToken();
if (existing) {
return existing;
}

return promptForLoginToken();
}

async function createProject(
root: string,
client: PlatformClient,
slug: string,
): Promise<ProjectConfig> {
log.step(`Creating project "${slug}"...`);
const project = await client.createProject(slug);
const config = { projectId: project.id, slug: project.slug };
writeProjectConfig(root, config);
log.success(`Project created and linked: ${config.slug}`);
return config;
}

export async function maybeSetupDeployProject(root: string): Promise<ProjectConfig | null> {
const existing = readProjectConfig(root);
if (existing) {
log.info(`Project already linked: ${existing.slug}`);
return existing;
}

const existingToken = getToken();
const shouldSetup = await confirm({
message: existingToken
? "Link your Void project now?"
: "Set up your Void project now? (log in + link project)",
});
if (isCancel(shouldSetup)) {
cancel("Setup cancelled.");
process.exit(0);
}
if (!shouldSetup) {
return null;
}

const token = existingToken ?? (await ensureToken());
const client = new PlatformClient(token);
const projects = await client.listProjects();

if (projects.length === 0) {
const slug = await promptProjectSlug(inferDefaultSlug(root));
return createProject(root, client, slug);
}

const result = await promptProjectSelection(projects, "Select a project to link:");
if (result.action === "select") {
const config = { projectId: result.project.id, slug: result.project.slug };
writeProjectConfig(root, config);
log.success(`Linked project: ${config.slug}`);
return config;
}

const slug = await promptProjectSlug(inferDefaultSlug(root));
return createProject(root, client, slug);
}
5 changes: 5 additions & 0 deletions packages/void/src/cli/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
type McpConfigTarget,
} from "./agents";
import { cliTitle } from "./output";
import { maybeSetupDeployProject } from "./deploy-setup";

export interface InitArgs {
tsconfig: boolean;
Expand Down Expand Up @@ -1201,5 +1202,9 @@ export async function runInitCommand(root: string, initArgs?: InitArgs): Promise
}
}

if (!targeted) {
await maybeSetupDeployProject(root);
}

outro(`Run \`${getDevCommand(root, framework)}\` to start developing`);
}
Loading
Loading