func runReleaseAction()

in cli/azd/extensions/microsoft.azd.extensions/internal/cmd/release.go [108:306]


func runReleaseAction(ctx context.Context, flags *releaseFlags) error {
	// Create a new context that includes the AZD access token
	ctx = azdext.WithAccessToken(ctx)

	// Create a new AZD client
	azdClient, err := azdext.NewAzdClient()
	if err != nil {
		return fmt.Errorf("failed to create azd client: %w", err)
	}

	defer azdClient.Close()

	absExtensionPath, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("failed to get absolute path for extension directory: %w", err)
	}

	extensionMetadata, err := models.LoadExtension(absExtensionPath)
	if err != nil {
		return err
	}

	if flags.version == "" {
		flags.version = extensionMetadata.Version
	}

	if flags.title == "" {
		flags.title = fmt.Sprintf("%s (%s)", extensionMetadata.DisplayName, flags.version)
	}

	if flags.artifacts == "" {
		localRegistryArtifactsPath, err := internal.LocalRegistryArtifactsPath()
		if err != nil {
			return err
		}

		flags.artifacts = filepath.Join(localRegistryArtifactsPath, extensionMetadata.Id, flags.version, "*.zip")
	}

	if flags.notes != "" && flags.notesFile != "" {
		return errors.New("only one of --notes or --notes-file can be specified")
	}

	if flags.notesFile != "" {
		if flags.notesFile == "-" {
			// Read from standard input
			notes, err := io.ReadAll(os.Stdin)
			if err != nil {
				return fmt.Errorf("failed to read notes from stdin: %w", err)
			}
			flags.notes = string(notes)
		} else {
			// Read from file
			notes, err := os.ReadFile(flags.notesFile)
			if err != nil {
				return fmt.Errorf("failed to read notes from file: %w", err)
			}
			flags.notes = string(notes)
		}
	}

	// Automatically include changelog.md if not notes are provided
	if flags.notes == "" {
		fileInfo, err := os.Stat("changelog.md")
		if err == nil && !fileInfo.IsDir() {
			notes, err := os.ReadFile("changelog.md")
			if err != nil {
				return fmt.Errorf("failed to read notes from changelog.md: %w", err)
			}
			flags.notes = string(notes)
		}
	}

	tagName := fmt.Sprintf("azd-ext-%s_%s", extensionMetadata.SafeDashId(), flags.version)

	args := []string{
		"release",
		"create",
		tagName,
	}

	if flags.notes != "" {
		args = append(args, "--notes", flags.notes)
	}

	if flags.title != "" {
		args = append(args, "--title", flags.title)
	}

	if flags.repository != "" {
		args = append(args, "--repo", flags.repository)
	}

	if flags.preRelease {
		args = append(args, "--prerelease")
	}

	if flags.draft {
		args = append(args, "--draft")
	}

	var releaseResult string

	repo, err := getGithubRepo(absExtensionPath, flags.repository)
	if err != nil {
		return err
	}

	fmt.Println()
	fmt.Printf("%s: %s\n", output.WithBold("Artifacts"), flags.artifacts)
	fmt.Printf("%s: %s - %s\n",
		output.WithBold("GitHub Repo"),
		repo.Name,
		output.WithHyperlink(repo.Url, "View Repo"),
	)
	fmt.Printf("%s: %s (%s)\n", "GitHub Release", flags.title, tagName)
	fmt.Printf("%s: %t\n", output.WithBold("Prerelease"), flags.preRelease)
	fmt.Printf("%s: %t\n", output.WithBold("Draft"), flags.draft)

	if !flags.confirm {
		fmt.Println()
		confirmReleaseResponse, err := azdClient.Prompt().Confirm(ctx, &azdext.ConfirmRequest{
			Options: &azdext.ConfirmOptions{
				Message:      "Are you sure you want to create the GitHub release?",
				DefaultValue: internal.ToPtr(false),
				Placeholder:  "no",
			},
		})
		if err != nil {
			return fmt.Errorf("failed to prompt for confirmation: %w", err)
		}

		if !*confirmReleaseResponse.Value {
			return errors.New("release cancelled by user")
		}
	}

	taskList := ux.NewTaskList(nil).
		AddTask(ux.TaskOptions{
			Title: "Validating artifacts",
			Action: func(spf ux.SetProgressFunc) (ux.TaskState, error) {
				files, err := filepath.Glob(flags.artifacts)
				if err != nil {
					return ux.Error, common.NewDetailedError("Artifacts not found",
						fmt.Errorf("failed to find artifacts: %w", err),
					)
				}

				if len(files) == 0 {
					return ux.Error, common.NewDetailedError("Artifacts not found",
						fmt.Errorf("no artifacts found at path: %s.", flags.artifacts),
					)
				}

				spf(fmt.Sprintf("Found %d artifacts", len(files)))
				args = append(args, files...)

				return ux.Success, nil
			},
		}).
		AddTask(
			ux.TaskOptions{
				Title: "Creating Github release",
				Action: func(spf ux.SetProgressFunc) (ux.TaskState, error) {
					// #nosec G204: Subprocess launched with variable
					ghReleaseCmd := exec.Command("gh", args...)
					ghReleaseCmd.Dir = absExtensionPath

					resultBytes, err := ghReleaseCmd.CombinedOutput()
					releaseResult = string(resultBytes)
					if err != nil {
						return ux.Error, common.NewDetailedError(
							"Release failed",
							errors.New(releaseResult),
						)
					}

					return ux.Success, nil
				},
			})

	if err := taskList.Run(); err != nil {
		return err
	}

	release, err := getGithubRelease(absExtensionPath, flags.repository, tagName)
	if err != nil {
		return err
	}

	fmt.Printf("%s: %s - %s\n",
		output.WithBold("GitHub Release"),
		release.Name,
		output.WithHyperlink(release.Url, "View Release"),
	)
	fmt.Println()

	return nil
}