Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blobs-bucket-name.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion docs/src/content/docs/guides/troubleshoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/wrangler-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
28 changes: 28 additions & 0 deletions packages/create-pds/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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...");
Expand All @@ -274,6 +298,10 @@ const main = defineCommand({
pdsVersion: `^${pdsVersion}`,
});

await replaceInFile(join(targetDir, "wrangler.jsonc"), {
blobsBucketName: bucketName,
});

spinner.stop("Template copied");

// Initialize git
Expand Down
6 changes: 3 additions & 3 deletions packages/create-pds/templates/pds-worker/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@
]
}
],
// R2 bucket for blob storage (optional - create via Cloudflare dashboard)
// R2 bucket for blob storage
// Uploads are staged under `<did>/staged/` until a record references
// them. Add a lifecycle rule to expire abandoned uploads, e.g.:
// wrangler r2 bucket lifecycle add pds-blobs --prefix "<did>/staged/" --expire-days 7
// wrangler r2 bucket lifecycle add <bucket-name> --prefix "<did>/staged/" --expire-days 7
"r2_buckets": [
{
"binding": "BLOBS",
"bucket_name": "pds-blobs"
"bucket_name": "{{blobsBucketName}}"
}
],
// Environment variables (public config)
Expand Down
36 changes: 36 additions & 0 deletions packages/create-pds/test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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("{{");
});
});
});