← back to Cli Printing Press
feat(cli): accept URLs for --spec with local caching
2e70bc46b259e0b799d5052b306bb52cd489c745 · 2026-03-23 23:55:19 -0700 · Matt Van Horn
printing-press generate --spec https://example.com/openapi.json now works.
Specs are cached at ~/.cache/printing-press/specs/<sha256>.json for 24h.
--refresh flag forces re-download.
Files touched
Diff
commit 2e70bc46b259e0b799d5052b306bb52cd489c745
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Mon Mar 23 23:55:19 2026 -0700
feat(cli): accept URLs for --spec with local caching
printing-press generate --spec https://example.com/openapi.json now works.
Specs are cached at ~/.cache/printing-press/specs/<sha256>.json for 24h.
--refresh flag forces re-download.
---
internal/cli/root.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 75 insertions(+), 3 deletions(-)
diff --git a/internal/cli/root.go b/internal/cli/root.go
index 2bb2f109..66d66767 100644
--- a/internal/cli/root.go
+++ b/internal/cli/root.go
@@ -1,9 +1,15 @@
package cli
import (
+ "crypto/sha256"
+ "encoding/hex"
"fmt"
+ "io"
+ "net/http"
"os"
"path/filepath"
+ "strings"
+ "time"
"github.com/mvanhorn/cli-printing-press/internal/generator"
"github.com/mvanhorn/cli-printing-press/internal/openapi"
@@ -33,6 +39,7 @@ func newGenerateCmd() *cobra.Command {
var specFile string
var outputDir string
var validate bool
+ var refresh bool
cmd := &cobra.Command{
Use: "generate",
@@ -42,9 +49,20 @@ func newGenerateCmd() *cobra.Command {
return fmt.Errorf("--spec is required")
}
- data, err := os.ReadFile(specFile)
- if err != nil {
- return fmt.Errorf("reading spec file: %w", err)
+ var (
+ data []byte
+ err error
+ )
+ if strings.HasPrefix(specFile, "http://") || strings.HasPrefix(specFile, "https://") {
+ data, err = fetchOrCacheSpec(specFile, refresh)
+ if err != nil {
+ return fmt.Errorf("fetching spec from URL: %w", err)
+ }
+ } else {
+ data, err = os.ReadFile(specFile)
+ if err != nil {
+ return fmt.Errorf("reading spec file: %w", err)
+ }
}
var apiSpec *spec.APISpec
@@ -84,10 +102,64 @@ func newGenerateCmd() *cobra.Command {
cmd.Flags().StringVar(&specFile, "spec", "", "Path to API spec (internal YAML or OpenAPI 3.0+)")
cmd.Flags().StringVar(&outputDir, "output", "", "Output directory (default: <name>-cli)")
cmd.Flags().BoolVar(&validate, "validate", true, "Run quality gates on the generated project")
+ cmd.Flags().BoolVar(&refresh, "refresh", false, "Refresh cached remote spec before generating")
return cmd
}
+func fetchOrCacheSpec(specURL string, refresh bool) ([]byte, error) {
+ sum := sha256.Sum256([]byte(specURL))
+ cacheKey := hex.EncodeToString(sum[:])
+
+ homeDir, err := os.UserHomeDir()
+ if err != nil {
+ return nil, fmt.Errorf("finding user home directory: %w", err)
+ }
+
+ cacheDir := filepath.Join(homeDir, ".cache", "printing-press", "specs")
+ if err := os.MkdirAll(cacheDir, 0o755); err != nil {
+ return nil, fmt.Errorf("creating cache directory: %w", err)
+ }
+
+ cachePath := filepath.Join(cacheDir, cacheKey+".json")
+ if !refresh {
+ info, err := os.Stat(cachePath)
+ switch {
+ case err == nil && time.Since(info.ModTime()) < 24*time.Hour:
+ fmt.Fprintf(os.Stderr, "Using cached spec for %s\n", specURL)
+ data, readErr := os.ReadFile(cachePath)
+ if readErr != nil {
+ return nil, fmt.Errorf("reading cached spec: %w", readErr)
+ }
+ return data, nil
+ case err != nil && !os.IsNotExist(err):
+ return nil, fmt.Errorf("checking cached spec: %w", err)
+ }
+ }
+
+ fmt.Fprintf(os.Stderr, "Fetching spec from %s...\n", specURL)
+ resp, err := http.Get(specURL)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
+ return nil, fmt.Errorf("unexpected response status: %s", resp.Status)
+ }
+
+ data, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("reading response body: %w", err)
+ }
+
+ if err := os.WriteFile(cachePath, data, 0o644); err != nil {
+ return nil, fmt.Errorf("writing cached spec: %w", err)
+ }
+
+ return data, nil
+}
+
func newVersionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
← 3cb6e869 feat(generator): add color and TTY detection to generated CL
·
back to Cli Printing Press
·
feat(generator): OAuth2 auth flow for generated CLIs dacda31f →