mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* feat: Add dependency cooldown for npm packages Strip recently-published package versions from npm registry metadata responses so npm's resolver naturally falls back to older versions. Overrides the Accept header to force full packument responses (which include the "time" field needed for publish-date checks). Reports cooldown blocks only when all versions are stripped (remaining == 0), matching npm's --min-release-age behavior for silent fallback. * fix: Report oldest version in cooldown block (shortest wait) When all versions are blocked by cooldown, report the oldest version since it exits the cooldown window first — giving the user the shortest wait time instead of the longest. * fix: Handle resp.Body.Close error return for errcheck linter * test: Add dependency cooldown assertions to template config tests * fix: config template for dependency cooldown * fix: Prevent npm from caching cooldown-stripped metadata responses * fix: Restore body on ReadAll failure and log Close errors in response modifier * fix: Close response body before replacing to prevent connection leak * fix: Correct daysLeft ceiling math and update ContentLength on error recovery * fix: Clear Status on status code change and update ContentLength in modifier error path * refactor: address review comments on dependency cooldown PR - Make NpmCooldownHandler and constructor package-private - Pass cooldown days as parameter instead of reading config internally - Convert standalone functions to methods on npmCooldownHandler - Set Accept-Encoding: identity to prevent gzip responses breaking JSON parsing - Return 503 with descriptive message when upstream body read fails * fix: log errors in stripCooldownVersions instead of swallowing them * fix: Config preserve fallback defaults * fix: Code review fixes * fix: correct cooldown tip to show wait time instead of incorrect trusted_packages advice * fix: prevent integer overflow in cooldown duration calculation with large days values * refactor: deduplicate CooldownBlock into internal/models, fix misleading variable names - Move CooldownBlock struct to internal/models to eliminate duplication between proxy/interceptors and internal/ui packages - Simplify proxy_flow.go by using direct assignment instead of field copy - Rename latestStripped/latestDate to oldestVer/oldestDate for clarity * fix: Dependency Cooldown Check Encapsulation (#207) * fix: Encapsulate cooldown check * feat: Add --skip-dependency-cooldown override * fix: Code review fixes --------- Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
142 lines
4.0 KiB
Go
142 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/safedep/dry/log"
|
|
"github.com/safedep/pmg/cmd/executors"
|
|
"github.com/safedep/pmg/cmd/npm"
|
|
"github.com/safedep/pmg/cmd/pypi"
|
|
"github.com/safedep/pmg/cmd/setup"
|
|
"github.com/safedep/pmg/cmd/version"
|
|
"github.com/safedep/pmg/config"
|
|
"github.com/safedep/pmg/internal/analytics"
|
|
"github.com/safedep/pmg/internal/eventlog"
|
|
"github.com/safedep/pmg/internal/ui"
|
|
appVersion "github.com/safedep/pmg/internal/version"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
debug bool
|
|
silent bool
|
|
verbose bool
|
|
logFile string
|
|
)
|
|
|
|
func main() {
|
|
cmd := &cobra.Command{
|
|
Use: "pmg",
|
|
TraverseChildren: true,
|
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
// Always set this first because we will override the log
|
|
// level if debug or verbose is set
|
|
if logFile != "" {
|
|
os.Setenv("APP_LOG_FILE", logFile)
|
|
os.Setenv("APP_LOG_LEVEL", "info")
|
|
}
|
|
|
|
// Set the log level when debug is enabled
|
|
if debug {
|
|
os.Setenv("APP_LOG_LEVEL", "debug")
|
|
}
|
|
|
|
// Skip stdout logging when debugging is not enabled
|
|
if !debug {
|
|
os.Setenv("APP_LOG_SKIP_STDOUT_LOGGER", "true")
|
|
}
|
|
|
|
// Apply config-based verbosity first
|
|
switch config.Get().Config.Verbosity {
|
|
case config.VerbositySilent:
|
|
ui.SetVerbosityLevel(ui.VerbosityLevelSilent)
|
|
case config.VerbosityVerbose:
|
|
ui.SetVerbosityLevel(ui.VerbosityLevelVerbose)
|
|
default:
|
|
ui.SetVerbosityLevel(ui.VerbosityLevelNormal)
|
|
}
|
|
|
|
// CLI flags override config
|
|
if silent && verbose {
|
|
ui.Fatalf("pmg: --silent and --verbose cannot be used together")
|
|
}
|
|
|
|
if silent {
|
|
ui.SetVerbosityLevel(ui.VerbosityLevelSilent)
|
|
} else if verbose {
|
|
ui.SetVerbosityLevel(ui.VerbosityLevelVerbose)
|
|
}
|
|
|
|
log.InitZapLogger("pmg", "cli")
|
|
|
|
// Initialize event logging (silently fail if it can't be initialized)
|
|
var eventlogErr error
|
|
if logFile != "" {
|
|
// If a custom log file is specified, use it for event logging too
|
|
eventlogErr = eventlog.InitializeWithFile(logFile)
|
|
} else {
|
|
// Otherwise use the default log directory
|
|
eventlogErr = eventlog.Initialize()
|
|
}
|
|
|
|
if eventlogErr != nil {
|
|
ui.Fatalf("failed to initialize event logging: %v", eventlogErr)
|
|
}
|
|
|
|
config.FinalizeDependencyCooldownOverride()
|
|
|
|
// Parse and validate --sandbox-allow flags after all flags are resolved
|
|
if err := config.FinalizeSandboxAllowOverrides(); err != nil {
|
|
ui.Fatalf("pmg: %v", err)
|
|
}
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) == 0 {
|
|
return cmd.Help()
|
|
}
|
|
|
|
return fmt.Errorf("pmg: %s is not a valid command", args[0])
|
|
},
|
|
}
|
|
|
|
cmd.PersistentFlags().StringVar(&logFile, "log", "", "Log file to write to")
|
|
cmd.PersistentFlags().BoolVar(&silent, "silent", false, "Silent mode for invisible experience")
|
|
cmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Verbose mode for more information")
|
|
cmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging (defaults to stdout)")
|
|
|
|
// Apply config flags to the command. This allows for overriding the configuration at runtime
|
|
// using the command line.
|
|
config.ApplyCobraFlags(cmd)
|
|
|
|
cmd.AddCommand(npm.NewNpmCommand())
|
|
cmd.AddCommand(npm.NewPnpmCommand())
|
|
cmd.AddCommand(npm.NewBunCommand())
|
|
cmd.AddCommand(npm.NewYarnCommand())
|
|
cmd.AddCommand(executors.NewNpxCommand())
|
|
cmd.AddCommand(executors.NewPnpxCommand())
|
|
cmd.AddCommand(pypi.NewPipCommand())
|
|
cmd.AddCommand(pypi.NewPip3Command())
|
|
cmd.AddCommand(pypi.NewUvCommand())
|
|
cmd.AddCommand(pypi.NewPoetryCommand())
|
|
cmd.AddCommand(version.NewVersionCommand())
|
|
cmd.AddCommand(setup.NewSetupCommand())
|
|
cmd.AddCommand(setup.NewRemoveCommand())
|
|
|
|
// Print Banner on --help / -h
|
|
cmd.SetHelpFunc(func(command *cobra.Command, args []string) {
|
|
fmt.Print(ui.GeneratePMGBanner(appVersion.Version, appVersion.Commit))
|
|
fmt.Println(command.UsageString())
|
|
})
|
|
|
|
defer analytics.Close()
|
|
defer eventlog.Close()
|
|
|
|
analytics.TrackCommandRun()
|
|
analytics.TrackCI()
|
|
|
|
if err := cmd.Execute(); err != nil {
|
|
os.Exit(1)
|
|
}
|
|
}
|