diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a290c6..0e3c436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.2.0-next.18 + +- `bool create` now verifies the new project can be developed against *before* + writing any files. A project that isn't on the gateway runtime can't be used + as a local backend; previously `create` scaffolded the whole app and only then + hit the error, leaving an orphaned folder. It now fails immediately with the + server's reason and scaffolds nothing. +- The scaffolded app pins `bool-sdk@0.2.0-next.17` (the latest published + release), catching the template pin up from `next.16`. + +## 0.2.0-next.17 + +- `createBoolClient` routes the gateway same-origin for any deployment + subdomain, not just the canonical one — fixes a 404 when a deployed app is + reached via a renamed slug. + ## 0.2.0-next.16 - `bool create` no longer requires a name — a bare `bool create` generates a diff --git a/README.md b/README.md index a729fe7..f2152e0 100644 --- a/README.md +++ b/README.md @@ -111,30 +111,28 @@ const todos = await bool.entities.todos.list(); ### Documentation -Complete guides and API reference at **[bool.com/docs](https://bool.com/docs)**: +Complete guides at **[bool.com/docs](https://bool.com/docs)**: -- **[Local Development](https://bool.com/docs/local-development)** — complete - walkthrough with use cases, workflows, and tips -- **[CLI Reference](https://bool.com/docs/cli)** — command-line tools -- **[SDK Reference](https://bool.com/docs/sdk-reference)** — API documentation -- **[Data Design](https://bool.com/docs/database)** — schema patterns and - privacy +- **[Develop locally (CLI)](https://bool.com/docs/cli)** — the CLI commands, the + local workflow, client setup, and deploying +- **[Database](https://bool.com/docs/database)** — entities, records, and your + data model ### Admin Key Gotcha -When using the admin key (`apiKey`), on a **private** entity (one with -`user_id` owner field), you must set `user_id` explicitly: +When using the admin key (`apiKey`), on a **private** entity (one Bool gives an +`owner_id` owner column), you must set `owner_id` explicitly: ```ts -// ❌ Fails on private entity (NOT NULL constraint) +// ❌ Fails on private entity (owner_id has no value to default to) await bool.entities.tasks.create({ title: "Task" }); // ✅ Works -await bool.entities.tasks.create({ title: "Task", user_id: userId }); +await bool.entities.tasks.create({ title: "Task", owner_id: userId }); ``` -The admin key has no user identity, so it can't default `user_id`. End-user -clients and `boolk_` keys carry the user and default automatically. +The admin key has no user identity, so it can't default `owner_id`. End-user +clients and `boolk_` keys carry the user and default it automatically. Coding agents can do all of the above through Bool's MCP server instead (`list_entities`, `define_entity`, `list_records`, `get_entity_types`, diff --git a/package.json b/package.json index 7d0aea1..9e7bf86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bool-sdk", - "version": "0.2.0-next.17", + "version": "0.2.0-next.18", "description": "Client SDK for apps built on Bool — gateway data access, end-user auth, the AI battery, the React auth layer, and the local-dev CLI (link, types, deploy).", "type": "module", "main": "./dist/index.js", diff --git a/src/cli.test.ts b/src/cli.test.ts index 2a48418..9289caf 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -138,6 +138,23 @@ describe("create", () => { expect(logs.join("\n")).toMatch(/Created project ".+"/); expect(logs.join("\n")).toMatch(/Scaffolded a todo app in [a-z]+-[a-z]+-\d+\//); }); + + test("fails fast (no scaffold) when the new project isn't a gateway project", async () => { + const routes = createRoutes(); + routes["/api/projects/new1/connection"] = () => + json({ error: "not_gateway_project", message: "This project runs on the v1 runtime" }, 409); + const { deps, calls, errors } = makeDeps(cwd, routes); + + expect(await runCli(["create", "my-todo"], deps)).toBe(1); + // Surfaces the server's reason plus the reassurance that nothing was written. + expect(errors.join("\n")).toContain("v1 runtime"); + expect(errors.join("\n")).toContain("nothing was scaffolded"); + // No files scaffolded — the target dir was never created. + expect(existsSync(join(cwd, "my-todo"))).toBe(false); + // Bailed at the connection check: never scaffolded, linked, or pushed. + expect(calls.some((c) => c.url.endsWith("/api/projects/new1/api-key"))).toBe(false); + expect(calls.some((c) => c.url.endsWith("/api/projects/new1/entities"))).toBe(false); + }); }); describe("link", () => { diff --git a/src/cli.ts b/src/cli.ts index 8de01db..4b34389 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -248,13 +248,19 @@ async function writeConfigAndKey( tok: string, typesPath: string, deps: CliDeps, + // A connection descriptor the caller already fetched (e.g. `create` checks + // the runtime before scaffolding). When omitted we fetch it here — the case + // for `link`, which has nothing to check first. + prefetched?: Connection, ): Promise<{ config: BoolConfig; name: string }> { - const conn = await apiJson( - deps, - tok, - apiUrl, - `/api/projects/${projectId}/connection`, - ); + const conn = + prefetched ?? + (await apiJson( + deps, + tok, + apiUrl, + `/api/projects/${projectId}/connection`, + )); const config: BoolConfig = { projectId: conn.projectId, @@ -365,7 +371,28 @@ async function cmdCreate( ); deps.log(`Created project "${project.name ?? name}" (${project.id}).`); - // 2. Scaffold the todo app into dir. + // 2. Confirm the project can actually be developed against BEFORE writing any + // files. Local dev requires the gateway runtime; a non-gateway (v1) + // project 409s here. Checking now means a v1 project fails with a clear + // message and leaves no half-scaffolded folder behind (previously we + // scaffolded first and only discovered the problem afterward). + let conn: Connection; + try { + conn = await apiJson( + deps, + tok, + apiUrl, + `/api/projects/${project.id}/connection`, + ); + } catch (e) { + throw new CliError( + `${e instanceof Error ? e.message : String(e)}\n` + + `Project ${project.id} was created but can't be used for local development, ` + + `so nothing was scaffolded.`, + ); + } + + // 3. Scaffold the todo app into dir. mkdirSync(dir, { recursive: true }); const files = todoTemplate(name); for (const [rel, content] of Object.entries(files)) { @@ -380,11 +407,19 @@ async function cmdCreate( // Everything below runs inside the new project dir. const sub: CliDeps = { ...deps, cwd: dir }; - // 3. Write bool.config.json + .env.bool into the project dir. - const { config } = await writeConfigAndKey(project.id, apiUrl, tok, DEFAULT_TYPES_PATH, sub); + // 4. Write bool.config.json + .env.bool into the project dir, reusing the + // connection descriptor we already fetched in step 2. + const { config } = await writeConfigAndKey( + project.id, + apiUrl, + tok, + DEFAULT_TYPES_PATH, + sub, + conn, + ); deps.log(`Linked ${CONFIG_FILE} to project ${project.id}.`); - // 4. Declare the todos entity so the table exists, then refresh types. + // 5. Declare the todos entity so the table exists, then refresh types. // If this fails the app has no data — don't ship a broken deploy; tell the // user to re-push once the cause is fixed. const pushCode = await cmdEntitiesPush(flags, sub); @@ -395,7 +430,7 @@ async function cmdCreate( } await pullTypes(config, tok, sub); - // 5. Optionally publish. + // 6. Optionally publish. if (flags.deploy) { await cmdDeploy(flags, sub); } else { diff --git a/src/templates.ts b/src/templates.ts index 1361bf9..8946185 100644 --- a/src/templates.ts +++ b/src/templates.ts @@ -8,9 +8,11 @@ // `todos` entity is PUBLIC (one shared list, no per-user isolation) so the // deployed app works for any visitor with no sign-in. -// Keep in sync with the CLI's own version so the scaffolded app pulls the -// matching client. Injected into package.json at scaffold time. -export const TEMPLATE_BOOL_SDK_VERSION = "0.2.0-next.16"; +// The bool-sdk version the scaffolded app pins, injected into its package.json. +// This must be an ALREADY-PUBLISHED version (the scaffold runs `npm install`), +// so it tracks the latest published release, not this in-flight package.json +// version — they can differ by the release currently being cut. +export const TEMPLATE_BOOL_SDK_VERSION = "0.2.0-next.17"; function packageJson(name: string): string { return (