Documentation
ΒΆ
Index ΒΆ
Constants ΒΆ
This section is empty.
Variables ΒΆ
View Source
var AddCmd = &cobra.Command{ Use: "add", Aliases: []string{"create", "new", "a"}, Short: "Add a new consignment", Long: `Create a new consignment to describe changes made to packages. This is used to track changes for release management.`, Example: ` shipyard add shipyard add --type patch shipyard add --summary "Fixed bug" shipyard add --package api --type patch shipyard add --type patch --summary "Fixed bug" shipyard add --package api --type patch --summary "Fixed bug" echo "Fixed bug" | shipyard add --type patch echo "patch" | shipyard add --summary "Fixed bug" echo "1" | shipyard add --summary "Fixed bug" `, Run: func(cmd *cobra.Command, args []string) { projectConfig, err := config.LoadProjectConfig() if err != nil { logger.Error("Failed to load project configuration", "error", err) fmt.Println("Error: Unable to load project configuration.") fmt.Println("Make sure you're in a Shipyard project directory and run 'shipyard init' if needed.") os.Exit(1) } manager := consignment.NewManager(projectConfig) var ( selectedPackages []string changeType string summary string ) packageFlag, _ := cmd.Flags().GetStringSlice("package") typeFlag, _ := cmd.Flags().GetString("type") summaryFlag, _ := cmd.Flags().GetString("summary") availablePackages := manager.GetAvailablePackages() if len(availablePackages) == 0 { fmt.Println("No packages configured in this project.") fmt.Println("Please run 'shipyard init' to configure your packages.") os.Exit(1) } if projectConfig.Type == config.RepositoryTypeSingleRepo && len(packageFlag) == 0 { packageFlag = []string{availablePackages[0].Name} } if projectConfig.Type == config.RepositoryTypeMonorepo { if len(packageFlag) > 0 { availablePackageNames := make(map[string]bool) for _, pkg := range availablePackages { availablePackageNames[pkg.Name] = true } for _, pkg := range packageFlag { if !availablePackageNames[pkg] { fmt.Printf("Error: Package '%s' not found in project configuration\n", pkg) fmt.Println("Available packages:") for _, availablePkg := range availablePackages { fmt.Printf(" - %s (%s)\n", availablePkg.Name, availablePkg.Path) } os.Exit(1) } } selectedPackages = packageFlag } else { packageOptions := make([]huh.Option[string], 0, len(availablePackages)) for _, pkg := range availablePackages { packageOptions = append(packageOptions, huh.NewOption(fmt.Sprintf("%s (%s)", pkg.Name, pkg.Path), pkg.Name)) } packageForm := huh.NewForm( huh.NewGroup( huh.NewNote(). Title("π¦ Package Selection"). Description("Select the packages that have been changed."), huh.NewMultiSelect[string](). Title("Which packages have changed?"). Description("Select all packages that have been modified."). Options(packageOptions...). Value(&selectedPackages), ), ) if err := packageForm.Run(); err != nil { logger.Error("Failed to get package selection", "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } if len(selectedPackages) == 0 { fmt.Println("No packages selected. Consignment creation cancelled.") return } } } else { selectedPackages = []string{availablePackages[0].Name} } if typeFlag != "" { changeType = typeFlag if err := manager.ValidateChangeType(changeType); err != nil { fmt.Printf("Error: %v\n", err) fmt.Println("Available change types:") for _, ct := range manager.GetAvailableChangeTypes() { displayName := ct.DisplayName if displayName == "" { displayName = ct.Name } fmt.Printf(" - %s (%s)\n", ct.Name, displayName) } os.Exit(1) } } else { if isStdinPiped() { input, err := readStdinLine() if err != nil { fmt.Println("Error: No change type provided and unable to read from stdin") fmt.Println("Available change types:") for i, ct := range projectConfig.GetChangeTypes() { fmt.Printf(" %d. %s\n", i+1, ct.Name) } fmt.Println("Usage: echo '<type-name-or-number>' | shipyard add --summary 'your summary'") os.Exit(1) } changeTypes := projectConfig.GetChangeTypes() if input >= "1" && input <= fmt.Sprintf("%d", len(changeTypes)) { if num := int(input[0] - '0'); num >= 1 && num <= len(changeTypes) { changeType = changeTypes[num-1].Name } } else { changeType = input } if err := manager.ValidateChangeType(changeType); err != nil { fmt.Printf("Error: %v\n", err) fmt.Println("Available change types:") for i, ct := range changeTypes { fmt.Printf(" %d. %s\n", i+1, ct.Name) } os.Exit(1) } } else { changeTypeOptions := make([]huh.Option[string], 0, len(projectConfig.GetChangeTypes())) for _, ct := range projectConfig.GetChangeTypes() { optionText := ct.DisplayName if optionText == "" { optionText = ct.Name } changeTypeOptions = append(changeTypeOptions, huh.NewOption(optionText, ct.Name)) } changeTypeForm := huh.NewForm( huh.NewGroup( huh.NewNote(). Title("π Change Type"). Description("Select the type of change you've made."), huh.NewSelect[string](). Title("What type of change is this?"). Description("Choose the appropriate change type."). Options(changeTypeOptions...). Value(&changeType), ), ) if err := changeTypeForm.Run(); err != nil { logger.Error("Failed to get change type", "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } } } if summaryFlag != "" { summary = summaryFlag if strings.TrimSpace(summary) == "" { fmt.Println("Error: --summary flag cannot be empty") os.Exit(1) } } else { if isStdinPiped() { input, err := readStdinLine() if err != nil { fmt.Println("Error: No summary provided and unable to read from stdin") fmt.Println("Usage: echo 'your summary' | shipyard add --type <type>") os.Exit(1) } summary = input if strings.TrimSpace(summary) == "" { fmt.Println("Error: Summary cannot be empty") os.Exit(1) } } else { summaryForm := huh.NewForm( huh.NewGroup( huh.NewNote(). Title("π Change Summary"). Description("Provide a summary of the changes made."), huh.NewText(). Title("Summary"). Description("Describe the changes made to the selected packages."). Placeholder("e.g. Fixed bug in user authentication flow"). Value(&summary), ), ) if err := summaryForm.Run(); err != nil { logger.Error("Failed to get change summary", "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } } } createdConsignment, err := manager.CreateConsignment(selectedPackages, changeType, summary) if err != nil { logger.Error("Failed to create consignment", "error", err) fmt.Printf("Error: Unable to create consignment: %v\n", err) os.Exit(1) } fmt.Printf("\nπ Consignment created successfully!\n") fmt.Printf("π File: %s/%s.md\n", manager.GetConsignmentDir(), createdConsignment.ID) fmt.Printf("π¦ Packages: %s\n", strings.Join(selectedPackages, ", ")) fmt.Printf("π Type: %s\n", changeType) fmt.Printf("π Summary: %s\n", summary) fmt.Printf("\nπ‘ Next steps:\n") fmt.Printf(" - Review the consignment file\n") fmt.Printf(" - Commit the consignment to your repository\n") fmt.Printf(" - Run 'shipyard version' to calculate new versions\n") logger.Info("Consignment created successfully", "id", createdConsignment.ID, "packages", selectedPackages, "type", changeType, ) }, }
View Source
var InitCmd = &cobra.Command{ Use: "init", Short: "Initialize a new Shipyard project", Long: "This command sets up a new Shipyard project with the necessary configuration files and directories. It can automatically detect your repository URL and scan for packages.", Run: func(cmd *cobra.Command, args []string) { skipAutoScan, _ := cmd.Flags().GetBool("skip-auto-scan") var ( repoType config.RepoType repoPath string changelogTemplate string packages []PackageConfig useAutoScan bool = !skipAutoScan ) if !skipAutoScan { welcomeForm := huh.NewForm( huh.NewGroup( huh.NewNote(). Title("`Shipyard Init`"). Description("Welcome to Shipyard! Let's set up your project with automatic detection."), huh.NewConfirm(). Title("Auto-scan for packages and repository?"). Description("Shipyard can automatically detect your repository URL and scan for packages. Would you like to use auto-detection?"). Value(&useAutoScan), ), ) if err := welcomeForm.Run(); err != nil { logger.Error("Failed to get user input", "error", err) return } } else { fmt.Println("π’ Shipyard Init") fmt.Println("Setting up your project with manual configuration...") } if useAutoScan { logger.Info("Auto-detecting repository and package information...") if detectedRepo, err := autoDetectRepoInfo(); err == nil { repoPath = detectedRepo logger.Info("Detected repository", "repo", repoPath) } else { logger.Warn("Could not auto-detect repository", "error", err.Error()) } if detectedPackages, err := autoScanPackages(); err == nil && len(detectedPackages) > 0 { logger.Info("Found packages", "count", len(detectedPackages)) for _, pkg := range detectedPackages { packages = append(packages, *pkg) logger.Info("Found package", "name", pkg.Name, "ecosystem", pkg.Ecosystem, "path", pkg.Path) } repoType = determineRepoType(detectedPackages) logger.Info("Determined repository type", "type", repoType) } else { if err != nil { logger.Warn("Could not auto-scan packages", "error", err.Error()) } else { logger.Info("No packages found automatically") } } } repoForm := huh.NewForm( huh.NewGroup( huh.NewNote(). Title("Repository Configuration"). Description("Please review and update the repository configuration."), huh.NewInput(). Title("Repository Path"). Value(&repoPath). Placeholder("e.g., github.com/your-org/your-repo"). Description("Enter the path to your repository. This can be a GitHub, GitLab, or Bitbucket URL."), huh.NewSelect[config.RepoType](). Value(&repoType). Title("Select Repository Type"). Description("Choose the type of repository for your project."). Options( huh.NewOption("Monorepo", config.RepositoryTypeMonorepo), huh.NewOption("Single Repo", config.RepositoryTypeSingleRepo), ), huh.NewInput(). Title("Changelog Template"). Value(&changelogTemplate). Placeholder("e.g., keepachangelog"). Description("Enter the template for your changelog. This can be a predefined template like 'keepachangelog' or a custom one."), ), ) if err := repoForm.Run(); err != nil { logger.Error("Failed to initialize project", "error", err) return } if len(packages) == 0 || !useAutoScan { if repoType == config.RepositoryTypeMonorepo { // TODO: add an option to scan for supported package ecosystems var addAnother bool = true for addAnother { var pkgConfig PackageConfig packageForm := huh.NewForm( packageGroup(&pkgConfig), ) if err := packageForm.Run(); err != nil { logger.Error("Failed to configure package", "error", err) return } packages = append(packages, pkgConfig) continueForm := huh.NewForm( huh.NewGroup( huh.NewConfirm(). Title("Add Another Package?"). Description("Would you like to add another package to your monorepo?"). Value(&addAnother), ), ) if err := continueForm.Run(); err != nil { logger.Error("Failed to get user input", "error", err) return } } } else { // Single repo - configure only one package var pkgConfig PackageConfig packageForm := huh.NewForm( packageGroup(&pkgConfig), ) if err := packageForm.Run(); err != nil { logger.Error("Failed to configure package", "error", err) return } packages = append(packages, pkgConfig) } } else { // Review auto-detected packages var confirmPackages bool var packagesDesc strings.Builder packagesDesc.WriteString("The following packages were detected:\n") for _, pkg := range packages { packagesDesc.WriteString(fmt.Sprintf("β’ %s (%s) at %s\n", pkg.Name, pkg.Ecosystem, pkg.Path)) } packagesDesc.WriteString("\nWould you like to use these packages?") reviewForm := huh.NewForm( huh.NewGroup( huh.NewNote(). Title("Package Review"). Description(packagesDesc.String()), huh.NewConfirm(). Title("Use detected packages?"). Description("Accept the auto-detected packages or configure manually?"). Value(&confirmPackages), ), ) if err := reviewForm.Run(); err != nil { logger.Error("Failed to get user input", "error", err) return } if !confirmPackages { packages = []PackageConfig{} if repoType == config.RepositoryTypeMonorepo { var addAnother bool = true for addAnother { var pkgConfig PackageConfig packageForm := huh.NewForm( packageGroup(&pkgConfig), ) if err := packageForm.Run(); err != nil { logger.Error("Failed to configure package", "error", err) return } packages = append(packages, pkgConfig) continueForm := huh.NewForm( huh.NewGroup( huh.NewConfirm(). Title("Add Another Package?"). Description("Would you like to add another package to your monorepo?"). Value(&addAnother), ), ) if err := continueForm.Run(); err != nil { logger.Error("Failed to get user input", "error", err) return } } } else { // Single repo - configure only one package var pkgConfig PackageConfig packageForm := huh.NewForm( packageGroup(&pkgConfig), ) if err := packageForm.Run(); err != nil { logger.Error("Failed to configure package", "error", err) return } packages = append(packages, pkgConfig) } } } configMap := map[string]interface{}{ "type": repoType, "repo": repoPath, "changelog": map[string]interface{}{ "template": changelogTemplate, }, } if repoType == config.RepositoryTypeMonorepo { configMap["packages"] = convertPackagesForConfig(packages) } else { configMap["package"] = convertPackageForConfig(packages[0]) } if err := config.InitProjectConfig(configMap); err != nil { logger.Error("Failed to initialize project configuration", "error", err) return } logger.Info("Successfully initialized Shipyard project", "type", repoType, "repo", repoPath) logger.Info("Configuration saved to", "path", config.AppConfig.GetString("config")) fmt.Println("\nπ Shipyard project initialized successfully!") fmt.Printf("π Configuration saved to: %s\n", config.AppConfig.GetString("config")) fmt.Printf("π Repository type: %s\n", repoType) fmt.Printf("π Repository: %s\n", repoPath) fmt.Printf("π Changelog template: %s\n", changelogTemplate) if repoType == config.RepositoryTypeMonorepo { fmt.Printf("π¦ Packages configured: %d\n", len(packages)) for _, pkg := range packages { fmt.Printf(" - %s (%s) at %s\n", pkg.Name, pkg.Ecosystem, pkg.Path) } } else { fmt.Printf("π¦ Package: %s (%s) at %s\n", packages[0].Name, packages[0].Ecosystem, packages[0].Path) } fmt.Println("\nπ Next steps:") fmt.Println(" - Review the generated configuration file") fmt.Println(" - Run 'shipyard --help' to see available commands") fmt.Println(" - Start managing your releases with Shipyard!") }, }
View Source
var ReleaseNotesCmd = &cobra.Command{ Use: "release-notes [version]", Short: "Get release notes for a specific version", Long: `Get release notes for a specific version from shipment history.`, Example: ` shipyard release-notes 1.2.3 # Get release notes for version 1.2.3 shipyard release-notes v1.2.3 # Get release notes for version v1.2.3 (v prefix is optional) shipyard release-notes 1.2.3 -p myapp # Get release notes for version 1.2.3 of package myapp (monorepo) shipyard release-notes 1.2.3 --raw # Get raw markdown output instead of rendered shipyard release-notes 1.2.3 -t simple # Use a different template for rendering`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { projectConfig, err := config.LoadProjectConfig() if err != nil { logger.Error("Failed to load project configuration", "error", err) fmt.Printf("Error: Unable to load project configuration: %v\n", err) fmt.Println("Please run 'shipyard init' to initialize your project.") os.Exit(1) } versionStr := args[0] versionStr = strings.TrimPrefix(versionStr, "v") requestedVersion, err := semver.Parse(versionStr) if err != nil { logger.Error("Invalid version format", "version", versionStr, "error", err) fmt.Printf("Error: Invalid version format '%s': %v\n", versionStr, err) fmt.Println("Version should be in format like '1.2.3' or 'v1.2.3'") os.Exit(1) } packageFilter, _ := cmd.Flags().GetString("package") if packageFilter != "" { if projectConfig.Type != config.RepositoryTypeMonorepo { logger.Error("Package filter can only be used with monorepo projects") fmt.Printf("Error: --package flag can only be used with monorepo projects\n") os.Exit(1) } packageExists := false for _, pkg := range projectConfig.Packages { if pkg.Name == packageFilter { packageExists = true break } } if !packageExists { logger.Error("Package not found", "package", packageFilter) fmt.Printf("Error: Package '%s' not found in project configuration\n", packageFilter) os.Exit(1) } } shipmentHistory := shipment.NewShipmentHistory(projectConfig) history, err := shipmentHistory.LoadHistory() if err != nil { logger.Error("Failed to load shipment history", "error", err) fmt.Printf("Error: Unable to load shipment history: %v\n", err) os.Exit(1) } if len(history) == 0 { logger.Error("No shipment history found") fmt.Printf("Error: No shipment history found\n") fmt.Println("Use 'shipyard version' to create some releases first.") os.Exit(1) } // Find the shipment for the requested version var matchingShipment *shipment.Shipment var matchingPackageName string for _, ship := range history { for packageName, version := range ship.Versions { if packageFilter != "" && packageName != packageFilter { continue } if projectConfig.Type == config.RepositoryTypeSingleRepo && packageFilter == "" { packageName = projectConfig.Package.Name } if version.Equals(requestedVersion) { matchingShipment = ship matchingPackageName = packageName break } } if matchingShipment != nil { break } } if matchingShipment == nil { logger.Error("Version not found in shipment history", "version", requestedVersion.String(), "package", packageFilter) if packageFilter != "" { fmt.Printf("Error: Version %s not found for package '%s' in shipment history\n", requestedVersion.String(), packageFilter) } else { fmt.Printf("Error: Version %s not found in shipment history\n", requestedVersion.String()) } fmt.Println("Available versions:") if packageFilter != "" { fmt.Printf(" Package '%s':\n", packageFilter) for _, ship := range history { if version, exists := ship.Versions[packageFilter]; exists { fmt.Printf(" - %s (shipped: %s)\n", version.String(), ship.Date.Format("2006-01-02")) } } } else { versionMap := make(map[string][]string) for _, ship := range history { for pkgName, version := range ship.Versions { versionMap[pkgName] = append(versionMap[pkgName], fmt.Sprintf("%s (shipped: %s)", version.String(), ship.Date.Format("2006-01-02"))) } } for pkgName, versions := range versionMap { fmt.Printf(" Package '%s':\n", pkgName) for _, v := range versions { fmt.Printf(" - %s\n", v) } } } os.Exit(1) } templateOverride, _ := cmd.Flags().GetString("template") if templateOverride != "" { if err := changelog.ValidateTemplate(templateOverride); err != nil { logger.Error("Invalid template specified", "template", templateOverride, "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } projectConfig.Changelog.Template = templateOverride } templateEngine := templates.NewTemplateEngine(projectConfig) releaseNotesContent, err := generateReleaseNotesForShipment(matchingShipment, matchingPackageName, requestedVersion, templateEngine, projectConfig) if err != nil { logger.Error("Failed to generate release notes", "error", err) fmt.Printf("Error: Unable to generate release notes: %v\n", err) os.Exit(1) } raw, _ := cmd.Flags().GetBool("raw") if raw { fmt.Print(releaseNotesContent) } else { rendered, err := renderMarkdown(releaseNotesContent) if err != nil { logger.Error("Failed to render markdown", "error", err) fmt.Printf("Warning: Failed to render markdown, showing raw content: %v\n", err) fmt.Print(releaseNotesContent) } else { fmt.Printf("π Release Notes for %s %s\n", matchingPackageName, requestedVersion.String()) fmt.Printf("========================================\n\n") fmt.Print(rendered) } } logger.Info("Release notes command completed successfully", "version", requestedVersion.String(), "package", matchingPackageName, "shipment_id", matchingShipment.ID, "shipment_date", matchingShipment.Date.Format("2006-01-02"), ) }, }
View Source
var RemoteCmd = &cobra.Command{
Use: "remote",
Short: "Manage remote configuration sources",
Long: `Manage remote configuration sources for Shipyard.
Remote configurations allow teams to extend a shared configuration from:
- HTTP/HTTPS URLs: https://example.com/config.yaml
- GitHub repositories: github:owner/repo/path/to/config.yaml[@ref] (supports SSH and HTTPS)
- Git repositories: git+https://github.com/owner/repo.git/path/to/config.yaml[@ref] (HTTPS)
- Git repositories: git+git@github.com:owner/repo.git/path/to/config.yaml[@ref] (SSH)
Authentication is supported for private repositories via SSH keys, tokens, and git credentials.
Remote configurations are cached locally for performance and can be refreshed as needed.`,
}
View Source
var RootCmd = &cobra.Command{ Use: "shipyard", Short: "Shipyard, Where releases are built", Long: "Shipyard is a tool for managing change notes, versions, and releases.", PersistentPreRun: func(cmd *cobra.Command, args []string) { initLogger() }, Run: func(cmd *cobra.Command, args []string) { println("Welcome to Shipyard!") _, err := config.LoadProjectConfig() if err != nil { println("You need to run `shipyard init` to set up your project.") logger.Error("Error loading config", "error", err) os.Exit(1) } logger.Info("Shipyard initialized successfully") println("You are in a Shipyard project!") println("Use `shipyard --help` to see available commands.") println("For more information, visit https://shipyard.tamez.dev/docs") }, }
View Source
var StatusCmd = &cobra.Command{ Use: "status", Short: "Show consignment status and version information", Long: "Display current consignments, what new versions would be, and optionally output release notes for packages.", Run: func(cmd *cobra.Command, args []string) { projectConfig, err := config.LoadProjectConfig() if err != nil { logger.Error("Failed to load project configuration", "error", err) fmt.Printf("Error: Unable to load project configuration: %v\n", err) fmt.Println("Please run 'shipyard init' to initialize your project.") os.Exit(1) } manager := consignment.NewManager(projectConfig) consignments, err := manager.GetConsignmens() if err != nil { logger.Error("Failed to get consignments", "error", err) fmt.Printf("Error: Unable to read consignments: %v\n", err) os.Exit(1) } packageFilter, _ := cmd.Flags().GetString("package") if packageFilter != "" { if projectConfig.Type != config.RepositoryTypeMonorepo { logger.Error("Package filter can only be used with monorepo projects") fmt.Printf("Error: --package flag can only be used with monorepo projects\n") os.Exit(1) } packageExists := false for _, pkg := range projectConfig.Packages { if pkg.Name == packageFilter { packageExists = true break } } if !packageExists { logger.Error("Package not found", "package", packageFilter) fmt.Printf("Error: Package '%s' not found in project configuration\n", packageFilter) os.Exit(1) } } fmt.Printf("π Shipyard Project Status\n") fmt.Printf("==========================\n\n") fmt.Printf("π Project Type: %s\n", projectConfig.Type) if projectConfig.Type == config.RepositoryTypeMonorepo { fmt.Printf("π¦ Packages: %d\n", len(projectConfig.Packages)) } else { fmt.Printf("π¦ Package: %s\n", projectConfig.Package.Name) } fmt.Printf("π Changelog Template: %s\n", projectConfig.Changelog.Template) fmt.Printf("\n") fmt.Printf("π Consignments\n") fmt.Printf("===============\n") if len(consignments) == 0 { fmt.Println("No consignments found.") fmt.Println("Create some consignments with 'shipyard add' first.") return } fmt.Printf("Total consignments: %d\n\n", len(consignments)) filteredConsignments := consignments if packageFilter != "" { filteredConsignments = make([]*consignment.Consignment, 0) for _, c := range consignments { if _, exists := c.Packages[packageFilter]; exists { filteredConsignments = append(filteredConsignments, c) } } } for _, c := range filteredConsignments { fmt.Printf("β’ %s (Created: %s)\n", c.ID, c.Created.Format(time.RFC3339)) fmt.Printf(" Summary: %s\n", c.Summary) fmt.Printf(" Packages: ") first := true for pkgName, changeType := range c.Packages { if packageFilter != "" && pkgName != packageFilter { continue } if !first { fmt.Printf(", ") } fmt.Printf("%s (%s)", pkgName, changeType) first = false } fmt.Printf("\n\n") } fmt.Printf("π Version Information\n") fmt.Printf("=====================\n") versions, err := manager.CalculateAllVersions() if err != nil { logger.Error("Failed to calculate versions", "error", err) fmt.Printf("Error: Unable to calculate versions: %v\n", err) os.Exit(1) } if packageFilter != "" { if version, exists := versions[packageFilter]; exists { fmt.Printf("Next version for %s: %s\n", packageFilter, version.String()) } else { fmt.Printf("No version changes for %s\n", packageFilter) } } else { fmt.Printf("Next versions:\n") for pkgName, version := range versions { fmt.Printf(" %s: %s\n", pkgName, version.String()) } } releaseNotes, _ := cmd.Flags().GetBool("release-notes") if releaseNotes { fmt.Printf("\nπ Release Notes\n") fmt.Printf("================\n\n") templateOverride, _ := cmd.Flags().GetString("template") if templateOverride != "" { if err := changelog.ValidateTemplate(templateOverride); err != nil { logger.Error("Invalid template specified", "template", templateOverride, "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } projectConfig.Changelog.Template = templateOverride } generator, err := changelog.NewGenerator(projectConfig) if err != nil { logger.Error("Failed to create changelog generator", "error", err) fmt.Printf("Error: Unable to create changelog generator: %v\n", err) os.Exit(1) } // Generate release notes var releaseNotesContent string if packageFilter != "" { version, exists := versions[packageFilter] if !exists { fmt.Printf("No version changes for package '%s'\n", packageFilter) return } releaseNotesContent, err = generator.GenerateChangelogForPackage(packageFilter, consignments, version) if err != nil { logger.Error("Failed to generate release notes for package", "package", packageFilter, "error", err) fmt.Printf("Error: Unable to generate release notes for package '%s': %v\n", packageFilter, err) os.Exit(1) } } else { releaseNotesContent, err = generator.GenerateChangelog(consignments, versions) if err != nil { logger.Error("Failed to generate release notes", "error", err) fmt.Printf("Error: Unable to generate release notes: %v\n", err) os.Exit(1) } } raw, _ := cmd.Flags().GetBool("raw") if raw { fmt.Print(releaseNotesContent) } else { rendered, err := renderMarkdown(releaseNotesContent) if err != nil { logger.Error("Failed to render markdown", "error", err) fmt.Printf("Warning: Failed to render markdown, showing raw content: %v\n", err) fmt.Print(releaseNotesContent) } else { fmt.Print(rendered) } } } fmt.Printf("\nπ‘ Next steps:\n") fmt.Printf(" - Run 'shipyard version --preview' to see the changelog\n") fmt.Printf(" - Run 'shipyard version --dry-run' to see changelog and version info\n") fmt.Printf(" - Run 'shipyard version' to generate changelog and apply versions\n") logger.Info("Status command completed successfully", "consignments", len(consignments), "packages", len(versions), ) }, }
View Source
var VersionCmd = &cobra.Command{ Use: "version", Short: "Generate changelogs and apply version updates", Long: `Generate changelogs and apply version updates to package manifests.`, Run: func(cmd *cobra.Command, args []string) { projectConfig, err := config.LoadProjectConfig() if err != nil { logger.Error("Failed to load project configuration", "error", err) fmt.Printf("Error: Unable to load project configuration: %v\n", err) fmt.Println("Please run 'shipyard init' to initialize your project.") os.Exit(1) } manager := consignment.NewManager(projectConfig) consignments, err := manager.GetConsignmens() if err != nil { logger.Error("Failed to get consignments", "error", err) fmt.Printf("Error: Unable to read consignments: %v\n", err) os.Exit(1) } regenerate, _ := cmd.Flags().GetBool("regenerate") // Calculate versions for packages that have consignments (if any) var versions map[string]*semver.Version if len(consignments) > 0 && !regenerate { versions, err = manager.CalculateAllVersions() if err != nil { logger.Error("Failed to calculate versions", "error", err) fmt.Printf("Error: Unable to calculate versions: %v\n", err) os.Exit(1) } } else { versions = make(map[string]*semver.Version) } templateOverride, _ := cmd.Flags().GetString("template") if templateOverride != "" { if err := changelog.ValidateTemplate(templateOverride); err != nil { logger.Error("Invalid template specified", "template", templateOverride, "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } projectConfig.Changelog.Template = templateOverride } generator, err := changelog.NewGenerator(projectConfig) if err != nil { logger.Error("Failed to create changelog generator", "error", err) fmt.Printf("Error: Unable to create changelog generator: %v\n", err) os.Exit(1) } packageFilter, _ := cmd.Flags().GetString("package") if packageFilter != "" { if projectConfig.Type != config.RepositoryTypeMonorepo { logger.Error("Package filter can only be used with monorepo projects") fmt.Printf("Error: --package flag can only be used with monorepo projects\n") os.Exit(1) } packageExists := false for _, pkg := range projectConfig.Packages { if pkg.Name == packageFilter { packageExists = true break } } if !packageExists { logger.Error("Package not found", "package", packageFilter) fmt.Printf("Error: Package '%s' not found in project configuration\n", packageFilter) os.Exit(1) } } // Generate changelog from shipment history (primary) or current consignments (fallback) var changelogContent string var packageChangelogs map[string]string // For monorepo separate changelogs hasCurrentConsignments := len(consignments) > 0 && !regenerate if regenerate { if packageFilter != "" { changelogContent, err = generator.GenerateChangelogFromHistoryForPackage(packageFilter) if err != nil { logger.Error("Failed to generate changelog from history for package", "package", packageFilter, "error", err) fmt.Printf("Error: Unable to generate changelog from history for package '%s': %v\n", packageFilter, err) fmt.Println("No shipment history found. Use 'shipyard version' to ship some consignments first.") os.Exit(1) } } else { if projectConfig.Type == config.RepositoryTypeMonorepo { packageChangelogs, err = generator.GenerateChangelogsFromHistoryForPackages() if err != nil { logger.Error("Failed to generate package changelogs from history", "error", err) fmt.Printf("Error: Unable to generate package changelogs from history: %v\n", err) fmt.Println("No shipment history found. Use 'shipyard version' to ship some consignments first.") os.Exit(1) } } else { changelogContent, err = generator.GenerateChangelogFromHistory() if err != nil { logger.Error("Failed to generate changelog from history", "error", err) fmt.Printf("Error: Unable to generate changelog from history: %v\n", err) fmt.Println("No shipment history found. Use 'shipyard version' to ship some consignments first.") os.Exit(1) } } } } else { if packageFilter != "" { changelogContent, err = generator.GenerateChangelogFromHistoryForPackage(packageFilter) if err != nil { if hasCurrentConsignments { logger.Info("No shipment history found for package, using current consignments", "package", packageFilter) version, exists := versions[packageFilter] if !exists { logger.Error("No version calculated for package", "package", packageFilter) fmt.Printf("Error: No version calculated for package '%s'\n", packageFilter) os.Exit(1) } changelogContent, err = generator.GenerateChangelogForPackage(packageFilter, consignments, version) if err != nil { logger.Error("Failed to generate changelog for package", "package", packageFilter, "error", err) fmt.Printf("Error: Unable to generate changelog for package '%s': %v\n", packageFilter, err) os.Exit(1) } } else { logger.Error("No shipment history or current consignments found for package", "package", packageFilter) fmt.Printf("Error: No shipment history found for package '%s' and no current consignments to process\n", packageFilter) fmt.Println("Create some consignments with 'shipyard add' first, or ensure shipment history exists.") os.Exit(1) } } } else { if projectConfig.Type == config.RepositoryTypeMonorepo { packageChangelogs, err = generator.GenerateChangelogsFromHistoryForPackages() if err != nil { if hasCurrentConsignments { logger.Info("No shipment history found, using current consignments for package changelogs") packageChangelogs, err = generator.GenerateChangelogsForPackages(consignments, versions) if err != nil { logger.Error("Failed to generate package changelogs", "error", err) fmt.Printf("Error: Unable to generate package changelogs: %v\n", err) os.Exit(1) } } else { logger.Error("No shipment history or current consignments found for monorepo") fmt.Printf("Error: No shipment history found for monorepo and no current consignments to process\n") fmt.Println("Create some consignments with 'shipyard add' first, or ensure shipment history exists.") os.Exit(1) } } } else { changelogContent, err = generator.GenerateChangelogFromHistory() if err != nil { if hasCurrentConsignments { logger.Info("No shipment history found, using current consignments") changelogContent, err = generator.GenerateChangelog(consignments, versions) if err != nil { logger.Error("Failed to generate changelog", "error", err) fmt.Printf("Error: Unable to generate changelog: %v\n", err) os.Exit(1) } } else { logger.Error("No shipment history or current consignments found") fmt.Printf("Error: No shipment history found and no current consignments to process\n") fmt.Println("Create some consignments with 'shipyard add' first, or ensure shipment history exists.") os.Exit(1) } } } } } dryRun, _ := cmd.Flags().GetBool("dry-run") preview, _ := cmd.Flags().GetBool("preview") if dryRun || preview { if dryRun { if packageChangelogs != nil { fmt.Println("π Generated Package Changelogs (Raw):") fmt.Println("======================================") fmt.Println() for packageName, changelog := range packageChangelogs { fmt.Printf("--- Package: %s ---\n", packageName) fmt.Print(changelog) fmt.Println() } } else { fmt.Println("π Generated Changelog (Raw):") fmt.Println("=============================") fmt.Println() fmt.Print(changelogContent) } fmt.Println() fmt.Println("π Version Information:") fmt.Println("======================") if packageFilter != "" { if version, exists := versions[packageFilter]; exists { fmt.Printf("%s: %s\n", packageFilter, version.String()) } } else { for pkgName, version := range versions { fmt.Printf("%s: %s\n", pkgName, version.String()) } } fmt.Println() fmt.Printf("π¦ Consignments to be processed: %d\n", len(consignments)) fmt.Printf("π Template: %s\n", projectConfig.Changelog.Template) if packageFilter != "" { fmt.Printf("π― Package filter: %s\n", packageFilter) } if packageChangelogs != nil { fmt.Printf("π Generated %d package changelogs\n", len(packageChangelogs)) } fmt.Println() fmt.Println("π‘ To apply these changes, run 'shipyard version' without --dry-run") } else { if packageChangelogs != nil { fmt.Println("π Generated Package Changelogs Preview:") fmt.Println("========================================") fmt.Println() for packageName, changelog := range packageChangelogs { fmt.Printf("--- Package: %s ---\n", packageName) rendered, err := renderMarkdown(changelog) if err != nil { logger.Error("Failed to render markdown for package", "package", packageName, "error", err) fmt.Printf("Warning: Failed to render markdown for package %s, showing raw content: %v\n", packageName, err) fmt.Print(changelog) } else { fmt.Print(rendered) } fmt.Println("=====================================") fmt.Println() } fmt.Printf("π Generated %d package changelogs\n", len(packageChangelogs)) } else { rendered, err := renderMarkdown(changelogContent) if err != nil { logger.Error("Failed to render markdown", "error", err) fmt.Printf("Warning: Failed to render markdown, showing raw content: %v\n", err) fmt.Print(changelogContent) } else { fmt.Println("π Generated Changelog Preview:") fmt.Println("===============================") fmt.Println() fmt.Print(rendered) fmt.Println("===============================") } } fmt.Printf("π Template: %s\n", projectConfig.Changelog.Template) fmt.Printf("π¦ Consignments processed: %d\n", len(consignments)) if packageFilter != "" { fmt.Printf("π― Package filter: %s\n", packageFilter) } } return } yes, _ := cmd.Flags().GetBool("yes") if !yes { var confirm bool var confirmationMessage string if regenerate { confirmationMessage = "This will regenerate the changelog from shipment history. Are you sure?" } else if hasCurrentConsignments { confirmationMessage = fmt.Sprintf("This will generate changelog and update package versions, clearing %d consignments. Are you sure?", len(consignments)) } else { confirmationMessage = "This will regenerate the changelog from shipment history. Are you sure?" } form := huh.NewForm( huh.NewGroup( huh.NewConfirm(). Title("Generate Changelog?"). Description(confirmationMessage). Value(&confirm), ), ) if err := form.Run(); err != nil { logger.Error("Failed to get user confirmation", "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } if !confirm { fmt.Println("Changelog generation cancelled.") return } } outputPath, _ := cmd.Flags().GetString("output") if outputPath == "" { outputPath = "CHANGELOG.md" } if _, err := os.Stat(outputPath); err == nil { if !yes { var overwrite bool form := huh.NewForm( huh.NewGroup( huh.NewConfirm(). Title("Overwrite Existing Changelog?"). Description(fmt.Sprintf("The file %s already exists. Do you want to overwrite it?", outputPath)). Value(&overwrite), ), ) if err := form.Run(); err != nil { logger.Error("Failed to get user confirmation", "error", err) fmt.Printf("Error: %v\n", err) os.Exit(1) } if !overwrite { fmt.Println("Changelog generation cancelled.") return } } } if packageChangelogs != nil { fmt.Printf("\nπ Writing package changelogs...\n") usePackagePaths := projectConfig.ShouldUsePackagePaths() defaultFilename := projectConfig.GetChangelogOutputPath() for packageName, changelog := range packageChangelogs { var packageOutputPath string if usePackagePaths { pkg := projectConfig.GetPackageByName(packageName) if pkg != nil { packageOutputPath = pkg.GetChangelogPath(defaultFilename) } else { packageOutputPath = filepath.Join(packageName, defaultFilename) } } else { if outputPath == "" { outputPath = defaultFilename } packageOutputPath = fmt.Sprintf("%s.%s.md", strings.TrimSuffix(outputPath, ".md"), packageName) } dir := filepath.Dir(packageOutputPath) if dir != "." { err := os.MkdirAll(dir, 0755) if err != nil { logger.Error("Failed to create directory for package changelog", "package", packageName, "dir", dir, "error", err) fmt.Printf("Error: Failed to create directory %s for package %s: %v\n", dir, packageName, err) os.Exit(1) } } err := os.WriteFile(packageOutputPath, []byte(changelog), 0644) if err != nil { logger.Error("Failed to write package changelog", "package", packageName, "path", packageOutputPath, "error", err) fmt.Printf("Error: Failed to write changelog for package %s to %s: %v\n", packageName, packageOutputPath, err) os.Exit(1) } fmt.Printf(" β %s: %s\n", packageName, packageOutputPath) } } else { if outputPath == "" { outputPath = projectConfig.GetChangelogOutputPath() } fmt.Printf("\nπ Writing changelog to %s...\n", outputPath) dir := filepath.Dir(outputPath) if dir != "." { err := os.MkdirAll(dir, 0755) if err != nil { logger.Error("Failed to create directory for changelog", "dir", dir, "error", err) fmt.Printf("Error: Failed to create directory %s: %v\n", dir, err) os.Exit(1) } } err := os.WriteFile(outputPath, []byte(changelogContent), 0644) if err != nil { logger.Error("Failed to write changelog", "path", outputPath, "error", err) fmt.Printf("Error: Failed to write changelog to %s: %v\n", outputPath, err) os.Exit(1) } fmt.Printf(" β Changelog written successfully\n") } // Apply version updates and record shipment history BEFORE writing changelog var appliedVersions map[string]*semver.Version if hasCurrentConsignments && !regenerate { fmt.Printf("\nπ Recording shipment history...\n") gitOps := git.NewGitOperations(projectConfig) gitPush, _ := cmd.Flags().GetBool("git-push") gitTag, _ := cmd.Flags().GetBool("git-tag") if gitPush { gitTag = true } // Create git tags if requested and git is available var gitTags map[string]string if gitTag && gitOps.IsAvailable() { tempVersions, err := manager.CalculateAllVersions() if err != nil { logger.Error("Failed to calculate versions for git tags", "error", err) fmt.Printf("Error: Unable to calculate versions for git tags: %v\n", err) os.Exit(1) } // Get consignment summaries for commit message var consignmentSummaries []string for _, c := range consignments { consignmentSummaries = append(consignmentSummaries, c.Summary) } commitMessage := gitOps.CreateShipmentCommitMessage(tempVersions, consignmentSummaries) gitTags, err = gitOps.CreateShipmentTags(tempVersions, commitMessage) if err != nil { logger.Error("Failed to create git tags", "error", err) fmt.Printf("Warning: Failed to create git tags: %v\n", err) gitTags = nil } else { fmt.Printf("β Git tags created!\n") } } templateName := projectConfig.Changelog.Template appliedVersions, err = manager.RecordShipmentHistoryWithTags(templateName, gitTags) if err != nil { logger.Error("Failed to record shipment history", "error", err) fmt.Printf("Error: Unable to record shipment history: %v\n", err) os.Exit(1) } fmt.Printf("β Shipment history recorded!\n") fmt.Printf("\nπ Regenerating changelog from updated history...\n") if packageFilter != "" { changelogContent, err = generator.GenerateChangelogFromHistoryForPackage(packageFilter) if err != nil { logger.Error("Failed to generate changelog from history for package", "package", packageFilter, "error", err) fmt.Printf("Error: Unable to generate changelog from history for package '%s': %v\n", packageFilter, err) os.Exit(1) } } else { changelogContent, err = generator.GenerateChangelogFromHistory() if err != nil { logger.Error("Failed to generate changelog from history", "error", err) fmt.Printf("Error: Unable to generate changelog from history: %v\n", err) os.Exit(1) } } fmt.Printf("β Changelog regenerated from updated history!\n") } else if regenerate { fmt.Printf("\nοΏ½ Changelog regenerated from shipment history (no version updates applied)\n") appliedVersions = make(map[string]*semver.Version) } else { fmt.Printf("\nπ No current consignments to apply - changelog regenerated from shipment history\n") appliedVersions = make(map[string]*semver.Version) } if err := writeChangelog(outputPath, changelogContent); err != nil { logger.Error("Failed to write changelog", "error", err) fmt.Printf("Error: Unable to write changelog: %v\n", err) os.Exit(1) } fmt.Printf("β Changelog generated successfully!\n") fmt.Printf("π File: %s\n", outputPath) if hasCurrentConsignments && !regenerate { fmt.Printf("\nπ Applying version updates...\n") err = manager.ApplyVersionUpdatesAndClearConsignments(appliedVersions) if err != nil { logger.Error("Failed to apply version updates", "error", err) fmt.Printf("Error: Unable to apply version updates: %v\n", err) os.Exit(1) } fmt.Printf("\nβ Version updates applied successfully!\n") fmt.Printf("π¦ Packages updated:\n") for pkgName, version := range appliedVersions { fmt.Printf(" - %s: %s\n", pkgName, version.String()) } fmt.Printf("\nπ§Ή Consignments cleared: %d\n", len(consignments)) gitCommit, _ := cmd.Flags().GetBool("git-commit") gitPush, _ := cmd.Flags().GetBool("git-push") if gitPush { gitCommit = true } if gitCommit || gitPush { gitOps := git.NewGitOperations(projectConfig) if gitOps.IsAvailable() { // Get consignment summaries for commit message var consignmentSummaries []string for _, c := range consignments { consignmentSummaries = append(consignmentSummaries, c.Summary) } changelogGitPath := outputPath if changelogGitPath == "" { changelogGitPath = "CHANGELOG.md" } err = gitOps.PerformShipmentGitOperations(appliedVersions, changelogGitPath, consignmentSummaries, gitCommit, gitPush) if err != nil { logger.Error("Failed to perform git operations", "error", err) fmt.Printf("Warning: Failed to perform git operations: %v\n", err) } } else { fmt.Printf("β οΈ Git repository not found - skipping git operations\n") } } } fmt.Printf("π Template: %s\n", projectConfig.Changelog.Template) if packageFilter != "" { fmt.Printf("π― Package filter: %s\n", packageFilter) } gitCommit, _ := cmd.Flags().GetBool("git-commit") gitPush, _ := cmd.Flags().GetBool("git-push") gitTag, _ := cmd.Flags().GetBool("git-tag") if gitPush { gitCommit = true gitTag = true } fmt.Printf("\nπ‘ Next steps:\n") fmt.Printf(" - Review the generated changelog\n") if hasCurrentConsignments && !regenerate { fmt.Printf(" - Review updated package manifests\n") } if !gitCommit { fmt.Printf(" - Commit all changes to your repository\n") } if hasCurrentConsignments && !regenerate && !gitTag { fmt.Printf(" - Create and push git tags for the new versions\n") } logger.Info("Changelog generation completed successfully", "output", outputPath, "template", projectConfig.Changelog.Template, "packages_updated", len(appliedVersions), "consignments_cleared", len(consignments), "regenerate_mode", regenerate, "from_history", regenerate || !hasCurrentConsignments, ) }, }
Functions ΒΆ
This section is empty.
Types ΒΆ
Click to show internal directories.
Click to hide internal directories.