diff --git a/.changeset/blobs-bucket-name.md b/.changeset/blobs-bucket-name.md new file mode 100644 index 00000000..17298b5b --- /dev/null +++ b/.changeset/blobs-bucket-name.md @@ -0,0 +1,5 @@ +--- +"create-pds": minor +--- + +The `create-pds` CLI now accepts a `--blobs-bucket-name` flag to set the R2 bucket name for blob storage. In interactive mode, users are prompted for a bucket name with `pds-blobs` as the default. The value is substituted into the generated `wrangler.jsonc` so the scaffolded project is ready to use without manual edits. diff --git a/docs/src/content/docs/guides/troubleshoot.md b/docs/src/content/docs/guides/troubleshoot.md index ae3519ca..b642a41a 100644 --- a/docs/src/content/docs/guides/troubleshoot.md +++ b/docs/src/content/docs/guides/troubleshoot.md @@ -132,7 +132,7 @@ The source account is not affected until the PLC rotation lands. **Common causes:** - Not authenticated. Run `pnpm wrangler login`. -- The R2 bucket name is taken (R2 bucket names are global). Edit `wrangler.jsonc` to use a different name. +- The R2 bucket name is taken (R2 bucket names must be unique within an account). Edit `wrangler.jsonc` to use a different name. - The Durable Object migration is missing. The first deploy creates the Durable Object class; subsequent renames or removals need a `migrations` block in `wrangler.jsonc`. See [the wrangler.jsonc reference](/reference/wrangler-config/). ## When all else fails diff --git a/docs/src/content/docs/reference/wrangler-config.md b/docs/src/content/docs/reference/wrangler-config.md index 5b748ee0..73f43b55 100644 --- a/docs/src/content/docs/reference/wrangler-config.md +++ b/docs/src/content/docs/reference/wrangler-config.md @@ -117,7 +117,7 @@ Adding a new migration block is necessary for renaming or removing the class. Fo ] ``` -The `binding` is what the Worker uses (`env.BLOBS`). The `bucket_name` is the R2 bucket — bucket names are global across all of Cloudflare. Pick a unique name. +The `binding` is what the Worker uses (`env.BLOBS`). The `bucket_name` is the R2 bucket which must be unique to your account but (unlike some other blog storage services) do not need to be globally unique. The `create-pds` CLI prompts you for this and will write it to this file, otherwise just provide one here if you're managing the `wrangler.jsonc` manually. The bucket is created on first deploy if it does not exist. diff --git a/packages/create-pds/src/index.ts b/packages/create-pds/src/index.ts index bdcd12fd..da1fdafe 100644 --- a/packages/create-pds/src/index.ts +++ b/packages/create-pds/src/index.ts @@ -147,6 +147,11 @@ const main = defineCommand({ alias: "pm", description: "Package manager to use (npm, yarn, pnpm, bun)", }, + "blobs-bucket-name": { + type: "string", + description: "Cloudflare R2 bucket name for blobs", + required: false, + }, "skip-install": { type: "boolean", description: "Skip installing dependencies", @@ -257,6 +262,25 @@ const main = defineCommand({ initGit = gitResult; } + // Get bucket name + let bucketName = args["blobs-bucket-name"]; + if (!bucketName) { + if (nonInteractive) { + bucketName = "pds-blobs"; + } else { + const result = await p.text({ + message: "Blobs bucket name (must be unique in your Cloudflare account):", + placeholder: "pds-blobs", + defaultValue: "pds-blobs", + }); + if (p.isCancel(result)) { + p.cancel("Cancelled"); + process.exit(0); + } + bucketName = result; + } + } + // Copy template const spinner = p.spinner(); spinner.start("Fetching latest @getcirrus/pds version..."); @@ -274,6 +298,10 @@ const main = defineCommand({ pdsVersion: `^${pdsVersion}`, }); + await replaceInFile(join(targetDir, "wrangler.jsonc"), { + blobsBucketName: bucketName, + }); + spinner.stop("Template copied"); // Initialize git diff --git a/packages/create-pds/templates/pds-worker/wrangler.jsonc b/packages/create-pds/templates/pds-worker/wrangler.jsonc index 6e190d9a..7d62d163 100644 --- a/packages/create-pds/templates/pds-worker/wrangler.jsonc +++ b/packages/create-pds/templates/pds-worker/wrangler.jsonc @@ -41,14 +41,14 @@ ] } ], - // R2 bucket for blob storage (optional - create via Cloudflare dashboard) + // R2 bucket for blob storage // Uploads are staged under `/staged/` until a record references // them. Add a lifecycle rule to expire abandoned uploads, e.g.: - // wrangler r2 bucket lifecycle add pds-blobs --prefix "/staged/" --expire-days 7 + // wrangler r2 bucket lifecycle add --prefix "/staged/" --expire-days 7 "r2_buckets": [ { "binding": "BLOBS", - "bucket_name": "pds-blobs" + "bucket_name": "{{blobsBucketName}}" } ], // Environment variables (public config) diff --git a/packages/create-pds/test/e2e.test.ts b/packages/create-pds/test/e2e.test.ts index 8afe9cde..07aa290b 100644 --- a/packages/create-pds/test/e2e.test.ts +++ b/packages/create-pds/test/e2e.test.ts @@ -80,4 +80,40 @@ describe("create-pds e2e", () => { ); }).toThrow(); }); + + describe("blobs bucket name", () => { + it("uses the default bucket name when none is specified", () => { + const projectName = "bucket-default-pds"; + execSync( + `node ${CLI_PATH} ${projectName} --yes --skip-install --skip-init --skip-git`, + { cwd: TEST_DIR, stdio: "pipe" }, + ); + const wrangler = readFileSync( + join(TEST_DIR, projectName, "wrangler.jsonc"), + "utf-8", + ); + expect(wrangler).toContain('"pds-blobs"'); + }); + + it("uses a custom bucket name from --blobs-bucket-name", () => { + const projectName = "bucket-custom-pds"; + execSync( + `node ${CLI_PATH} ${projectName} --yes --skip-install --skip-init --skip-git --blobs-bucket-name my-custom-bucket`, + { cwd: TEST_DIR, stdio: "pipe" }, + ); + const wrangler = readFileSync( + join(TEST_DIR, projectName, "wrangler.jsonc"), + "utf-8", + ); + expect(wrangler).toContain('"my-custom-bucket"'); + }); + + it("leaves no unreplaced placeholders in wrangler.jsonc", () => { + const wrangler = readFileSync( + join(TEST_DIR, "bucket-default-pds", "wrangler.jsonc"), + "utf-8", + ); + expect(wrangler).not.toContain("{{"); + }); + }); });