Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 4 additions & 2 deletions docs/cli/mint.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ fullsend mint deploy \
--platform cloudflare
```

Use `--preview` for ephemeral test deploys (supports teardown). Use `--worker-name` to target a specific Worker script name.
Use `--preview=<alias>` for ephemeral preview deploys. This runs `wrangler versions upload --preview-alias=<alias>` instead of `wrangler deploy`, so the durable Worker script is not affected. The preview mint URL is deterministic: `https://<alias>-<worker-name>.workers.dev`. Preview teardown abandons the alias without deleting the Worker script.

Use `--worker-name` to target a specific Worker script name.

Required environment variables:
- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account identifier
Expand All @@ -74,7 +76,7 @@ Required environment variables:
| `--source-dir` | | Path to local mint source (default: embedded) |
| `--dry-run` | `false` | Preview changes without making them |
| `--worker-name` | `fullsend-mint` | Cloudflare Worker script name (Cloudflare only) |
| `--preview` | `false` | Deploy as ephemeral preview Worker (Cloudflare only) |
| `--preview` | `""` | Preview alias for `wrangler versions upload` (Cloudflare only). Example: `--preview=bt-run-42` |

### Required IAM roles (GCP)

Expand Down
75 changes: 48 additions & 27 deletions internal/cli/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func newMintDeployCmd() *cobra.Command {

// Cloudflare-specific flags.
var workerName string
var preview bool
var preview string

cmd := &cobra.Command{
Use: "deploy",
Expand Down Expand Up @@ -401,13 +401,20 @@ Cloudflare mode (--platform=cloudflare):
WASM module with a thin TypeScript adapter for I/O.

Required flags: none (Worker name defaults to "fullsend-mint")
Optional: --worker-name, --preview, --source-dir
Optional: --worker-name, --preview=<alias>, --source-dir

Required environment variables:
- CLOUDFLARE_ACCOUNT_ID Cloudflare account identifier
- CLOUDFLARE_API_TOKEN API token with Workers write permission

Use --preview for ephemeral BT test deploys (supports teardown).
Use --preview=<alias> for ephemeral preview deploys. This runs
'wrangler versions upload --preview-alias=<alias>' instead of
'wrangler deploy', so the durable Worker script is not affected.
The preview mint URL is deterministic from the alias and worker name:
https://<alias>-<worker-name>.workers.dev
Callers (e.g. BT) can compute this URL and pass it to
'fullsend github setup --mint-url' or 'fullsend mint enroll'.
Preview teardown abandons the alias without deleting the Worker script.
Use --worker-name to target a specific Worker script name.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -440,7 +447,10 @@ Cloudflare mode (--platform=cloudflare):

// Cloudflare-specific flags.
cmd.Flags().StringVar(&workerName, "worker-name", "", "Cloudflare Worker script name (default: fullsend-mint)")
cmd.Flags().BoolVar(&preview, "preview", false, "deploy as ephemeral preview Worker for testing (Cloudflare only)")
cmd.Flags().StringVar(&preview, "preview", "", `deploy as preview via wrangler versions upload (Cloudflare only)
Value is the preview alias passed to --preview-alias. The preview
mint URL is deterministic: https://<alias>-<worker-name>.workers.dev
Example: --preview=bt-run-42`)

return cmd
}
Expand Down Expand Up @@ -585,7 +595,7 @@ func runMintDeployGCP(ctx context.Context, project, region, sourceDir string, sk
return nil
}

func runMintDeployCloudflare(ctx context.Context, workerName, sourceDir string, preview, dryRun bool) error {
func runMintDeployCloudflare(ctx context.Context, workerName, sourceDir, previewAlias string, dryRun bool) error {
if err := cf.ValidateCloudflareEnv(); err != nil {
return err
}
Expand All @@ -596,6 +606,10 @@ func runMintDeployCloudflare(ctx context.Context, workerName, sourceDir string,
return fmt.Errorf("invalid --worker-name %q: must be 2-63 lowercase alphanumeric characters or hyphens", workerName)
}

if previewAlias != "" && !cf.ValidatePreviewAlias(previewAlias) {
return fmt.Errorf("invalid --preview alias %q: must be 2-63 lowercase alphanumeric characters or hyphens", previewAlias)
}

printer := ui.New(os.Stdout)

printer.Banner(Version())
Expand All @@ -604,26 +618,33 @@ func runMintDeployCloudflare(ctx context.Context, workerName, sourceDir string,
printer.Blank()

deployMode := cf.DeployDurable
if preview {
if previewAlias != "" {
deployMode = cf.DeployPreview
}

effectiveName := workerName
if effectiveName == "" {
effectiveName = "fullsend-mint"
}

if dryRun {
printer.StepInfo("Dry run — no changes will be made")
printer.Blank()
effectiveName := workerName
if effectiveName == "" {
effectiveName = "fullsend-mint (default)"
dryRunName := workerName
if dryRunName == "" {
dryRunName = "fullsend-mint (default)"
}
printer.StepInfo(fmt.Sprintf("Would deploy Worker %s", effectiveName))
printer.StepInfo(fmt.Sprintf("Would deploy Worker %s", dryRunName))
printer.StepInfo(fmt.Sprintf("Account: %s", accountID))
if sourceDir != "" {
printer.StepInfo(fmt.Sprintf("Source directory: %s", sourceDir))
} else {
printer.StepInfo("Source: embedded Worker adapter")
}
if preview {
printer.StepInfo("Mode: preview (ephemeral, supports teardown)")
if previewAlias != "" {
printer.StepInfo(fmt.Sprintf("Mode: preview (alias=%s)", previewAlias))
printer.StepInfo(fmt.Sprintf("Preview URL: https://%s-%s.workers.dev", previewAlias, effectiveName))
printer.StepInfo("Command: wrangler versions upload --preview-alias=" + previewAlias)
} else {
printer.StepInfo("Mode: durable (persistent)")
}
Expand All @@ -635,20 +656,21 @@ func runMintDeployCloudflare(ctx context.Context, workerName, sourceDir string,
}

cfg := cf.Config{
AccountID: accountID,
WorkerName: workerName,
DeployMode: deployMode,
SourceDir: sourceDir,
Version: version,
Commit: commitSHA,
AccountID: accountID,
WorkerName: workerName,
DeployMode: deployMode,
PreviewAlias: previewAlias,
SourceDir: sourceDir,
Version: version,
Commit: commitSHA,
}

wrangler := mintCFWranglerFactory(accountID)
provisioner := cf.NewProvisioner(cfg, wrangler)

modeLabel := "durable"
if preview {
modeLabel = "preview"
if previewAlias != "" {
modeLabel = fmt.Sprintf("preview (alias=%s)", previewAlias)
}
printer.StepStart(fmt.Sprintf("Deploying %s Worker", modeLabel))
result, err := provisioner.Provision(ctx)
Expand All @@ -661,17 +683,16 @@ func runMintDeployCloudflare(ctx context.Context, workerName, sourceDir string,
printer.StepDone(fmt.Sprintf("Worker deployed at %s", mintURL))
printer.Blank()

effectiveName := workerName
if effectiveName == "" {
effectiveName = "fullsend-mint"
}
summaryLines := []string{
fmt.Sprintf("Worker: %s", effectiveName),
fmt.Sprintf("URL: %s", mintURL),
fmt.Sprintf("Mode: %s", modeLabel),
}
if preview {
summaryLines = append(summaryLines, "Teardown: fullsend mint deploy --platform=cloudflare --worker-name="+effectiveName+" --preview (then delete)")
if previewAlias != "" {
summaryLines = append(summaryLines, fmt.Sprintf("Mode: preview (alias=%s)", previewAlias))
summaryLines = append(summaryLines, fmt.Sprintf("Preview URL pattern: https://<alias>-%s.workers.dev", effectiveName))
summaryLines = append(summaryLines, "Teardown: preview alias is abandoned (Worker script is preserved)")
} else {
summaryLines = append(summaryLines, "Mode: durable")
}
printer.Summary("Deployment complete", summaryLines)

Expand Down
40 changes: 31 additions & 9 deletions internal/cli/mint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ type fakeCFWranglerRunner struct {
}

type fakeCFDeployCall struct {
workerName string
workerName string
previewAlias string
}

func (f *fakeCFWranglerRunner) Deploy(_ context.Context, _ string, workerName string, _ bool, _ map[string]string) (string, error) {
f.deployCalls = append(f.deployCalls, fakeCFDeployCall{workerName: workerName})
func (f *fakeCFWranglerRunner) Deploy(_ context.Context, _ string, workerName string, previewAlias string, _ map[string]string) (string, error) {
f.deployCalls = append(f.deployCalls, fakeCFDeployCall{workerName: workerName, previewAlias: previewAlias})
if f.deployErr != nil {
return "", f.deployErr
}
Expand Down Expand Up @@ -254,7 +255,7 @@ func TestMintDeployCmd_CloudflareFlags(t *testing.T) {

previewFlag := cmd.Flags().Lookup("preview")
require.NotNil(t, previewFlag, "expected --preview flag")
assert.Equal(t, "false", previewFlag.DefValue)
assert.Equal(t, "", previewFlag.DefValue)
}

func TestMintDeployCmd_InvalidPlatform(t *testing.T) {
Expand Down Expand Up @@ -332,11 +333,29 @@ func TestMintDeployCmd_CloudflareDryRunPreview(t *testing.T) {
os.Setenv("CLOUDFLARE_API_TOKEN", "test-token")

cmd := newRootCmd()
cmd.SetArgs([]string{"mint", "deploy", "--platform=cloudflare", "--dry-run", "--preview"})
cmd.SetArgs([]string{"mint", "deploy", "--platform=cloudflare", "--dry-run", "--preview=bt-test-42"})
err := cmd.Execute()
require.NoError(t, err)
}

func TestMintDeployCmd_CloudflareDryRunPreviewInvalidAlias(t *testing.T) {
origAccount := os.Getenv("CLOUDFLARE_ACCOUNT_ID")
origToken := os.Getenv("CLOUDFLARE_API_TOKEN")
defer func() {
os.Setenv("CLOUDFLARE_ACCOUNT_ID", origAccount)
os.Setenv("CLOUDFLARE_API_TOKEN", origToken)
}()

os.Setenv("CLOUDFLARE_ACCOUNT_ID", "test-account")
os.Setenv("CLOUDFLARE_API_TOKEN", "test-token")

cmd := newRootCmd()
cmd.SetArgs([]string{"mint", "deploy", "--platform=cloudflare", "--preview=INVALID_ALIAS"})
err := cmd.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid --preview alias")
}

// --- Cloudflare non-dry-run deploy tests ---

// withMintCFWrangler overrides the mintCFWranglerFactory package-level
Expand Down Expand Up @@ -395,19 +414,22 @@ func TestMintDeployCmd_CloudflareDurableDeploy(t *testing.T) {
func TestMintDeployCmd_CloudflarePreviewDeploy(t *testing.T) {
withCFEnvVars(t)
sourceDir := createMinimalWorkerSourceDir(t)
withMintCFWrangler(t, &fakeCFWranglerRunner{
deployURL: "https://fullsend-mint-preview.workers.dev",
})
fake := &fakeCFWranglerRunner{
deployURL: "https://bt-run-42-fullsend-mint.workers.dev",
}
withMintCFWrangler(t, fake)

cmd := newRootCmd()
cmd.SetArgs([]string{
"mint", "deploy",
"--platform=cloudflare",
"--preview",
"--preview=bt-run-42",
"--source-dir=" + sourceDir,
})
err := cmd.Execute()
require.NoError(t, err)
require.Len(t, fake.deployCalls, 1)
assert.Equal(t, "bt-run-42", fake.deployCalls[0].previewAlias)
}

func TestMintDeployCmd_CloudflareCustomWorkerName(t *testing.T) {
Expand Down
Loading
Loading