diff --git a/.changeset/domain-set-primary.md b/.changeset/domain-set-primary.md new file mode 100644 index 00000000..8138ba63 --- /dev/null +++ b/.changeset/domain-set-primary.md @@ -0,0 +1,5 @@ +--- +"@cloudhal/cli": patch +--- + +`cloudhal domain set ` also makes the host the local site's primary domain, so canonical links, the sitemap and feeds use it before the first deploy (#48). diff --git a/packages/cli/README.md b/packages/cli/README.md index eb1d0567..d3209224 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -163,7 +163,7 @@ Locally, `cloudhal studio` reads the project's SQLite (`analytics_events`), roll **From Git (Workers Builds).** With `WORKERS_CI=1`, which Cloudflare sets in [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/), `cloudhal deploy` works from a fresh clone. It takes the names from the committed `wrangler.jsonc`, seeds the starter site when the checkout has none, keeps the secrets on the Workers and does not print the setup link. This is what the Deploy to Cloudflare button runs: see [docs/guides/deploy-button.md](../../docs/guides/deploy-button.md). -**Custom domain.** `cloudhal domain set news.example.com --studio studio.example.com` records the hostnames in `cloudhal.json`; the next `cloudhal deploy` routes them to the Workers as [Cloudflare Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) (the zone must be in the same Cloudflare account — Cloudflare creates the DNS record and certificate) and makes the site's one its primary hostname, so preview links use it (canonical URLs in the starter are not rendered yet). The workers.dev addresses keep working. `cloudhal domain remove` and a deploy undo it. +**Custom domain.** `cloudhal domain set news.example.com --studio studio.example.com` records the hostnames in `cloudhal.json`; the next `cloudhal deploy` routes them to the Workers as [Cloudflare Custom Domains](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/) (the zone must be in the same Cloudflare account — Cloudflare creates the DNS record and certificate) and makes the site's one its primary hostname on the deployment. `domain set` makes it primary in the local store right away, so `cloudhal dev`, `render` and `build` write it into canonical links, the sitemap and feeds before any deploy. The workers.dev addresses keep working. `cloudhal domain remove` and a deploy undo it. **Email (RFC-0011).** Add `"mail": { "from": "studio@example.com", "fromName": "Example Studio" }` to `cloudhal.json` and the next `cloudhal deploy` gives the admin Worker a [Cloudflare Email Service](https://developers.cloudflare.com/email-service/) `send_email` binding locked to that address. Invites can then be emailed, and staff can reset a forgotten password from the sign-in screen. Onboard the address's domain in Email Service first. Sending to any recipient needs Workers Paid (3,000 emails a month included); on Free, Email Service delivers only to verified destination addresses of your account. Locally, `cloudhal studio` prints these emails in the terminal instead of sending them. diff --git a/packages/cli/src/commands/domain.ts b/packages/cli/src/commands/domain.ts index e30bbaf5..10ca6701 100644 --- a/packages/cli/src/commands/domain.ts +++ b/packages/cli/src/commands/domain.ts @@ -2,8 +2,13 @@ * `cloudhal domain` — the custom domains `cloudhal deploy` routes to this project's Workers * (Cloudflare Custom Domains: the zone must be in the same Cloudflare account; wrangler creates * the DNS record and certificate). Kept in cloudhal.json so the setting travels with the project. + * The site's own hostname also becomes its primary domain in the local store, so `cloudhal build` + * writes it into canonical links, the sitemap and feeds before any deploy (#48); `deploy` does the + * same on the deployment. */ -import { isHostname, normalizeHostname } from "@cloudhal/core-ts"; +import path from "node:path"; +import { isHostname, normalizeHostname, withPrimaryDomain } from "@cloudhal/core-ts"; +import { openLocalStore } from "@cloudhal/core-ts/local"; import { type ProjectConfig, readProjectConfig, writeProjectConfig } from "../config.ts"; export type DomainTarget = "site" | "studio"; @@ -40,5 +45,18 @@ export async function domain(options: DomainOptions): Promise d.primary)?.hostname !== hostname) + await local.store.updateSite({ ...site, domains: withPrimaryDomain(site.domains, hostname) }); + } finally { + await local.close(); + } +} diff --git a/packages/cli/test/deploy.test.ts b/packages/cli/test/deploy.test.ts index bf31f646..eb4a259b 100644 --- a/packages/cli/test/deploy.test.ts +++ b/packages/cli/test/deploy.test.ts @@ -494,6 +494,13 @@ describe("cloudhal deploy", () => { site: "news.example.com", studio: "studio.example.com", }); + // #48: the local site takes it as its primary domain before any deploy (`cloudhal build`) + const before = await openLocalStore(path.join(projectDir, cfg.dataDir)); + expect((await before.store.getSite(cfg.siteId))?.domains[0]).toEqual({ + hostname: "news.example.com", + primary: true, + }); + await before.close(); const r = await run(); expect(r).toMatchObject({