From 8316f418a4bbecc6abc5a470f2aeb9a5997a2416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Mon, 9 Nov 2026 03:53:09 +0100 Subject: [PATCH 1/2] Rename Zero v2 to First Zero v2 is now First. Zero 0.8 keeps the name Zero: it is retired, existing Spaces keep serving and publishing it, and Zero 0.9 is its last release apart from security fixes, for moving apps to First. scripts/rename-to-first.mjs applies the rename and is idempotent, so a branch picks it up by merging and running it again. It moves the First page to /first and the CLI page to /cli/first, renames v2 names in authored content, and keeps every Zero 0.8 name and every name production already ships. /zero redirects to /first. Hand edits on top: the Zero 0.8 page says Zero is retired, the glossary defines both, and links that said "an older Zero app" now say Zero 0.8. --- content/(account)/billing.mdx | 2 +- content/(account)/usage.mdx | 6 +- content/(dynamic)/crons.mdx | 2 +- content/(dynamic)/database.mdx | 24 +-- content/(dynamic)/environment-variables.mdx | 8 +- content/(dynamic)/{zero.mdx => first.mdx} | 88 ++++---- content/(dynamic)/functions.mdx | 4 +- content/(dynamic)/meta.ts | 2 +- content/(dynamic)/object-storage.mdx | 4 +- content/(dynamic)/wordpress.mdx | 2 +- content/(dynamic)/zero-migrate.mdx | 40 ++-- content/(reference)/config-file.mdx | 2 +- content/(reference)/glossary.mdx | 8 +- content/(serve)/site-pages.mdx | 2 +- content/(serve)/stats.mdx | 2 +- content/cli/builds.mdx | 2 +- content/cli/db.mdx | 4 +- content/cli/{zero.mdx => first.mdx} | 102 ++++----- content/cli/meta.ts | 2 +- content/cli/project.mdx | 6 +- content/cli/publish.mdx | 2 +- content/index.mdx | 2 +- .../infrastructure/server-specs-settings.mdx | 2 +- content/quickstart.mdx | 4 +- evals.yaml | 8 +- redirects.ts | 5 +- scripts/docs-experience.test.mjs | 10 +- scripts/redirect-rules.mjs | 4 +- scripts/rename-to-first.mjs | 203 ++++++++++++++++++ scripts/verify-routes.mjs | 2 +- 30 files changed, 382 insertions(+), 172 deletions(-) rename content/(dynamic)/{zero.mdx => first.mdx} (63%) rename content/cli/{zero.mdx => first.mdx} (67%) create mode 100644 scripts/rename-to-first.mjs diff --git a/content/(account)/billing.mdx b/content/(account)/billing.mdx index 29af7a70..39bcfdee 100644 --- a/content/(account)/billing.mdx +++ b/content/(account)/billing.mdx @@ -19,7 +19,7 @@ Free needs no card and is for personal, non-commercial projects. Enterprise plan See [Usage](/usage) for Free allowances, shared pools, and paid usage rates. -PHP, Zero, Workers, SQL, private sharing, managed publishing, Git, CLI, API, MCP and exports are available across plans. Usage limits still apply. Connecting a domain has no extra fee. Domain registration, renewal and third-party licenses are separate purchases. +PHP, First, Workers, SQL, private sharing, managed publishing, Git, CLI, API, MCP and exports are available across plans. Usage limits still apply. Connecting a domain has no extra fee. Domain registration, renewal and third-party licenses are separate purchases. Domains registered through Spacefast or WordPress.com, and domains using Spacefast DNS, do not count toward the Free plan's {{free-customDomains}}-domain allowance. Other connected domains and individually connected subdomains count toward it. Managed HTTPS is included. Wildcards such as `*.example.com` require Plus or Enterprise. diff --git a/content/(account)/usage.mdx b/content/(account)/usage.mdx index 101293de..42d7fbe8 100644 --- a/content/(account)/usage.mdx +++ b/content/(account)/usage.mdx @@ -13,7 +13,7 @@ These are per team each month, except storage, which is a retained-byte allowanc /_pricing/usage.md -The CPU allowance combines PHP, Zero and SQL. Each other allowance stands on its own; unused build minutes cannot buy more transfer. Free has no dollar credit and no automatic overage bill. +The CPU allowance combines PHP, First and SQL. Each other allowance stands on its own; unused build minutes cannot buy more transfer. Free has no dollar credit and no automatic overage bill. Exceeding an allowance alerts Spacefast for review. A Space may be suspended until the applicable allowance resets, retained storage falls below its limit, or the team upgrades. Data stays and exports remain available. @@ -23,9 +23,9 @@ Prices are USD before tax. The included quantities below belong to the whole tea CPU means active processing time, excluding waits. There is no memory charge. Build minutes measure elapsed machine time, including installation and idle time; platform-caused retries are not billed. -An invocation is one PHP or Zero origin execution, including a request that bypasses the cache or cron run. Cached hits and Worker requests are excluded. Workers have their own request meter. +An invocation is one PHP or First origin execution, including a request that bypasses the cache or cron run. Cached hits and Worker requests are excluded. Workers have their own request meter. -Cached delivery counts cached response bytes. Non-cached transfer counts origin misses, bypasses and PHP or Zero response bytes. Worker responses are excluded from that transfer charge. Bytes are counted once on the path that produced them. +Cached delivery counts cached response bytes. Non-cached transfer counts origin misses, bypasses and PHP or First response bytes. Worker responses are excluded from that transfer charge. Bytes are counted once on the path that produced them. Email counts recipient deliveries, so one message to three people counts as three. Daily delivery safeguards still apply. A credit balance does not override them; check the current entitlement before sending. diff --git a/content/(dynamic)/crons.mdx b/content/(dynamic)/crons.mdx index d9e53a0d..ebebed84 100644 --- a/content/(dynamic)/crons.mdx +++ b/content/(dynamic)/crons.mdx @@ -9,7 +9,7 @@ There's no dashboard editor for crons. Declare schedules in `sf.jsonc`, then pub ## How a run happens -A cron is a scheduled `GET` against a path on your own site. It goes through the same front door a visitor gets, so whatever serves that path answers it: a [Zero](/zero) page, a [Functions](/functions) route, or a static file. +A cron is a scheduled `GET` against a path on your own site. It goes through the same front door a visitor gets, so whatever serves that path answers it: a [First](/first) page, a [Functions](/functions) route, or a static file. A response under 400 is a success and is silent. A response of 400 or worse, or a fatal error, is a failure and is reported. diff --git a/content/(dynamic)/database.mdx b/content/(dynamic)/database.mdx index 8e3e3ac8..d219cff3 100644 --- a/content/(dynamic)/database.mdx +++ b/content/(dynamic)/database.mdx @@ -1,32 +1,32 @@ --- title: Database -description: Every Space gets its own MySQL. A Zero app's collections and tables live there, Spacefast migrates it on publish, and a single-use SQL console opens it when you need raw SQL. +description: Every Space gets its own MySQL. A First app's collections and tables live there, Spacefast migrates it on publish, and a single-use SQL console opens it when you need raw SQL. sidebar: order: 2 --- -There's no connection string for this database. Your code reaches it through Zero's `db` or a worker's `env.DB`, and you reach it through a single-use SQL console. +There's no connection string for this database. Your code reaches it through First's `db` or a worker's `env.DB`, and you reach it through a single-use SQL console. :::note[Don't have a database yet?] -Start a [Zero](/zero) app with `sf init --runtime zero`. Its `zero.config.ts` declares the collections and tables, and Spacefast creates them when you publish. A [Functions](/functions#what-is-on-env) worker also gets the database, through `env.DB`. +Start a [First](/first) app with `sf init --runtime first`. Its `first.config.ts` declares the collections and tables, and Spacefast creates them when you publish. A [Functions](/functions#what-is-on-env) worker also gets the database, through `env.DB`. ::: ## What it is -Every Space has one database, the MySQL that lives on the machine serving the site. A [Zero](/zero) app keeps its collections and tables there. A [Functions](/functions) worker gets it as `env.DB`. +Every Space has one database, the MySQL that lives on the machine serving the site. A [First](/first) app keeps its collections and tables there. A [Functions](/functions) worker gets it as `env.DB`. You do not create it, size it, or connect to it. Your config declares the schema, and Spacefast applies it when a version initializes. :::warning[There is no external connection string] -No route, flag, or dashboard field returns a DSN. Your code reaches the database through `db` (Zero) or `env.DB` (Functions). For everything else there is the [SQL console](#the-sql-console). +No route, flag, or dashboard field returns a DSN. Your code reaches the database through `db` (First) or `env.DB` (Functions). For everything else there is the [SQL console](#the-sql-console). ::: ## Query it from code -In a Zero app, hooks and actions read and write through `db`. Your schema is the API, and its column and relation names are checked at build time. +In a First app, hooks and actions read and write through `db`. Your schema is the API, and its column and relation names are checked at build time. -```ts zero.config.ts -import { action, db, defineConfig, HttpError, integer, table, text } from "zero"; +```ts first.config.ts +import { action, db, defineConfig, HttpError, integer, table, text } from "first"; // Each signed-in account reads and writes only its own rows, through every route. const notes = table( @@ -99,12 +99,12 @@ export default { ## Migrations -Your `zero.config.ts` is the schema. When you publish a version that changes it, Spacefast diffs it against the schema the Space already runs and applies the difference while the new version initializes, before it can go live. +Your `first.config.ts` is the schema. When you publish a version that changes it, Spacefast diffs it against the schema the Space already runs and applies the difference while the new version initializes, before it can go live. -To see the SQL a change produces, build locally and read the newest file in `zero/migrations/`: +To see the SQL a change produces, build locally and read the newest file in `first/_generated/migrations/`: ```bash -sf zero build +sf first build ``` :::warning[Rollback does not roll back the schema] @@ -130,7 +130,7 @@ sf db console --show-secret `sf db console` opens a browser by default, because the URL grants full SQL authority over the Space's database. `--show-secret` prints it instead, and is the deliberate override. The URL is masked in `--json` receipts unless you pass it. Opening the console needs write access to the Space. -A Zero app's tables share a prefix that starts with `sfz_`. +A First app's tables share a prefix that starts with `sfz_`. ## Limits diff --git a/content/(dynamic)/environment-variables.mdx b/content/(dynamic)/environment-variables.mdx index 6f813e31..64a10a63 100644 --- a/content/(dynamic)/environment-variables.mdx +++ b/content/(dynamic)/environment-variables.mdx @@ -92,10 +92,10 @@ EOF sf publish ``` -A [Zero](/zero) action or hook reads it with `env()`, which returns `null` for a name that isn't set. +A [First](/first) action or hook reads it with `env()`, which returns `null` for a name that isn't set. -```ts zero.config.ts -import { action, defineConfig, env } from "zero"; +```ts first.config.ts +import { action, defineConfig, env } from "first"; export default defineConfig({ actions: { @@ -109,7 +109,7 @@ export default defineConfig({ }); ``` -A Zero app sees **Space** variables only. Team variables do not reach it, so set what the app needs on the Space. +A First app sees **Space** variables only. Team variables do not reach it, so set what the app needs on the Space. A [Functions](/functions) worker reads the same values from `env`, its second handler argument. diff --git a/content/(dynamic)/zero.mdx b/content/(dynamic)/first.mdx similarity index 63% rename from content/(dynamic)/zero.mdx rename to content/(dynamic)/first.mdx index 329f5438..e0a6cbb7 100644 --- a/content/(dynamic)/zero.mdx +++ b/content/(dynamic)/first.mdx @@ -1,35 +1,35 @@ --- -title: Dynamic sites with Zero -description: Zero compiles one zero.config.ts (collections, tables, actions, and TSX pages) into WordPress, and Spacefast hosts it on your Space. +title: Dynamic sites with First +description: First compiles one first.config.ts (collections, tables, actions, and TSX pages) into WordPress, and Spacefast hosts it on your Space. sidebar: order: 0 --- -Zero turns one `zero.config.ts` into a WordPress app: collections, tables, actions, access rules, and pages. `sf publish` sends the source, and Spacefast compiles it and runs it on the Space's own WordPress. Zero is available on every account, with [usage allowances and metered rates](/usage). +First (formerly Zero v2) turns one `first.config.ts` into a WordPress app: collections, tables, actions, access rules, and pages. `sf publish` sends the source, and Spacefast compiles it and runs it on the Space's own WordPress. First is available on every account, with [usage allowances and metered rates](/usage). -## What Zero is +## What First is -Zero is a compiler. You describe your data and your server logic in TypeScript, and Zero compiles it to WordPress plugins: a post type for each collection, a SQL table for each table, REST routes for both, and plain PHP for your hooks and actions. Nothing runs JavaScript on the server. +First is a compiler. You describe your data and your server logic in TypeScript, and First compiles it to WordPress plugins: a post type for each collection, a SQL table for each table, REST routes for both, and plain PHP for your hooks and actions. Nothing runs JavaScript on the server. -A project is a Zero app when it has a `zero.config.ts` at its root. There is no runtime block to declare and no switch to turn on. +A project is a First app when it has a `first.config.ts` at its root. There is no runtime block to declare and no switch to turn on. The other runtime is [Functions](/functions), a worker for npm-heavy code and framework output like OpenNext. ## Start an app ```bash -sf init my-app --runtime zero +sf init my-app --runtime first cd my-app -sf zero dev +sf first dev sf publish ``` -`sf init --runtime zero` writes a starter `zero.config.ts` named after the directory. `sf zero init` writes the same file. `sf zero dev` runs the app on a local WordPress and rebuilds when you save. `sf publish` publishes it to a new Space. +`sf init --runtime first` writes a starter `first.config.ts` named after the directory. `sf first init` writes the same file. `sf first dev` runs the app on a local WordPress and rebuilds when you save. `sf publish` publishes it to a new Space. The starter holds one collection, one table, and one action: -```ts zero.config.ts -import { action, collection, db, defineConfig, table, text, timestamp } from "zero"; +```ts first.config.ts +import { action, collection, db, defineConfig, table, text, timestamp } from "first"; // Collections are WordPress posts: they show up in wp-admin and the REST API. export const posts = collection({ @@ -61,7 +61,7 @@ export default defineConfig({ }); ``` -Everything comes from the `"zero"` module. +Everything comes from the `"first"` module. ## What you get @@ -69,11 +69,11 @@ Everything comes from the `"zero"` module. | --- | --- | | Collections | Editorial content. Each is a WordPress post type with the block editor, drafts, and revisions | | Tables | App data in its own SQL table | -| REST routes | `/wp-json/zero/v1/` for every collection and table, with relations loaded through `with` | -| Actions | Named server functions at `POST /wp-json/zero/v1/actions/` | +| REST routes | `/wp-json/first/v1/` for every collection and table, with relations loaded through `with` | +| Actions | Named server functions at `POST /wp-json/first/v1/actions/` | | Hooks | Code that runs before or after a collection write, inside the same transaction | | Access rules | Who may read and write each collection, table, field, and action | -| Sign-in | Accounts, sessions, guests, passkeys, and MFA through the `zero-identity` plugin | +| Sign-in | Accounts, sessions, guests, passkeys, and MFA through the `first-identity` plugin | | Jobs and webhooks | Background work that runs after a request commits | | Pages | TSX routes and layouts that WordPress renders per request | | Typed client | A `fetch` client generated from your schema | @@ -84,8 +84,8 @@ Your data lives in the Space's MySQL database. See [Database](/database). Pick the storage by what the data is. A `collection()` is content people edit: it gets a WordPress post type, an editor in wp-admin, drafts, and revisions. A `table()` is app data: it gets its own SQL table and nothing else. -```ts zero.config.ts -import { collection, defineConfig, defineRelations, integer, richText, table, text } from "zero"; +```ts first.config.ts +import { collection, defineConfig, defineRelations, integer, richText, table, text } from "first"; const posts = collection({ title: text().notNull(), body: richText() }, { content: "body" }); const stock = table({ @@ -103,7 +103,7 @@ const relations = defineRelations({ ...collections, ...tables }, (r) => ({ export default defineConfig({ collections, tables, relations }); ``` -Every row gets an `id`. The registry key is the name everywhere: `shoppingList` is `zero.shoppingList` in the client and `/wp-json/zero/v1/shopping_list` over REST. +Every row gets an `id`. The registry key is the name everywhere: `shoppingList` is `first.shoppingList` in the client and `/wp-json/first/v1/shopping_list` over REST. Column builders include `text()`, `integer()`, `real()`, `boolean()`, `timestamp()`, `date()`, `json()`, `email()`, `url()`, `slug()`, `richText()`, `media()`, `file()`, `select()`, `group()`, `repeater()`, and `blocks()`. Chain `.notNull()`, `.default(value)`, `.unique()`, `.min(n)`, `.max(n)`, `.pattern(/re/)`, or `.references(...)` to constrain one. A modifier that makes no sense for its column, like `.max()` on a boolean, fails the build at that line. @@ -115,14 +115,14 @@ Every collection and table takes `access: { read, create, update, delete }`. Eac { access: { read: "anyone", create: "author", update: "author", delete: "editor" } } ``` -The default is read by `"anyone"`, write by `"editor"`. Actions default to `"editor"` too, so an action a visitor calls needs `access: "anyone"`. The rules hold on every path into the data: Zero's REST routes, WordPress's own `/wp/v2` routes, wp-admin, and Abilities. +The default is read by `"anyone"`, write by `"editor"`. Actions default to `"editor"` too, so an action a visitor calls needs `access: "anyone"`. The rules hold on every path into the data: First's REST routes, WordPress's own `/wp/v2` routes, wp-admin, and Abilities. ## Actions An action takes typed input and returns JSON. `input` uses the same column builders as your schema, so a bad request is refused before your handler runs. ```bash -curl -X POST https://my-site.view.fast/wp-json/zero/v1/actions/signup \ +curl -X POST https://my-site.view.fast/wp-json/first/v1/actions/signup \ -H 'Content-Type: application/json' \ --data '{"email":"sam@example.com"}' ``` @@ -146,26 +146,26 @@ hooks: { ## What compiles -Hooks, actions, and access functions are a checked subset of TypeScript compiled to PHP. Functions, closures, spread, classes, `async`/`await` on Zero calls, and the usual control flow all work. `eval`, generators, `any`, and dynamic `import()` do not. Anything outside the subset fails the build at `file:line:col` with a fix: +Hooks, actions, and access functions are a checked subset of TypeScript compiled to PHP. Functions, closures, spread, classes, `async`/`await` on First calls, and the usual control flow all work. `eval`, generators, `any`, and dynamic `import()` do not. Anything outside the subset fails the build at `file:line:col` with a fix: ```bash -sf zero check -sf zero check --json +sf first check +sf first check --json ``` -Imports can be `"zero"`, your own relative modules, and installed source packages. `sf publish` captures the installed packages it needs. Spacefast never runs `npm install`. +Imports can be `"first"`, your own relative modules, and installed source packages. `sf publish` captures the installed packages it needs. Spacefast never runs `npm install`. ## Call it from a frontend -`sf zero build` and `sf zero dev` write a typed client to `zero/client/`. It is plain `fetch`, so it works from any framework. +`sf first build` and `sf first dev` write a typed client to `first/client/`. It is plain `fetch`, so it works from any framework. ```ts -import { createClient } from "./zero/client/index.ts"; +import { createClient } from "./first/client/index.ts"; -const zero = createClient({ url: "https://my-site.view.fast/wp-json/zero/v1" }); +const first = createClient({ url: "https://my-site.view.fast/wp-json/first/v1" }); -const posts = await zero.posts.list({ orderBy: { title: "asc" } }); -await zero.actions.signup({ email: "sam@example.com" }); +const posts = await first.posts.list({ orderBy: { title: "asc" } }); +await first.actions.signup({ email: "sam@example.com" }); ``` Every argument and result is typed from your schema. A published Space also serves its client at `/__spacefast/client.js`, with types at `/__spacefast/client.d.ts`. @@ -193,7 +193,7 @@ A page reads data with a `load()` function and renders request values through he Read a Space variable with `env()`. Set it with `sf env set`, or put it in `.env.server` and publish. ```ts -import { action, env } from "zero"; +import { action, env } from "first"; const checkout = action({ access: "anyone", @@ -221,11 +221,11 @@ jobs: { }, ``` -Call `jobs.notifyEditors.enqueue({ postId })` to queue one. A `webhooks` entry sends each matching write to a URL as a POST request, signed in the `zero-signature` header, and retries the same way. On Spacefast a due job can wait up to five minutes before it starts. +Call `jobs.notifyEditors.enqueue({ postId })` to queue one. A `webhooks` entry sends each matching write to a URL as a POST request, signed in the `first-signature` header, and retries the same way. On Spacefast a due job can wait up to five minutes before it starts. ## No realtime -Zero has no live subscriptions. A client sees a change when it reads again. +First has no live subscriptions. A client sees a change when it reads again. ## Before you publish a schema change @@ -234,27 +234,27 @@ Every version of a Space shares one database, migrated forward to the newest sch ## Run it locally ```bash -sf zero dev -sf zero dev stop -sf zero build +sf first dev +sf first dev stop +sf first build ``` -`sf zero dev` boots WordPress through WordPress Studio, with no Docker. It needs Node 22 or newer. The first start downloads WordPress and the plugins the app needs. Keep it running and it rebuilds on every save. It prints the local URL and an admin login for local use only. +`sf first dev` boots WordPress through WordPress Studio, with no Docker. It needs Node 22 or newer. The first start downloads WordPress and the plugins the app needs. Keep it running and it rebuilds on every save. It prints the local URL and an admin login for local use only. -`sf zero build` writes the plugins to `.zero/wp-content/`, new migrations to `zero/migrations/`, and the client to `zero/client/`. Commit `zero/migrations/`, `zero/client/`, and `zero.gen.ts`. `.zero/` is local state, and publish skips it. +`sf first build` writes the plugins to `.first/wp-content/`, new migrations to `first/_generated/migrations/`, and the client to `first/client/`. Commit `first/_generated/migrations/`, `first/client/`, and `first.gen.ts`. `.first/` is local state, and publish skips it. -`sf zero check` and `sf publish` use the compiler bundled with the CLI. `sf zero dev` and `sf zero build` need the CLI's native optional package. If it is missing, reinstall the CLI with optional dependencies enabled. +`sf first check` and `sf publish` use the compiler bundled with the CLI. `sf first dev` and `sf first build` need the CLI's native optional package. If it is missing, reinstall the CLI with optional dependencies enabled. ## Bring a schema from another CMS ```bash -sf zero import payload ../my-payload-app -sf zero import emdash ../site --output app/zero.config.ts +sf first import payload ../my-payload-app +sf first import emdash ../site --output app/first.config.ts ``` -`sf zero import` reads a Payload, EmDash, or Sanity schema and writes a new `zero.config.ts`. It never runs code from the other CMS and never overwrites a file. Anything Zero cannot hold refuses the import, and nothing is written. Every write rule starts as denied, so set `access` before you publish. +`sf first import` reads a Payload, EmDash, or Sanity schema and writes a new `first.config.ts`. It never runs code from the other CMS and never overwrites a file. Anything First cannot hold refuses the import, and nothing is written. Every write rule starts as denied, so set `access` before you publish. -Have a Zero app on Spacefast with no `zero.config.ts`? See [Migrate an older Zero app](/zero-migrate). +Have a Zero app on Spacefast, one with `"runtime": { "kind": "zero" }` in `sf.jsonc`? Zero is retired. See [Zero 0.8](/zero-migrate). @@ -266,7 +266,7 @@ Have a Zero app on Spacefast with no `zero.config.ts`? See [Migrate an older Zer Workers, the `functions/` file router, and OpenNext. - - Every `sf zero` command and its flags. + + Every `sf first` command and its flags. diff --git a/content/(dynamic)/functions.mdx b/content/(dynamic)/functions.mdx index c316287d..64a73a00 100644 --- a/content/(dynamic)/functions.mdx +++ b/content/(dynamic)/functions.mdx @@ -5,9 +5,9 @@ sidebar: order: 1 --- -Functions can detect a supported worker layout when you publish, without an explicit `runtime` block. Custom layouts need a declaration. A [Zero](/zero) app is a project with a `zero.config.ts`. +Functions can detect a supported worker layout when you publish, without an explicit `runtime` block. Custom layouts need a declaration. A [First](/first) app is a project with a `first.config.ts`. -Functions runs your code as a worker. Use it when you need npm packages or framework output like OpenNext Next.js. If you want collections, tables, and actions compiled to WordPress, with a schema Spacefast migrates for you, use [Zero](/zero) instead. Both can call `fetch()`. One version declares one runtime. +Functions runs your code as a worker. Use it when you need npm packages or framework output like OpenNext Next.js. If you want collections, tables, and actions compiled to WordPress, with a schema Spacefast migrates for you, use [First](/first) instead. Both can call `fetch()`. One version declares one runtime. Functions is on for every account. diff --git a/content/(dynamic)/meta.ts b/content/(dynamic)/meta.ts index b4ca900d..f4153553 100644 --- a/content/(dynamic)/meta.ts +++ b/content/(dynamic)/meta.ts @@ -4,5 +4,5 @@ export default defineMeta({ title: "Dynamic", collapsed: false, order: 5, - pages: ["zero", "functions", "database", "environment-variables", "crons", "object-storage", "logs", "wordpress", "zero-migrate"], + pages: ["first", "functions", "database", "environment-variables", "crons", "object-storage", "logs", "wordpress", "zero-migrate"], }); diff --git a/content/(dynamic)/object-storage.mdx b/content/(dynamic)/object-storage.mdx index f691da05..f8b20aee 100644 --- a/content/(dynamic)/object-storage.mdx +++ b/content/(dynamic)/object-storage.mdx @@ -28,9 +28,9 @@ An object is addressed by a bare 32-character hexadecimal id. There is no key an `filename` is presentation only. Path separators, control characters, `.`, and `..` are refused rather than stripped, so the value you send is the value that comes back. -## Uploads in a Zero app +## Uploads in a First app -A [Zero](/zero) app handles its own uploads. Declare a `media()` column for images your content shows, or a `file()` column for private files that download through a signed link valid for five minutes. +A [First](/first) app handles its own uploads. Declare a `media()` column for images your content shows, or a `file()` column for private files that download through a signed link valid for five minutes. ## Upload from the API diff --git a/content/(dynamic)/wordpress.mdx b/content/(dynamic)/wordpress.mdx index ef4e5008..3a2503e6 100644 --- a/content/(dynamic)/wordpress.mdx +++ b/content/(dynamic)/wordpress.mdx @@ -5,7 +5,7 @@ description: Every Space runs a WordPress behind its static files. Run WP-CLI ag Every Space includes a WordPress install behind its published files. Reach it through `sf wp` or its published Abilities. -Your published files serve in front of WordPress, so a static Space never touches it and never pays for it. It is there because it is what makes a Space more than a bucket: [Zero](/zero) apps run on it, [Storage](/object-storage) is its media library, and [Database](/database) is its MySQL. This page covers the two ways to reach that WordPress directly. +Your published files serve in front of WordPress, so a static Space never touches it and never pays for it. It is there because it is what makes a Space more than a bucket: [First](/first) apps run on it, [Storage](/object-storage) is its media library, and [Database](/database) is its MySQL. This page covers the two ways to reach that WordPress directly. ## `sf wp` diff --git a/content/(dynamic)/zero-migrate.mdx b/content/(dynamic)/zero-migrate.mdx index 2a7e21aa..c5ef63c0 100644 --- a/content/(dynamic)/zero-migrate.mdx +++ b/content/(dynamic)/zero-migrate.mdx @@ -1,17 +1,19 @@ --- -title: Migrate from Zero 0.8 -description: Publishing a Zero 0.8 app moves it to the current Zero whenever nothing would be lost. When something would, it publishes as 0.8 and tells you exactly what to change. +title: Zero 0.8 +description: Zero is retired and replaced by First. Existing Zero Spaces keep working, and publishing a Zero app moves it to First whenever nothing would be lost. sidebar: order: 8 --- -Your Zero 0.8 app keeps working. Publish it with a current CLI and it moves to the current Zero, the `zero.config.ts` compiler described in [Dynamic sites with Zero](/zero), whenever nothing would be lost. When something would, it publishes as 0.8, keeps serving, and the receipt lists each blocker. New Spaces start on the current Zero. +Zero is retired. [First](/first) replaces it, and new apps start on First. -A Zero 0.8 app is one built on `@spacefast/zero`: a `capsule()` in `server/index.ts`, declared in `sf.jsonc` as `"runtime": { "kind": "zero" }`. +Your Zero app keeps working. Its Space keeps serving, and you can keep publishing it. Publish it with a current CLI and it moves to First whenever nothing would be lost. When something would, it publishes as Zero 0.8, keeps serving, and the receipt lists each blocker. Zero 0.9 is Zero's last release apart from security fixes, and it exists to move apps to First. + +A Zero app is one built on `@spacefast/zero`: a `capsule()` in `server/index.ts`, declared in `sf.jsonc` as `"runtime": { "kind": "zero" }`. ## Keep working on a 0.8 app -A 0.8 app keeps its own local dev server and database commands. The [`sf zero`](/cli/zero) development commands are for `zero.config.ts` apps, not for this one. The exception is `sf zero migrate app`, which [previews the conversion](#preview-the-conversion). +A 0.8 app keeps its own local dev server and database commands. The [`sf first`](/cli/first) development commands are for `first.config.ts` apps, not for this one. The exception is `sf first migrate app`, which [previews the conversion](#preview-the-conversion). ### Run it locally @@ -67,19 +69,19 @@ The CLI converts the app on your machine and checks the result. Then one of two | Result | What you see | | --- | --- | -| Nothing would be lost | The Space moves to the current Zero, on its existing tables. `version.zero.migration.status` is `migrated` | +| Nothing would be lost | The Space moves to First, on its existing tables. `version.zero.migration.status` is `migrated` | | Something would be lost | The app publishes as 0.8 and keeps serving. `status` is `refused`, and `diagnostics` lists each blocker with its `code`, `file`, `line`, `feature` and `fix` | -Fix what `diagnostics` names and publish again. Nothing about your app changes until a publish migrates it. `waiting_on_runtime` means the app can move but the Space's runtime can't run the current Zero yet. It publishes as 0.8, and the next publish after the runtime update migrates it. +Fix what `diagnostics` names and publish again. Nothing about your app changes until a publish migrates it. `waiting_on_runtime` means the app can move but the Space's runtime can't run First yet. It publishes as 0.8, and the next publish after the runtime update migrates it. ## Preview the conversion To read the converted source before you publish, run the converter yourself. It writes a new project and never touches the Space. ```bash -sf zero migrate app --from zero-0.8 ./old-app --out ./candidate --space spc_123 --dry-run -sf zero migrate app --from zero-0.8 ./old-app --out ./candidate --space spc_123 -sf zero check ./candidate +sf first migrate app --from zero-0.8 ./old-app --out ./candidate --space spc_123 --dry-run +sf first migrate app --from zero-0.8 ./old-app --out ./candidate --space spc_123 +sf first check ./candidate ``` `./old-app` is the 0.8 project root, the directory with `sf.jsonc` and `server/index.ts`. `--out` must be a new directory, and a refusal writes nothing. Pass the Space's ID with `--space` so the converter binds each table to the exact MySQL table the Space already stores it in, or `--fresh` for an app that has no Space yet. Add `--users` when the Space has Space Users turned on but the capsule has no `auth` declaration. @@ -97,9 +99,9 @@ The exit code is `0` for a candidate or a clean dry run, `1` for a refusal, and ## What converts -| Zero 0.8 | Current Zero | +| Zero 0.8 | First | | --- | --- | -| `capsule()` in `server/index.ts` | `defineConfig()` in `zero.config.ts` | +| `capsule()` in `server/index.ts` | `defineConfig()` in `first.config.ts` | | `schema` tables | `table()` entries under `tables`, bound to the existing MySQL tables | | `query()` | An action that reads | | `mutation()` | An action that writes, in one transaction | @@ -111,24 +113,24 @@ The exit code is `0` for a candidate or a clean dry run, `1` for a refusal, and Ported tables keep their storage. IDs stay text in action results, numbers stay doubles, and booleans match both the old `true`/`false` text and the new `1`/`0`. The converter copies no rows and renames no columns. -Every write starts denied. Open `zero.config.ts` and set `access` on each collection, table, and action before you rely on it. +Every write starts denied. Open `first.config.ts` and set `access` on each collection, table, and action before you rely on it. ## What you port by hand The converter refuses these, with a pointer to each: -- **Endpoints.** Rewrite each one as a named action at `POST /wp-json/zero/v1/actions/`, then update its callers. -- **Pages, layouts, and islands.** Rebuild them as [TSX pages](/zero#pages). +- **Endpoints.** Rewrite each one as a named action at `POST /wp-json/first/v1/actions/`, then update its callers. +- **Pages, layouts, and islands.** Rebuild them as [TSX pages](/first#pages). - **Connector calls inside handlers.** -- **Realtime.** Declarations are dropped with a review hint. The current Zero has no live queries, so a client reads again after a write. +- **Realtime.** Declarations are dropped with a review hint. First has no live queries, so a client reads again after a write. - **Pagination, dynamic query builders, and range comparisons on optional fields.** - **npm imports and unknown `ctx` services.** - **Row IDs above JavaScript's safe integer range.** -The converter reads only the server. Your `client/` code uses `@spacefast/zero/client`, which has no counterpart: call the generated client in `zero/client/` from your frontend instead, or move the UI to TSX pages. +The converter reads only the server. Your `client/` code uses `@spacefast/zero/client`, which has no counterpart: call the generated client in `first/client/` from your frontend instead, or move the UI to TSX pages. ## Publishing a hand-ported app -Publish the 0.8 project, not a hand-edited candidate. A Space's data moves with the app: publishing a `zero.config.ts` app to a Space that still runs Zero 0.8 is refused with `zero_migration_required`, because it would leave the posts, users, and sessions behind. Once a publish has migrated the Space, edit and publish the current Zero app as usual. +Publish the 0.8 project, not a hand-edited candidate. A Space's data moves with the app: publishing a `first.config.ts` app to a Space that still runs Zero 0.8 is refused with `zero_migration_required`, because it would leave the posts, users, and sessions behind. Once a publish has migrated the Space, edit and publish the current Zero app as usual. -A project with both `"runtime": { "kind": "zero" }` in `sf.jsonc` and a `zero.config.ts` fails to publish with `zero_mixed_generation`. Delete that block if it has one. +A project with both `"runtime": { "kind": "zero" }` in `sf.jsonc` and a `first.config.ts` fails to publish with `zero_mixed_generation`. Delete that block if it has one. diff --git a/content/(reference)/config-file.mdx b/content/(reference)/config-file.mdx index 3a3d8ee9..64991fee 100644 --- a/content/(reference)/config-file.mdx +++ b/content/(reference)/config-file.mdx @@ -180,7 +180,7 @@ Data location is chosen once, when the Space is created, and never changes. Omit the block for a static site. -A [Zero](/zero) app needs no `runtime` block. A `zero.config.ts` at the project root is the declaration, and a project that has one must not also set `runtime.kind` to `"zero"`: publish refuses it with `zero_mixed_generation`. +A [First](/first) app needs no `runtime` block. A `first.config.ts` at the project root is the declaration, and a project that has one must not also set `runtime.kind` to `"zero"`: publish refuses it with `zero_mixed_generation`. ### `kind: "functions"` diff --git a/content/(reference)/glossary.mdx b/content/(reference)/glossary.mdx index 0ce0f514..f7288018 100644 --- a/content/(reference)/glossary.mdx +++ b/content/(reference)/glossary.mdx @@ -23,9 +23,13 @@ Attaching an anonymous Space (one published with no account) to a real account a The drag-and-drop publish flow at [drop.fast](https://drop.fast): drag a folder, a `.zip`, or a single `index.html` onto the window. No install, no account required. See [Publishing](/publish#publish). +## First + +The compiler that turns one `first.config.ts` (collections, tables, actions, and pages) into a WordPress app, hosted on the Space with its own MySQL database. [Functions](#functions) also provides a database, and [crons](/crons) can call either runtime or static files. See [Dynamic sites with First](/first). + ## Functions -The runtime for shipping a worker alongside your site — npm packages, framework server output, outbound HTTP. The other option, alongside [Zero](#zero), for sites that need more than static files. See [Functions](/functions). +The runtime for shipping a worker alongside your site — npm packages, framework server output, outbound HTTP. The other option, alongside [First](#first), for sites that need more than static files. See [Functions](/functions). ## Grant @@ -49,4 +53,4 @@ An immutable snapshot created when a publish produces a new version. A ready ver ## Zero -The compiler that turns one `zero.config.ts` (collections, tables, actions, and pages) into a WordPress app, hosted on the Space with its own MySQL database. [Functions](#functions) also provides a database, and [crons](/crons) can call either runtime or static files. See [Dynamic sites with Zero](/zero). +The retired runtime that First replaced: a `capsule()` in `server/index.ts`, declared in `sf.jsonc` as `"runtime": { "kind": "zero" }`. Existing Zero Spaces keep serving and publishing. Zero 0.9 is its last release apart from security fixes, and it moves apps to First. See [Zero 0.8](/zero-migrate). diff --git a/content/(serve)/site-pages.mdx b/content/(serve)/site-pages.mdx index c90bf42e..64301250 100644 --- a/content/(serve)/site-pages.mdx +++ b/content/(serve)/site-pages.mdx @@ -198,7 +198,7 @@ _pages/*.html takeovers require Plus or Enterprise. Remove the templates or upgr -Full flag tables for `sf pages pull`, `sf pages validate`, and `sf design generate` are on the [CLI reference](/cli/zero). +Full flag tables for `sf pages pull`, `sf pages validate`, and `sf design generate` are on the [CLI reference](/cli/first). ## What publish does with a bad page diff --git a/content/(serve)/stats.mdx b/content/(serve)/stats.mdx index 7b2ae562..abc359a7 100644 --- a/content/(serve)/stats.mdx +++ b/content/(serve)/stats.mdx @@ -50,7 +50,7 @@ Every read goes straight to the edge on request. Spacefast keeps no copy, becaus sf analytics --space docs --window 30d ``` -`--window` takes `48h`, `7d`, or `30d` and defaults to `7d`. The output prints the window, the granularity, the totals, then the per-day visitors, the top paths, and the series. Full flags and the `--json` shape are on [`sf analytics`](/cli/zero#sf-analytics). +`--window` takes `48h`, `7d`, or `30d` and defaults to `7d`. The output prints the window, the granularity, the totals, then the per-day visitors, the top paths, and the series. Full flags and the `--json` shape are on [`sf analytics`](/cli/first#sf-analytics). ## From the API diff --git a/content/cli/builds.mdx b/content/cli/builds.mdx index 88aac8ca..6ce720a6 100644 --- a/content/cli/builds.mdx +++ b/content/cli/builds.mdx @@ -17,7 +17,7 @@ Build and pack project output. sf build [DIR] ``` -Detects build settings, runs the build, and packs the output into an archive. This command talks to your filesystem, not to a Space. To compile a [Zero](/zero) app locally, use `sf zero build`. +Detects build settings, runs the build, and packs the output into an archive. This command talks to your filesystem, not to a Space. To compile a [First](/first) app locally, use `sf first build`. ### Arguments diff --git a/content/cli/db.mdx b/content/cli/db.mdx index 332d12ce..5734b536 100644 --- a/content/cli/db.mdx +++ b/content/cli/db.mdx @@ -5,13 +5,13 @@ sidebar: order: 22 --- -Open a single-use SQL console on a Space's database, and exercise its scheduled jobs on demand. A [Zero](/zero) app's schema lives in its `zero.config.ts` and migrates on publish. See [Database](/database). +Open a single-use SQL console on a Space's database, and exercise its scheduled jobs on demand. A [First](/first) app's schema lives in its `first.config.ts` and migrates on publish. See [Database](/database). Every command here takes the global flags (`--api-url`, `--profile`, `--token`, `-y`/`--yes`, `--json`) and the space selection flags (`--space`, `-o`/`--team`, `--claim-token`). See [the CLI overview](/cli). There is no connection string anywhere in the CLI or the API. `sf db console` is how you reach the database directly. -An older Zero app that declares `"runtime": { "kind": "zero" }` in `sf.jsonc` also has `sf db`, `sf db list`, `sf db dump`, `sf db export`, and `sf db migrate`. See [Migrate an older Zero app](/zero-migrate#read-and-migrate-its-database). +A Zero 0.8 app that declares `"runtime": { "kind": "zero" }` in `sf.jsonc` also has `sf db`, `sf db list`, `sf db dump`, `sf db export`, and `sf db migrate`. See [Zero 0.8](/zero-migrate#read-and-migrate-its-database). ## sf db console diff --git a/content/cli/zero.mdx b/content/cli/first.mdx similarity index 67% rename from content/cli/zero.mdx rename to content/cli/first.mdx index 2a851691..46835f7c 100644 --- a/content/cli/zero.mdx +++ b/content/cli/first.mdx @@ -1,63 +1,63 @@ --- -title: zero, wp, runtime, pages, design, analytics -description: Start, check, run, and build Zero apps, import a CMS schema, run WP-CLI, see what a Space is serving, and read its traffic +title: first, wp, runtime, pages, design, analytics +description: Start, check, run, and build First apps, import a CMS schema, run WP-CLI, see what a Space is serving, and read its traffic sidebar: order: 25 --- -`sf zero` builds and runs [Zero](/zero) apps: a `zero.config.ts` compiled to WordPress. The rest of this page covers WP-CLI, runtime status, site pages, design tokens, and analytics. +`sf first` builds and runs [First](/first) apps: a `first.config.ts` compiled to WordPress. The rest of this page covers WP-CLI, runtime status, site pages, design tokens, and analytics. Every command here takes the global flags (`--api-url`, `--profile`, `--token`, `-y`/`--yes`, `--json`). `sf wp`, `sf runtime status`, and `sf analytics` also take the space selection flags (`--space`, `-o`/`--team`, `--claim-token`). See [the CLI overview](/cli). -To publish a Zero app, run `sf publish` from its root. See [Dynamic sites with Zero](/zero). +To publish a First app, run `sf publish` from its root. See [Dynamic sites with First](/first). -## sf zero init +## sf first init -Start a Zero app: a starter `zero.config.ts` with one collection, one table, and one action, named after the directory. +Start a First app: a starter `first.config.ts` with one collection, one table, and one action, named after the directory. ```text -sf zero init [name] [-f] +sf first init [name] [-f] ``` | Argument or flag | What it does | | --- | --- | | `[name]` | Directory to create. Omit to initialize the current directory | -| `-f, --force` | Overwrite an existing `zero.config.ts` | +| `-f, --force` | Overwrite an existing `first.config.ts` | -It also adds `.zero/` and `node_modules/` to `.gitignore`. `sf init --runtime zero` writes the same starter. +It also adds `.first/` and `node_modules/` to `.gitignore`. `sf init --runtime first` writes the same starter. ```bash -sf zero init my-app -sf zero init +sf first init my-app +sf first init ``` -## sf zero check +## sf first check -Compile `zero.config.ts` and report diagnostics without writing anything. +Compile `first.config.ts` and report diagnostics without writing anything. ```text -sf zero check [project] [--json] +sf first check [project] [--json] ``` -Each diagnostic names a `file:line:col` and a fix. `--json` prints the diagnostics envelope instead of text. `sf zero check` uses the compiler bundled with the CLI, so it needs no native download. +Each diagnostic names a `file:line:col` and a fix. `--json` prints the diagnostics envelope instead of text. `sf first check` uses the compiler bundled with the CLI, so it needs no native download. ```bash -sf zero check -sf zero check ./app --json +sf first check +sf first check ./app --json ``` -## sf zero dev +## sf first dev Run the app on a local WordPress and rebuild when its source changes. ```text -sf zero dev [project] [--port ] [--no-watch] [--allow-destructive] -sf zero dev stop [project] -sf zero dev status [project] -sf zero dev wp [project] -- +sf first dev [project] [--port ] [--no-watch] [--allow-destructive] +sf first dev stop [project] +sf first dev status [project] +sf first dev wp [project] -- ``` -`sf zero dev` boots WordPress through WordPress Studio, with no Docker. It needs Node 22 or newer, and the first start downloads WordPress and the plugins the app needs. It watches TypeScript, config, and `public/` files. Ctrl-C stops WordPress and keeps the local database for the next start. +`sf first dev` boots WordPress through WordPress Studio, with no Docker. It needs Node 22 or newer, and the first start downloads WordPress and the plugins the app needs. It watches TypeScript, config, and `public/` files. Ctrl-C stops WordPress and keeps the local database for the next start. | Argument or flag | What it does | | --- | --- | @@ -71,73 +71,73 @@ sf zero dev wp [project] -- The login it prints is for local development only. ```bash -sf zero dev -sf zero dev --no-watch -sf zero dev wp -- user application-password create admin local --porcelain -sf zero dev stop +sf first dev +sf first dev --no-watch +sf first dev wp -- user application-password create admin local --porcelain +sf first dev stop ``` -## sf zero build +## sf first build -Compile `zero.config.ts` into WordPress plugins. +Compile `first.config.ts` into WordPress plugins. ```text -sf zero build [project] [--out ] +sf first build [project] [--out ] ``` -It writes the plugins to `.zero/wp-content/`, new migrations to `zero/migrations/`, the typed client to `zero/client/`, and editor types to `zero.gen.ts`. Commit everything except `.zero/`. +It writes the plugins to `.first/wp-content/`, new migrations to `first/_generated/migrations/`, the typed client to `first/client/`, and editor types to `first.gen.ts`. Commit everything except `.first/`. ```bash -sf zero build -sf zero build ./app --out ./dist +sf first build +sf first build ./app --out ./dist ``` -`sf zero dev` and `sf zero build` run the native compiler from the CLI's optional platform package. If it's missing, reinstall the CLI with optional dependencies enabled. Set `SPACEFAST_ZERO_BIN` to a `zero` binary to use your own build instead. +`sf first dev` and `sf first build` run the native compiler from the CLI's optional platform package. If it's missing, reinstall the CLI with optional dependencies enabled. Set `SPACEFAST_FIRST_BIN` to a `first` binary to use your own build instead. -## sf zero import +## sf first import -Translate a Payload, EmDash, or Sanity schema into a new `zero.config.ts`, check that it compiles, and print what mapped and what did not. Fully local, no space flags. +Translate a Payload, EmDash, or Sanity schema into a new `first.config.ts`, check that it compiles, and print what mapped and what did not. Fully local, no space flags. ```text -sf zero import [--output ] +sf first import [--output ] ``` | Argument or flag | What it does | | --- | --- | | `` | `payload`, `emdash`, or `sanity`. `payloadcms` is accepted for `payload` | | `` | The schema file, or the project directory to find it in (`payload.config.ts`, `.emdash/seed.json`, `sanity.config.ts`) | -| `--output=` | Config file to write. Defaults to `zero.config.ts`, and it must not exist yet | +| `--output=` | Config file to write. Defaults to `first.config.ts`, and it must not exist yet | -It never runs code from the other CMS and never overwrites a file. Anything Zero cannot hold refuses the whole import, and nothing is written. Every write rule in the result starts as denied, so set `access` before you publish. +It never runs code from the other CMS and never overwrites a file. Anything First cannot hold refuses the whole import, and nothing is written. Every write rule in the result starts as denied, so set `access` before you publish. ```bash -sf zero import payload ../my-payload-app -sf zero import emdash ../site --output app/zero.config.ts +sf first import payload ../my-payload-app +sf first import emdash ../site --output app/first.config.ts ``` ### Output One line per schema entry: its outcome, its JSON pointer, and the reason. With `--json`, `data` carries `source`, `from`, `out`, and `report.entries[]`. -## sf zero migrate +## sf first migrate -Bring a schema, a site's content, or an older Zero app into a `zero.config.ts` project. Everything after `migrate` passes to the Zero compiler unchanged. +Bring a schema, a site's content, or a Zero 0.8 app into a `first.config.ts` project. Everything after `migrate` passes to the First compiler unchanged. ```text -sf zero migrate config --from --out -sf zero migrate site [--dry-run] -sf zero migrate app ... +sf first migrate config --from --out +sf first migrate site [--dry-run] +sf first migrate app ... ``` | Subcommand | What it does | | --- | --- | -| `config` | The same schema import as [`sf zero import`](#sf-zero-import), with its own flags | -| `site` | Moves an older Zero site's content into the app's collections on a standalone WordPress, through WP-CLI. It does not touch a Space. `--dry-run` previews it. Needs the native compiler, like `sf zero build` | -| `app` | Converts an older Zero app's source. See [Migrate an older Zero app](/zero-migrate) | +| `config` | The same schema import as [`sf first import`](#sf-first-import), with its own flags | +| `site` | Moves a Zero 0.8 site's content into the app's collections on a standalone WordPress, through WP-CLI. It does not touch a Space. `--dry-run` previews it. Needs the native compiler, like `sf first build` | +| `app` | Converts a Zero 0.8 app's source. See [Zero 0.8](/zero-migrate) | ```bash -sf zero migrate config --from payload payload.config.ts --out app/zero.config.ts -sf zero migrate site content-model.php --dry-run +sf first migrate config --from payload payload.config.ts --out app/first.config.ts +sf first migrate site content-model.php --dry-run ``` ## sf wp diff --git a/content/cli/meta.ts b/content/cli/meta.ts index 8d9eb558..c6dea7b7 100644 --- a/content/cli/meta.ts +++ b/content/cli/meta.ts @@ -16,7 +16,7 @@ export default defineMeta({ "storage", "git", "source", - "zero", + "first", "share", "teams", "api-keys", diff --git a/content/cli/project.mdx b/content/cli/project.mdx index d6fb435d..fab4a709 100644 --- a/content/cli/project.mdx +++ b/content/cli/project.mdx @@ -16,7 +16,7 @@ Every command here also takes the [global flags](/cli#global-flags). Create a Spacefast project. ```text -sf init [NAME] [--space ] [-o ] [-f] [--runtime static|zero|functions] +sf init [NAME] [--space ] [-o ] [-f] [--runtime static|first|functions] ``` ### Arguments @@ -30,7 +30,7 @@ sf init [NAME] [--space ] [-o ] [-f] [--runtime static|zero|functio | Flag | Default | What it does | | --- | --- | --- | | `-f, --force` | `false` | Overwrite existing generated files. | -| `--runtime=` | `static` | Project to scaffold. `zero` writes a starter [Zero](/zero) `zero.config.ts`, the same one `sf zero init` writes. | +| `--runtime=` | `static` | Project to scaffold. `first` writes a starter [First](/first) `first.config.ts`, the same one `sf first init` writes. | | `--space=` | none | Space ID, live URL, domain, or slug to link the directory to. Only with `--runtime static`. Env `SPACEFAST_SPACE`. | | `-o, --team=` | default team | Team slug, ID, or name. Env `SPACEFAST_TEAM`. | | `--claim-token=` | none | Space key (`sfc_…`) for an unclaimed anonymous Space. Env `SPACEFAST_CLAIM_TOKEN`. | @@ -41,7 +41,7 @@ sf init [NAME] [--space ] [-o ] [-f] [--runtime static|zero|functio sf init sf init my-site sf init --space docs -sf init my-app --runtime zero +sf init my-app --runtime first ``` ### What it writes diff --git a/content/cli/publish.mdx b/content/cli/publish.mdx index cdd183a0..409faa2c 100644 --- a/content/cli/publish.mdx +++ b/content/cli/publish.mdx @@ -295,7 +295,7 @@ Start the local dev server. sf dev ``` -In a Pages project, `sf dev` serves a Pages preview with sample data and the publish-time expander at `/_spacefast/pages/`. It is not a local mirror of what publish serves, and there is no static file server for your site on that path. For a [Zero](/zero) app, run `sf zero dev`. An older Zero app that declares `"runtime": { "kind": "zero" }` in `sf.jsonc` gets its own Zero 0.8 dev server from `sf dev` instead of a Pages preview. See [Migrate an older Zero app](/zero-migrate#keep-working-on-a-08-app). For a framework project, use that framework's own dev server (`next dev`, `vite`), then run `sf publish` when you're ready. +In a Pages project, `sf dev` serves a Pages preview with sample data and the publish-time expander at `/_spacefast/pages/`. It is not a local mirror of what publish serves, and there is no static file server for your site on that path. For a [First](/first) app, run `sf first dev`. A Zero 0.8 app, one that declares `"runtime": { "kind": "zero" }` in `sf.jsonc`, gets its own Zero 0.8 dev server from `sf dev` instead of a Pages preview. See [Zero 0.8](/zero-migrate#keep-working-on-a-08-app). For a framework project, use that framework's own dev server (`next dev`, `vite`), then run `sf publish` when you're ready. | Flag | Default | What it does | | --- | --- | --- | diff --git a/content/index.mdx b/content/index.mdx index 40ccab2f..3a00d3f7 100644 --- a/content/index.mdx +++ b/content/index.mdx @@ -41,7 +41,7 @@ Publish this folder to Spacefast and give me the live URL. A **Space** holds a site and its **versions**, immutable snapshots with their own URLs. A publish with no changes can reuse the current version. The `live` channel points at the version visitors see, or is empty before the first publish. Promoting or rolling back to a retained, ready version moves that pointer without rebuilding. Preview publishes and manual promotion policies leave `live` alone. (New terms piling up? See the [glossary](/glossary).) -Static output is the default path. Point `sf publish` at a build directory and it uploads only the files that changed. If you'd rather publish source, Spacefast detects the framework and builds it. Sites that need a database, server functions, or scheduled jobs add a [Zero](/zero) `zero.config.ts` or a [Functions](/functions) worker. +Static output is the default path. Point `sf publish` at a build directory and it uploads only the files that changed. If you'd rather publish source, Spacefast detects the framework and builds it. Sites that need a database, server functions, or scheduled jobs add a [First](/first) `first.config.ts` or a [Functions](/functions) worker. In a self-serve account, your team owns the Spaces you claim, even when you are its only member. On [Plus](/billing#seats), you can add members who publish and roll back without sharing your login. Free and Go allow only the owner. Team roles control who can manage domains, credentials, and billing. See [Teams and members](/teams) to invite people and choose how new Spaces are shared. diff --git a/content/infrastructure/server-specs-settings.mdx b/content/infrastructure/server-specs-settings.mdx index 9184891a..7e3c5538 100644 --- a/content/infrastructure/server-specs-settings.mdx +++ b/content/infrastructure/server-specs-settings.mdx @@ -25,7 +25,7 @@ For application access to WordPress, use the documented [WordPress commands and | --- | --- | | Redirects, rewrites, and response headers | Publish `_redirects`, `_headers`, or the supported [routing configuration](/routing). | | Application environment variables | Use [Environment variables](/environment-variables). | -| Runtime configuration | Use the documented [Functions runtime settings](/functions) or [Zero app configuration](/zero). | +| Runtime configuration | Use the documented [Functions runtime settings](/functions) or [First app configuration](/first). | | Storage and resource entitlements | Check [Limits](/limits). | Spacefast rejects authored `.htaccess` and `.user.ini` files. You cannot replace the managed server configuration or `php.ini` through a publish. Use the supported configuration for the behavior you need. diff --git a/content/quickstart.mdx b/content/quickstart.mdx index a5b4f63d..4fe04f55 100644 --- a/content/quickstart.mdx +++ b/content/quickstart.mdx @@ -107,8 +107,8 @@ Working with other people? If your Space is still anonymous, [claim it first](/a Push to a remote or connect GitHub and let Spacefast build. - - Add collections, tables, and actions with Zero. + + Add collections, tables, and actions with First. `CI=1` switches the CLI to JSON output and no prompts. diff --git a/evals.yaml b/evals.yaml index f31f7358..3108de72 100644 --- a/evals.yaml +++ b/evals.yaml @@ -68,10 +68,10 @@ questions: question: What is the project config file called? expected: ["sf.jsonc"] routes: /config-file - - id: zero-declare - question: How do I turn on the Zero runtime for a project? - expected: ["a zero.config.ts at the project root", "no runtime block in sf.jsonc"] - routes: /zero + - id: first-declare + question: How do I turn on the First runtime for a project? + expected: ["a first.config.ts at the project root", "no runtime block in sf.jsonc"] + routes: /first - id: routing-precedence question: If both a _redirects file and sf.jsonc rules match a path, which wins? expected: ["_redirects"] diff --git a/redirects.ts b/redirects.ts index a2c0ac48..7ec50fff 100644 --- a/redirects.ts +++ b/redirects.ts @@ -61,10 +61,11 @@ export const redirects: { from: string; to: string; status: 301 }[] = [ { from: "/recipes/preview-channels", to: "/versions", status: 301 }, { from: "/recipes/redirects", to: "/routing", status: 301 }, { from: "/redirects", to: "/routing", status: 301 }, - { from: "/runtimes", to: "/zero", status: 301 }, + { from: "/runtimes", to: "/first", status: 301 }, { from: "/runtimes/functions", to: "/functions", status: 301 }, - { from: "/runtimes/zero", to: "/zero", status: 301 }, + { from: "/runtimes/zero", to: "/first", status: 301 }, { from: "/sdk", to: "/api/sdk", status: 301 }, + { from: "/zero", to: "/first", status: 301 }, { from: "/spaces/rename", to: "/spaces", status: 301 }, { from: "/versions-and-rollback", to: "/versions", status: 301 }, { from: "/workflow/agent-auth", to: "/agents/permissions", status: 301 }, diff --git a/scripts/docs-experience.test.mjs b/scripts/docs-experience.test.mjs index dfbecd88..c48f143e 100644 --- a/scripts/docs-experience.test.mjs +++ b/scripts/docs-experience.test.mjs @@ -60,19 +60,19 @@ test("unfamiliar homepage terms have a linked glossary with definitions", async const home = await builtPage("/"); const glossary = await builtPage("/glossary"); assert.match(home, /\[glossary\]\(\/docs\/glossary\/\)/iu); - for (const term of ["Ability", "Grant", "Live", "Space", "Version", "Zero"]) { + for (const term of ["Ability", "First", "Grant", "Live", "Space", "Version", "Zero"]) { assert.match(glossary, new RegExp(`^## ${term}(?:\\b| \\()`, "mu"), `${term} needs a definition`); } }); -test("Database gives the Zero prerequisite before query instructions", async () => { +test("Database gives the First prerequisite before query instructions", async () => { const database = await builtPage("/database"); const prerequisite = database.indexOf("Don't have a database yet?"); const query = database.indexOf("## Query it from code"); assert.ok(prerequisite >= 0 && query > prerequisite); - assert.match(database.slice(prerequisite, query), /\[Zero\]\(\/docs\/zero\/\)/u); - assert.match(database.slice(prerequisite, query), /sf init --runtime zero/u); - assert.match(database.slice(prerequisite, query), /`zero\.config\.ts`/u); + assert.match(database.slice(prerequisite, query), /\[First\]\(\/docs\/first\/\)/u); + assert.match(database.slice(prerequisite, query), /sf init --runtime first/u); + assert.match(database.slice(prerequisite, query), /`first\.config\.ts`/u); }); test("Troubleshooting gives a first diagnostic step before the error catalog", async () => { diff --git a/scripts/redirect-rules.mjs b/scripts/redirect-rules.mjs index ac39d7e1..b76197bd 100644 --- a/scripts/redirect-rules.mjs +++ b/scripts/redirect-rules.mjs @@ -23,12 +23,12 @@ const authoredRedirectRules = [ { from: `${deploymentBase}/apps/zero`, status: 301, - to: `${deploymentBase}/zero/`, + to: `${deploymentBase}/first/`, }, { from: `${deploymentBase}/zero-runtime`, status: 301, - to: `${deploymentBase}/zero/`, + to: `${deploymentBase}/first/`, }, { from: `${deploymentBase}/storage`, diff --git a/scripts/rename-to-first.mjs b/scripts/rename-to-first.mjs new file mode 100644 index 00000000..9ef64dea --- /dev/null +++ b/scripts/rename-to-first.mjs @@ -0,0 +1,203 @@ +#!/usr/bin/env node +// Renames Zero v2 to First across the authored docs. Zero 0.8 keeps the name +// "Zero". Idempotent: running it again on its own output changes nothing, so +// an open branch takes the rename by merging main and running it once more. +// +// Generated snapshots (generated/**) are producer-owned and change only when +// the exporter re-runs against a renamed CLI and API. +import { readFile, readdir, rename, stat, writeFile } from "node:fs/promises"; +import path from "node:path"; + +const root = process.cwd(); + +// 1. File moves. The v2 page and the CLI page become /first and /cli/first. +// zero-migrate stays: it is the page about Zero 0.8. +const moves = [ + ["content/(dynamic)/zero.mdx", "content/(dynamic)/first.mdx"], + ["content/cli/zero.mdx", "content/cli/first.mdx"], +]; + +// 2. Names that must survive the text pass untouched, because Zero 0.8 or the +// production API and engine already ship them. Each is swapped for a token +// before the replacements and restored after. +const kept = [ + // Zero 0.8: its runtime declaration, packages, converter flag and page. + '"runtime": { "kind": "zero" }', + 'runtime: { kind: "zero"', + '`runtime.kind` to `"zero"`', + "@spacefast/zero/client", + "@spacefast/zero", + "--from zero-0.8", + "/zero-migrate", + "zero-migrate", + "Zero 0.8", + "Zero 0.9", + "older Zero app", + "older Zero site", + // Copy that names Zero as the retired runtime, written by hand after the + // first run; protected so a re-run leaves it alone. + "formerly Zero v2", + "Have a Zero app", + "Zero is retired", + "## Zero\n", + "Existing Zero Spaces", + "Zero's last release", + "Your Zero app", + "A Zero app is one", + "publishing a Zero app", + // Shipped in production: API operations and paths, Abilities, storage. + "createSpaceZeroToken", + "listSpaceZeroAbilities", + "listspacezeroabilities", + "/zero/token", + "zero/storage-", + "zero-storage", + "sfz_", + "`ZERO_`", + "version.zero.migration", + // Error codes. They keep their names; the reference pages are generated. + "zero_mixed_generation", + "zero_migration_required", + // Ordinary English and unrelated terms. + "nonzero", + "non-zero", + "zero-dependency", + "zeros", + "zero or more", + "zero or", + "at zero", + "position zero", + "exited zero", + "hit zero", + "a zero ", + "zero operations", + "Zero alerts", + "zero 404", +]; + +// 3. Replacements for v2 names, most specific first. Every left side is gone +// after one run, which keeps the script idempotent. +const replacements = [ + // Routes. + ["(/zero)", "(/first)"], + ["(/zero#", "(/first#"], + ['href="/zero"', 'href="/first"'], + ["(/cli/zero)", "(/cli/first)"], + ["(/cli/zero#", "(/cli/first#"], + ['href="/cli/zero"', 'href="/cli/first"'], + ["#sf-zero-", "#sf-first-"], + // Files, folders and wire names of a First app. + ["zero.config.ts", "first.config.ts"], + ["zero.gen.ts", "first.gen.ts"], + [".zero/", ".first/"], + ["zero/_generated/", "first/_generated/"], + ["zero/migrations/", "first/_generated/migrations/"], + ["zero/client/", "first/client/"], + ["/wp-json/zero/v1", "/wp-json/first/v1"], + ["zero-identity", "first-identity"], + ["zero-signature", "first-signature"], + ["SPACEFAST_ZERO_BIN", "SPACEFAST_FIRST_BIN"], + ['from "zero"', 'from "first"'], + ['`"zero"` module', '`"first"` module'], + ['Imports can be `"zero"`', 'Imports can be `"first"`'], + ["const zero = createClient", "const first = createClient"], + ["await zero.", "await first."], + ["`zero.shoppingList`", "`first.shoppingList`"], + ["a `zero` binary", "a `first` binary"], + // Commands. + ["sf zero ", "sf first "], + ["sf zero`", "sf first`"], + ["sf zero\n", "sf first\n"], + ["--runtime zero", "--runtime first"], + ["static|zero|functions", "static|first|functions"], + ["static\\|zero\\|functions", "static\\|first\\|functions"], + ["`zero` writes a starter", "`first` writes a starter"], + ["Zero compiler", "First compiler"], + // Navigation entries in meta.ts. + ['"zero"', '"first"'], + // Product name in prose, after every 0.8 phrase is protected above. + ["Zero v2", "First"], + ["Zero's", "First's"], + ["Zero", "First"], +]; + +// Authored files only; never generated/, .blume/ or dist/. +// The pricing fixture mirrors the API's pricing document; its labels follow the API. +const roots = ["content", "README.md", "evals.yaml", "redirects.ts"]; +const textFile = /\.(mdx?|ts|mjs|json|ya?ml)$/u; + +async function exists(file) { + try { + await stat(file); + return true; + } catch { + return false; + } +} + +// Build overlays materialized from generated/ (see AGENTS.md); never authored. +const overlays = [ + "content/cli/reference.md", + "content/(reference)/errors", + "content/(reference)/changelog", + "content/setup", + "content/_pricing", +]; + +async function files(entry) { + const full = path.join(root, entry); + const info = await stat(full); + if (info.isFile()) return [full]; + const out = []; + for (const child of await readdir(full, { withFileTypes: true })) { + const childPath = path.join(entry, child.name); + if (overlays.includes(childPath)) continue; + if (child.isDirectory()) out.push(...(await files(childPath))); + else if (textFile.test(child.name)) out.push(path.join(root, childPath)); + } + return out; +} + +function renameText(source, file) { + // The Zero 0.8 page names the old runtime throughout; only its links to the + // First pages and First's own names change there. + const zero08Page = file.endsWith(path.join("(dynamic)", "zero-migrate.mdx")); + const tokens = []; + let text = source; + const protect = (value) => { + text = text.split(value).join(`\u0000${tokens.length}\u0000`); + tokens.push(value); + }; + for (const value of kept) protect(value); + if (zero08Page) { + for (const value of ["Zero app", "Zero apps", "a Zero", "Zero's", "Zero 0.9", "Zero."]) protect(value); + } + for (const [from, to] of replacements) { + if (zero08Page && (from === "Zero" || from === "Zero's")) continue; + text = text.split(from).join(to); + } + return text.replace(/\u0000(\d+)\u0000/gu, (_, index) => tokens[Number(index)]); +} + +let moved = 0; +for (const [from, to] of moves) { + if (await exists(path.join(root, from))) { + await rename(path.join(root, from), path.join(root, to)); + moved += 1; + } +} + +let changed = 0; +for (const entry of roots) { + if (!(await exists(path.join(root, entry)))) continue; + for (const file of await files(entry)) { + const before = await readFile(file, "utf8"); + const after = renameText(before, file); + if (after !== before) { + await writeFile(file, after); + changed += 1; + } + } +} + +console.log(`rename-to-first: moved ${moved} file(s), rewrote ${changed} file(s).`); diff --git a/scripts/verify-routes.mjs b/scripts/verify-routes.mjs index 526130d5..fb2071f5 100644 --- a/scripts/verify-routes.mjs +++ b/scripts/verify-routes.mjs @@ -149,7 +149,7 @@ const representativePages = [ ["errors/index.html", `${docsRoot}/errors`, "Error reference"], ["errors/rate_limited/index.html", `${docsRoot}/errors/rate_limited`, "rate_limited"], ["recipes/vite/index.html", `${docsRoot}/recipes/vite`, "Vite"], - ["zero/index.html", `${docsRoot}/zero`, "What Zero is"], + ["first/index.html", `${docsRoot}/first`, "What First is"], ]; for (const page of representativePages) { await requirePage(...page); From f5e90b77431718860c654893bd0547c40700a2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Mon, 9 Nov 2026 04:49:23 +0100 Subject: [PATCH 2/2] Keep /zero for Zero 0.8 and add no redirects No First app exists in production, so nothing documents First at a published URL today. /docs/zero now documents Zero 0.8 (the page moves from /zero-migrate, which was never published), First is at /first, and the redirect rules are unchanged from the base branch. --- content/(dynamic)/first.mdx | 2 +- content/(dynamic)/meta.ts | 2 +- .../(dynamic)/{zero-migrate.mdx => zero.mdx} | 0 content/(reference)/glossary.mdx | 2 +- content/cli/db.mdx | 2 +- content/cli/first.mdx | 2 +- content/cli/publish.mdx | 2 +- redirects.ts | 5 ++-- scripts/redirect-rules.mjs | 4 +-- scripts/rename-to-first.mjs | 30 ++++++++++++------- 10 files changed, 30 insertions(+), 21 deletions(-) rename content/(dynamic)/{zero-migrate.mdx => zero.mdx} (100%) diff --git a/content/(dynamic)/first.mdx b/content/(dynamic)/first.mdx index e0a6cbb7..67c58098 100644 --- a/content/(dynamic)/first.mdx +++ b/content/(dynamic)/first.mdx @@ -254,7 +254,7 @@ sf first import emdash ../site --output app/first.config.ts `sf first import` reads a Payload, EmDash, or Sanity schema and writes a new `first.config.ts`. It never runs code from the other CMS and never overwrites a file. Anything First cannot hold refuses the import, and nothing is written. Every write rule starts as denied, so set `access` before you publish. -Have a Zero app on Spacefast, one with `"runtime": { "kind": "zero" }` in `sf.jsonc`? Zero is retired. See [Zero 0.8](/zero-migrate). +Have a Zero app on Spacefast, one with `"runtime": { "kind": "zero" }` in `sf.jsonc`? Zero is retired. See [Zero 0.8](/zero). diff --git a/content/(dynamic)/meta.ts b/content/(dynamic)/meta.ts index f4153553..acbafeb0 100644 --- a/content/(dynamic)/meta.ts +++ b/content/(dynamic)/meta.ts @@ -4,5 +4,5 @@ export default defineMeta({ title: "Dynamic", collapsed: false, order: 5, - pages: ["first", "functions", "database", "environment-variables", "crons", "object-storage", "logs", "wordpress", "zero-migrate"], + pages: ["first", "functions", "database", "environment-variables", "crons", "object-storage", "logs", "wordpress", "zero"], }); diff --git a/content/(dynamic)/zero-migrate.mdx b/content/(dynamic)/zero.mdx similarity index 100% rename from content/(dynamic)/zero-migrate.mdx rename to content/(dynamic)/zero.mdx diff --git a/content/(reference)/glossary.mdx b/content/(reference)/glossary.mdx index f7288018..35c0308d 100644 --- a/content/(reference)/glossary.mdx +++ b/content/(reference)/glossary.mdx @@ -53,4 +53,4 @@ An immutable snapshot created when a publish produces a new version. A ready ver ## Zero -The retired runtime that First replaced: a `capsule()` in `server/index.ts`, declared in `sf.jsonc` as `"runtime": { "kind": "zero" }`. Existing Zero Spaces keep serving and publishing. Zero 0.9 is its last release apart from security fixes, and it moves apps to First. See [Zero 0.8](/zero-migrate). +The retired runtime that First replaced: a `capsule()` in `server/index.ts`, declared in `sf.jsonc` as `"runtime": { "kind": "zero" }`. Existing Zero Spaces keep serving and publishing. Zero 0.9 is its last release apart from security fixes, and it moves apps to First. See [Zero 0.8](/zero). diff --git a/content/cli/db.mdx b/content/cli/db.mdx index 5734b536..15f1c293 100644 --- a/content/cli/db.mdx +++ b/content/cli/db.mdx @@ -11,7 +11,7 @@ Every command here takes the global flags (`--api-url`, `--profile`, `--token`, There is no connection string anywhere in the CLI or the API. `sf db console` is how you reach the database directly. -A Zero 0.8 app that declares `"runtime": { "kind": "zero" }` in `sf.jsonc` also has `sf db`, `sf db list`, `sf db dump`, `sf db export`, and `sf db migrate`. See [Zero 0.8](/zero-migrate#read-and-migrate-its-database). +A Zero 0.8 app that declares `"runtime": { "kind": "zero" }` in `sf.jsonc` also has `sf db`, `sf db list`, `sf db dump`, `sf db export`, and `sf db migrate`. See [Zero 0.8](/zero#read-and-migrate-its-database). ## sf db console diff --git a/content/cli/first.mdx b/content/cli/first.mdx index 46835f7c..b5563bd3 100644 --- a/content/cli/first.mdx +++ b/content/cli/first.mdx @@ -133,7 +133,7 @@ sf first migrate app ... | --- | --- | | `config` | The same schema import as [`sf first import`](#sf-first-import), with its own flags | | `site` | Moves a Zero 0.8 site's content into the app's collections on a standalone WordPress, through WP-CLI. It does not touch a Space. `--dry-run` previews it. Needs the native compiler, like `sf first build` | -| `app` | Converts a Zero 0.8 app's source. See [Zero 0.8](/zero-migrate) | +| `app` | Converts a Zero 0.8 app's source. See [Zero 0.8](/zero) | ```bash sf first migrate config --from payload payload.config.ts --out app/first.config.ts diff --git a/content/cli/publish.mdx b/content/cli/publish.mdx index 409faa2c..37249ca3 100644 --- a/content/cli/publish.mdx +++ b/content/cli/publish.mdx @@ -295,7 +295,7 @@ Start the local dev server. sf dev ``` -In a Pages project, `sf dev` serves a Pages preview with sample data and the publish-time expander at `/_spacefast/pages/`. It is not a local mirror of what publish serves, and there is no static file server for your site on that path. For a [First](/first) app, run `sf first dev`. A Zero 0.8 app, one that declares `"runtime": { "kind": "zero" }` in `sf.jsonc`, gets its own Zero 0.8 dev server from `sf dev` instead of a Pages preview. See [Zero 0.8](/zero-migrate#keep-working-on-a-08-app). For a framework project, use that framework's own dev server (`next dev`, `vite`), then run `sf publish` when you're ready. +In a Pages project, `sf dev` serves a Pages preview with sample data and the publish-time expander at `/_spacefast/pages/`. It is not a local mirror of what publish serves, and there is no static file server for your site on that path. For a [First](/first) app, run `sf first dev`. A Zero 0.8 app, one that declares `"runtime": { "kind": "zero" }` in `sf.jsonc`, gets its own Zero 0.8 dev server from `sf dev` instead of a Pages preview. See [Zero 0.8](/zero#keep-working-on-a-08-app). For a framework project, use that framework's own dev server (`next dev`, `vite`), then run `sf publish` when you're ready. | Flag | Default | What it does | | --- | --- | --- | diff --git a/redirects.ts b/redirects.ts index 7ec50fff..a2c0ac48 100644 --- a/redirects.ts +++ b/redirects.ts @@ -61,11 +61,10 @@ export const redirects: { from: string; to: string; status: 301 }[] = [ { from: "/recipes/preview-channels", to: "/versions", status: 301 }, { from: "/recipes/redirects", to: "/routing", status: 301 }, { from: "/redirects", to: "/routing", status: 301 }, - { from: "/runtimes", to: "/first", status: 301 }, + { from: "/runtimes", to: "/zero", status: 301 }, { from: "/runtimes/functions", to: "/functions", status: 301 }, - { from: "/runtimes/zero", to: "/first", status: 301 }, + { from: "/runtimes/zero", to: "/zero", status: 301 }, { from: "/sdk", to: "/api/sdk", status: 301 }, - { from: "/zero", to: "/first", status: 301 }, { from: "/spaces/rename", to: "/spaces", status: 301 }, { from: "/versions-and-rollback", to: "/versions", status: 301 }, { from: "/workflow/agent-auth", to: "/agents/permissions", status: 301 }, diff --git a/scripts/redirect-rules.mjs b/scripts/redirect-rules.mjs index b76197bd..ac39d7e1 100644 --- a/scripts/redirect-rules.mjs +++ b/scripts/redirect-rules.mjs @@ -23,12 +23,12 @@ const authoredRedirectRules = [ { from: `${deploymentBase}/apps/zero`, status: 301, - to: `${deploymentBase}/first/`, + to: `${deploymentBase}/zero/`, }, { from: `${deploymentBase}/zero-runtime`, status: 301, - to: `${deploymentBase}/first/`, + to: `${deploymentBase}/zero/`, }, { from: `${deploymentBase}/storage`, diff --git a/scripts/rename-to-first.mjs b/scripts/rename-to-first.mjs index 9ef64dea..94bcfbc1 100644 --- a/scripts/rename-to-first.mjs +++ b/scripts/rename-to-first.mjs @@ -10,12 +10,15 @@ import path from "node:path"; const root = process.cwd(); -// 1. File moves. The v2 page and the CLI page become /first and /cli/first. -// zero-migrate stays: it is the page about Zero 0.8. +// 1. File moves, in order. The v2 page and its CLI page become /first and +// /cli/first; then the Zero 0.8 page takes over /zero, which documents Zero. +// A move runs only while its source exists and its target does not. const moves = [ ["content/(dynamic)/zero.mdx", "content/(dynamic)/first.mdx"], ["content/cli/zero.mdx", "content/cli/first.mdx"], + ["content/(dynamic)/zero-migrate.mdx", "content/(dynamic)/zero.mdx"], ]; +const zero08Page = path.join("content", "(dynamic)", "zero.mdx"); // 2. Names that must survive the text pass untouched, because Zero 0.8 or the // production API and engine already ship them. Each is swapped for a token @@ -28,8 +31,8 @@ const kept = [ "@spacefast/zero/client", "@spacefast/zero", "--from zero-0.8", - "/zero-migrate", - "zero-migrate", + // Links to the Zero 0.8 page at /zero; longer entries before shorter ones. + "Zero 0.8](/zero", "Zero 0.8", "Zero 0.9", "older Zero app", @@ -86,6 +89,10 @@ const replacements = [ ["(/cli/zero#", "(/cli/first#"], ['href="/cli/zero"', 'href="/cli/first"'], ["#sf-zero-", "#sf-first-"], + // Navigation entries in meta.ts. + ['["zero", "functions"', '["first", "functions"'], + ['"wordpress", "zero-migrate"]', '"wordpress", "zero"]'], + [' "zero",\n', ' "first",\n'], // Files, folders and wire names of a First app. ["zero.config.ts", "first.config.ts"], ["zero.gen.ts", "first.gen.ts"], @@ -113,8 +120,6 @@ const replacements = [ ["static\\|zero\\|functions", "static\\|first\\|functions"], ["`zero` writes a starter", "`first` writes a starter"], ["Zero compiler", "First compiler"], - // Navigation entries in meta.ts. - ['"zero"', '"first"'], // Product name in prose, after every 0.8 phrase is protected above. ["Zero v2", "First"], ["Zero's", "First's"], @@ -158,22 +163,27 @@ async function files(entry) { return out; } +// Links to the Zero 0.8 page, rewritten before any name is protected: the page +// moved from /zero-migrate to /zero, and its title is now "Zero 0.8". +const prepass = [["[Migrate an older Zero app](/zero-migrate", "[Zero 0.8](/zero"]]; + function renameText(source, file) { // The Zero 0.8 page names the old runtime throughout; only its links to the // First pages and First's own names change there. - const zero08Page = file.endsWith(path.join("(dynamic)", "zero-migrate.mdx")); + const isZero08Page = file === path.join(root, zero08Page); const tokens = []; let text = source; + for (const [from, to] of prepass) text = text.split(from).join(to); const protect = (value) => { text = text.split(value).join(`\u0000${tokens.length}\u0000`); tokens.push(value); }; for (const value of kept) protect(value); - if (zero08Page) { + if (isZero08Page) { for (const value of ["Zero app", "Zero apps", "a Zero", "Zero's", "Zero 0.9", "Zero."]) protect(value); } for (const [from, to] of replacements) { - if (zero08Page && (from === "Zero" || from === "Zero's")) continue; + if (isZero08Page && (from === "Zero" || from === "Zero's")) continue; text = text.split(from).join(to); } return text.replace(/\u0000(\d+)\u0000/gu, (_, index) => tokens[Number(index)]); @@ -181,7 +191,7 @@ function renameText(source, file) { let moved = 0; for (const [from, to] of moves) { - if (await exists(path.join(root, from))) { + if ((await exists(path.join(root, from))) && !(await exists(path.join(root, to)))) { await rename(path.join(root, from), path.join(root, to)); moved += 1; }