← back to Cli Printing Press
refactor(cli): use ClaimOutputDir for atomic output directory claiming
da9500e4b2056803b8112fd296cc4f95bc9b6281 · 2026-03-27 19:40:01 -0700 · Trevin Chow
Replace inline collision checks in generate command with claimOrForce helper.
--force claims the exact base slot. --output errors on collision. Default
auto-increments via os.Mkdir for race-safe concurrent generation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit da9500e4b2056803b8112fd296cc4f95bc9b6281
Author: Trevin Chow <trevin@trevinchow.com>
Date: Fri Mar 27 19:40:01 2026 -0700
refactor(cli): use ClaimOutputDir for atomic output directory claiming
Replace inline collision checks in generate command with claimOrForce helper.
--force claims the exact base slot. --output errors on collision. Default
auto-increments via os.Mkdir for race-safe concurrent generation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
internal/cli/root.go | 53 ++++++++++++++++++++++++++++++++++------------------
1 file changed, 35 insertions(+), 18 deletions(-)
diff --git a/internal/cli/root.go b/internal/cli/root.go
index 07fedae2..45fc17b4 100644
--- a/internal/cli/root.go
+++ b/internal/cli/root.go
@@ -118,15 +118,9 @@ func newGenerateCmd() *cobra.Command {
if err != nil {
return fmt.Errorf("resolving output path: %w", err)
}
- if force {
- if err := os.RemoveAll(absOut); err != nil {
- return fmt.Errorf("removing existing output dir: %w", err)
- }
- } else if info, err := os.Stat(absOut); err == nil && info.IsDir() {
- entries, _ := os.ReadDir(absOut)
- if len(entries) > 0 {
- return fmt.Errorf("output directory %s already exists (use --force to overwrite)", absOut)
- }
+ absOut, err = claimOrForce(absOut, force, outputDir != "")
+ if err != nil {
+ return &ExitError{Code: ExitInputError, Err: err}
}
gen := generator.New(parsed, absOut)
@@ -221,15 +215,9 @@ func newGenerateCmd() *cobra.Command {
if dryRun {
return printDryRun(apiSpec, absOut, specFiles)
}
- if force {
- if err := os.RemoveAll(absOut); err != nil {
- return fmt.Errorf("removing existing output dir: %w", err)
- }
- } else if info, err := os.Stat(absOut); err == nil && info.IsDir() {
- entries, _ := os.ReadDir(absOut)
- if len(entries) > 0 {
- return fmt.Errorf("output directory %s already exists (use --force to overwrite)", absOut)
- }
+ absOut, err = claimOrForce(absOut, force, outputDir != "")
+ if err != nil {
+ return &ExitError{Code: ExitInputError, Err: err}
}
gen := generator.New(apiSpec, absOut)
@@ -342,6 +330,35 @@ func mergeSpecs(specs []*spec.APISpec, name string) *spec.APISpec {
return merged
}
+// claimOrForce resolves the output directory based on --force and --output flags.
+//
+// - force=true: RemoveAll the target, then create it fresh (claims exact slot)
+// - explicit output (--output set) without force: error if exists and non-empty
+// - default (no --output, no --force): auto-increment via ClaimOutputDir
+func claimOrForce(absOut string, force bool, explicitOutput bool) (string, error) {
+ if force {
+ if err := os.RemoveAll(absOut); err != nil {
+ return "", fmt.Errorf("removing existing output dir: %w", err)
+ }
+ if err := os.MkdirAll(absOut, 0o755); err != nil {
+ return "", fmt.Errorf("creating output dir: %w", err)
+ }
+ return absOut, nil
+ }
+
+ if explicitOutput {
+ if info, err := os.Stat(absOut); err == nil && info.IsDir() {
+ entries, _ := os.ReadDir(absOut)
+ if len(entries) > 0 {
+ return "", fmt.Errorf("output directory %s already exists (use --force to overwrite)", absOut)
+ }
+ }
+ return absOut, nil
+ }
+
+ return pipeline.ClaimOutputDir(absOut)
+}
+
func fetchOrCacheSpec(specURL string, refresh bool, skipCache bool) ([]byte, error) {
sum := sha256.Sum256([]byte(specURL))
cacheKey := hex.EncodeToString(sum[:])
← 0fd4cba6 test(pipeline): add concurrency test for ClaimOutputDir
·
back to Cli Printing Press
·
fix(pipeline): backfill PlanStatus in migration and persist 3a9e977b →