diff --git a/cmd/openapi/commands/overlay/apply.go b/cmd/openapi/commands/overlay/apply.go index d8603a8..342c716 100644 --- a/cmd/openapi/commands/overlay/apply.go +++ b/cmd/openapi/commands/overlay/apply.go @@ -5,20 +5,21 @@ import ( "os" "github.com/speakeasy-api/openapi/cmd/openapi/commands/cmdutil" + overlayPkg "github.com/speakeasy-api/openapi/overlay" "github.com/speakeasy-api/openapi/overlay/loader" "github.com/spf13/cobra" "gopkg.in/yaml.v3" ) var ( - applyOverlayFlag string - applySchemaFlag string - applyOutFlag string + applyOverlayFlags []string + applySchemaFlag string + applyOutFlag string ) var applyCmd = &cobra.Command{ - Use: "apply [ ]", - Short: "Given an overlay, it will apply it to the spec. If omitted, spec will be loaded via extends (only from local file system).", + Use: "apply [ []]", + Short: "Given one or more overlays, it will apply them sequentially to the spec. If omitted, spec will be loaded via extends (only from local file system).", Args: cobra.RangeArgs(0, 2), Run: RunApply, Example: ` # Apply overlay using positional arguments @@ -35,29 +36,37 @@ var applyCmd = &cobra.Command{ # Pipe spec via stdin and provide overlay as file cat spec.yaml | openapi overlay apply overlay.yaml - - cat spec.yaml | openapi overlay apply --overlay overlay.yaml --schema -`, + cat spec.yaml | openapi overlay apply --overlay overlay.yaml --schema - + + # Apply multiple overlays sequentially (repeated flag) + openapi overlay apply --overlay base.yaml --overlay env.yaml --schema spec.yaml`, } func init() { - applyCmd.Flags().StringVar(&applyOverlayFlag, "overlay", "", "Path to the overlay file") + applyCmd.Flags().StringSliceVar(&applyOverlayFlags, "overlay", nil, "Path to an overlay file (can be repeated or comma-separated)") applyCmd.Flags().StringVar(&applySchemaFlag, "schema", "", "Path to the OpenAPI specification file (use '-' for stdin)") applyCmd.Flags().StringVarP(&applyOutFlag, "out", "o", "", "Output file path (defaults to stdout)") } func RunApply(cmd *cobra.Command, args []string) { - // Determine overlay file path from flag or positional argument - var overlayFile string - if applyOverlayFlag != "" { - overlayFile = applyOverlayFlag + // Build the list of overlay files from flags or positional args + var overlayFiles []string + if len(applyOverlayFlags) > 0 { + overlayFiles = applyOverlayFlags } else if len(args) > 0 { - overlayFile = args[0] + overlayFiles = []string{args[0]} } else { - Dief("overlay file is required (use --overlay flag or provide as first argument)") + Dief("at least one overlay file is required (use --overlay flag or provide as first argument)") } - o, err := loader.LoadOverlay(overlayFile) - if err != nil { - Die(err) + // Load all overlays upfront (fail fast on parse errors) + overlays := make([]*overlayPkg.Overlay, 0, len(overlayFiles)) + for _, f := range overlayFiles { + o, err := loader.LoadOverlay(f) + if err != nil { + Dief("Failed to load overlay %q: %s", f, err.Error()) + } + overlays = append(overlays, o) } // Determine spec file path from flag or positional argument @@ -68,8 +77,9 @@ func RunApply(cmd *cobra.Command, args []string) { specFile = cmdutil.ArgAt(args, 1, "") } - // Load spec from stdin or file + // Load spec from stdin or file (use first overlay's extends as fallback) var ys *yaml.Node + var err error specSource := specFile if cmdutil.IsStdin(specFile) || (specFile == "" && cmdutil.StdinIsPiped()) { fmt.Fprintf(os.Stderr, "Reading specification from stdin\n") @@ -79,15 +89,17 @@ func RunApply(cmd *cobra.Command, args []string) { Die(err) } } else { - ys, specSource, err = loader.LoadEitherSpecification(specFile, o) + ys, specSource, err = loader.LoadEitherSpecification(specFile, overlays[0]) if err != nil { Die(err) } } - err = o.ApplyTo(ys) - if err != nil { - Dief("Failed to apply overlay to spec %q: %v", specSource, err) + // Apply all overlays sequentially + for i, o := range overlays { + if err := o.ApplyTo(ys); err != nil { + Dief("Failed to apply overlay %q (#%d) to spec %q: %v", overlayFiles[i], i+1, specSource, err) + } } // Write to output file if specified, otherwise stdout