diff --git a/.env.sh b/.env.sh deleted file mode 100755 index 6c9146b..0000000 --- a/.env.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash - -github_token="$GITHUB_TOKEN" -if [[ -z "$github_token" ]]; then - echo "Please provide a GitHub token. You can create one at:" - echo " https://github.com/settings/tokens/new?scopes=repo,read:user,user:email,write:packages" - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "GitHub token: " - read -s github_token -fi - -github_user_name="$GITHUB_USER_NAME" -if [[ -z "$github_user_name" ]]; then - github_user_name="$(git config --global user.name)" -fi -if [[ -z "$github_user_name" ]]; then - echo "Please provide a GitHub user name. You can also configure it with:" - echo " git config --global user.name \"Your Name\"" - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "GitHub user name: " - read github_user_name -fi - -github_user_email="$GITHUB_USER_EMAIL" -if [[ -z "$github_user_email" ]]; then - github_user_email="$(git config --global user.email)" -fi -if [[ -z "$github_user_email" ]]; then - echo "Please provide a GitHub user email. You can also configure it with:" - echo " git config --global user.email \"Your Email\"" - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "GitHub user email: " - read github_user_email -fi - -if [[ -z "$NO_GPG" ]]; then - gpg_id="$GPG_ID" - if [[ -z "$gpg_id" ]]; then - gpg_id="$(git config --global user.signingkey)" - fi - if [[ -z "$gpg_id" ]]; then - echo "Please provide a GPG ID. You can also configure it by following:" - echo " https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key" - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "GPG ID: " - read gpg_id - fi - if [[ -n "$gpg_id" ]]; then - gpg_passphrase="$GPG_PASSPHRASE" - if [[ -z "$gpg_passphrase" ]]; then - echo "Please provide a GPG passphrase for the key $gpg_id." - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "GPG passphrase: " - read -s gpg_passphrase - fi - if [[ -n "$gpg_passphrase" ]]; then - gpg_key="$GPG_KEY" - if [[ -z "$gpg_key" ]]; then - gpg_key="$(gpg --armor --pinentry-mode=loopback --passphrase "$gpg_passphrase" --export-secret-key "$gpg_id" -w0 | base64 -w0)" - fi - fi - fi -fi - -if [[ -z "$NO_MATRIX" ]]; then - matrix_url="$MATRIX_URL" - if [[ -z "$matrix_url" ]]; then - echo "Please provide a Matrix URL. For example: https://matrix-client.matrix.org/" - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "Matrix URL: " - read matrix_url - fi - matrix_user="$MATRIX_USER" - if [[ -z "$matrix_user" ]]; then - echo "Please provide a Matrix username." - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "Matrix username: " - read matrix_user - fi - - matrix_password="$MATRIX_PASSWORD" - if [[ -z "$matrix_password" ]]; then - echo "Please provide a Matrix password." - echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." - echo "Matrix password: " - read -s matrix_password - fi -fi - -export GITHUB_TOKEN="$github_token" -export GITHUB_USER_NAME="$github_user_name" -export GITHUB_USER_EMAIL="$github_user_email" - -export NO_GPG="$NO_GPG" -export GPG_ID="$gpg_id" -export GPG_PASSPHRASE="$gpg_passphrase" -export GPG_KEY="$gpg_key" - -export NO_MATRIX="$NO_MATRIX" -export MATRIX_URL="$matrix_url" -export MATRIX_USER="$matrix_user" -export MATRIX_PASSWORD="$matrix_password" - -cat .env.template | envsubst > .env diff --git a/.env.sh b/.env.sh new file mode 120000 index 0000000..7f711a4 --- /dev/null +++ b/.env.sh @@ -0,0 +1 @@ +actions/embed/.env.sh \ No newline at end of file diff --git a/.env.template b/.env.template deleted file mode 100644 index 65cb8ff..0000000 --- a/.env.template +++ /dev/null @@ -1,13 +0,0 @@ -GITHUB_TOKEN=$GITHUB_TOKEN -GITHUB_USER_NAME=$GITHUB_USER_NAME -GITHUB_USER_EMAIL=$GITHUB_USER_EMAIL - -NO_GPG=$NO_GPG -GPG_ID=$GPG_ID -GPG_KEY=$GPG_KEY -GPG_PASSPHRASE=$GPG_PASSPHRASE - -NO_MATRIX=$NO_MATRIX -MATRIX_URL=$MATRIX_URL -MATRIX_USER=$MATRIX_USER -MATRIX_PASSWORD=$MATRIX_PASSWORD diff --git a/.env.template b/.env.template new file mode 120000 index 0000000..3976b8d --- /dev/null +++ b/.env.template @@ -0,0 +1 @@ +actions/embed/.env.template \ No newline at end of file diff --git a/actions/embed/.env.sh b/actions/embed/.env.sh new file mode 100755 index 0000000..8c3994c --- /dev/null +++ b/actions/embed/.env.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +github_token="$GITHUB_TOKEN" +if [[ -z "$github_token" ]]; then + echo "Please provide a GitHub token. You can create one at:" + echo " https://github.com/settings/tokens/new?scopes=repo,read:user,user:email,write:packages" + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "GitHub token: " + read -s github_token +fi + +github_user_name="$GITHUB_USER_NAME" +if [[ -z "$github_user_name" ]]; then + github_user_name="$(git config --global user.name)" +fi +if [[ -z "$github_user_name" ]]; then + echo "Please provide a GitHub user name. You can also configure it with:" + echo " git config --global user.name \"Your Name\"" + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "GitHub user name: " + read github_user_name +fi + +github_user_email="$GITHUB_USER_EMAIL" +if [[ -z "$github_user_email" ]]; then + github_user_email="$(git config --global user.email)" +fi +if [[ -z "$github_user_email" ]]; then + echo "Please provide a GitHub user email. You can also configure it with:" + echo " git config --global user.email \"Your Email\"" + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "GitHub user email: " + read github_user_email +fi + +if [[ -z "$NO_GPG" ]]; then + gpg_id="$GPG_ID" + if [[ -z "$gpg_id" ]]; then + gpg_id="$(git config --global user.signingkey)" + fi + if [[ -z "$gpg_id" ]]; then + echo "Please provide a GPG ID. You can also configure it by following:" + echo " https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key" + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "GPG ID: " + read gpg_id + fi + if [[ -n "$gpg_id" ]]; then + gpg_passphrase="$GPG_PASSPHRASE" + if [[ -z "$gpg_passphrase" ]]; then + echo "Please provide a GPG passphrase for the key $gpg_id." + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "GPG passphrase: " + read -s gpg_passphrase + fi + if [[ -n "$gpg_passphrase" ]]; then + gpg_key="$GPG_KEY" + if [[ -z "$gpg_key" ]]; then + gpg_key="$(gpg --armor --pinentry-mode=loopback --passphrase "$gpg_passphrase" --export-secret-key "$gpg_id" -w0 | base64 -w0)" + fi + fi + fi +fi + +if [[ -z "$NO_MATRIX" ]]; then + matrix_url="$MATRIX_URL" + if [[ -z "$matrix_url" ]]; then + echo "Please provide a Matrix URL. For example: https://matrix-client.matrix.org/" + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "Matrix URL: " + read matrix_url + fi + matrix_user="$MATRIX_USER" + if [[ -z "$matrix_user" ]]; then + echo "Please provide a Matrix username." + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "Matrix username: " + read matrix_user + fi + + matrix_password="$MATRIX_PASSWORD" + if [[ -z "$matrix_password" ]]; then + echo "Please provide a Matrix password." + echo "If you don't want the value to be stored in a file, leave it empty and you will be prompted for it later." + echo "Matrix password: " + read -s matrix_password + fi +fi + +export GITHUB_TOKEN="$github_token" +export GITHUB_USER_NAME="$github_user_name" +export GITHUB_USER_EMAIL="$github_user_email" + +export NO_GPG="$NO_GPG" +export GPG_ID="$gpg_id" +export GPG_PASSPHRASE="$gpg_passphrase" +export GPG_KEY="$gpg_key" + +export NO_MATRIX="$NO_MATRIX" +export MATRIX_URL="$matrix_url" +export MATRIX_USER="$matrix_user" +export MATRIX_PASSWORD="$matrix_password" + + +# cat $ENV_TEMPLATE or fall back to .env.template +cat "${ENV_TEMPLATE:-.env.template}" | envsubst > .env diff --git a/actions/embed/.env.template b/actions/embed/.env.template new file mode 100644 index 0000000..65cb8ff --- /dev/null +++ b/actions/embed/.env.template @@ -0,0 +1,13 @@ +GITHUB_TOKEN=$GITHUB_TOKEN +GITHUB_USER_NAME=$GITHUB_USER_NAME +GITHUB_USER_EMAIL=$GITHUB_USER_EMAIL + +NO_GPG=$NO_GPG +GPG_ID=$GPG_ID +GPG_KEY=$GPG_KEY +GPG_PASSPHRASE=$GPG_PASSPHRASE + +NO_MATRIX=$NO_MATRIX +MATRIX_URL=$MATRIX_URL +MATRIX_USER=$MATRIX_USER +MATRIX_PASSWORD=$MATRIX_PASSWORD diff --git a/actions/env.go b/actions/env.go new file mode 100644 index 0000000..fd9c95f --- /dev/null +++ b/actions/env.go @@ -0,0 +1,59 @@ +package actions + +import ( + _ "embed" + "fmt" + "os" + + "github.com/ipfs/kuboreleaser/util" +) + +type Env struct{} + +//go:embed embed/.env.sh +var envScript string + +//go:embed embed/.env.template +var envTemplate string + +func (ctx Env) Check() error { + if _, err := os.Stat(".env"); os.IsNotExist(err) { + return fmt.Errorf("file .env does not exist yet in the current directory (%w)", ErrIncomplete) + } + return nil +} + +func (ctx Env) Run() error { + envScriptFile, err := os.CreateTemp("", ".env.*.sh") + if err != nil { + return err + } + _, err = envScriptFile.WriteString(envScript) + if err != nil { + return err + } + err = os.Chmod(envScriptFile.Name(), 0755) + if err != nil { + return err + } + envTemplateFile, err := os.CreateTemp("", ".env.*.sh") + if err != nil { + return err + } + _, err = envTemplateFile.WriteString(envTemplate) + if err != nil { + return err + } + + cmd := util.Command{ + Name: envScriptFile.Name(), + Stdin: os.Stdin, + Env: append(os.Environ(), fmt.Sprintf("ENV_TEMPLATE=%s", envTemplateFile.Name())), + } + err = cmd.Run() + if err != nil { + return err + } + + return nil +} diff --git a/actions/publish_to_github.go b/actions/publish_to_github.go index 7d553cf..0d623d3 100644 --- a/actions/publish_to_github.go +++ b/actions/publish_to_github.go @@ -71,7 +71,17 @@ func (ctx PublishToGitHub) Run() error { } } - _, err := ctx.GitHub.GetOrCreateRelease(repos.Kubo.Owner, repos.Kubo.Repo, ctx.Version.String(), ctx.Version.String(), body, ctx.Version.IsPrerelease()) + latestRelease, err := ctx.GitHub.GetLatestRelease(repos.Kubo.Owner, repos.Kubo.Repo) + if err != nil { + return err + } + + latestVersion, err := util.NewVersion(latestRelease.GetTagName()) + if err != nil { + return err + } + + _, err = ctx.GitHub.GetOrCreateRelease(repos.Kubo.Owner, repos.Kubo.Repo, ctx.Version.String(), ctx.Version.String(), body, ctx.Version.IsPrerelease(), ctx.Version.Compare(latestVersion) >= 0) if err != nil { return err } diff --git a/cmd/kuboreleaser/main.go b/cmd/kuboreleaser/main.go index 2a076e4..d0d235a 100644 --- a/cmd/kuboreleaser/main.go +++ b/cmd/kuboreleaser/main.go @@ -72,6 +72,17 @@ func Execute(action actions.IAction, c *cli.Context) error { return nil } +func ExecuteAll(actions []actions.IAction, c *cli.Context) error { + // execute actions one by one, fail if any of them fails + for _, action := range actions { + err := Execute(action, c) + if err != nil { + return err + } + } + return nil +} + func main() { app := &cli.App{ Name: "kuboreleaser", @@ -109,6 +120,14 @@ func main() { return nil }, Commands: []*cli.Command{ + { + Name: "env", + Usage: "Generate .env file in your current directory", + Action: func(c *cli.Context) error { + action := actions.Env{} + return Execute(action, c) + }, + }, { Name: "release", Usage: "Release Kubo", @@ -190,6 +209,44 @@ func main() { return Execute(action, c) }, }, + { + Name: "publish-to-all", + Usage: "Publish the release to DockerHub, distributions, NPM, and GitHub", + Action: func(c *cli.Context) error { + git, err := git.NewClient() + if err != nil { + return err + } + log.Debug("Initializing GitHub client...") + github, err := github.NewClient() + if err != nil { + return err + } + version := c.App.Metadata["version"].(*util.Version) + + actions := []actions.IAction{ + &actions.PublishToDockerHub{ + GitHub: github, + Version: version, + }, + &actions.PublishToDistributions{ + Git: git, + GitHub: github, + Version: version, + }, + &actions.PublishToNPM{ + GitHub: github, + Version: version, + }, + &actions.PublishToGitHub{ + GitHub: github, + Version: version, + }, + } + + return ExecuteAll(actions, c) + }, + }, { Name: "publish-to-distributions", Usage: "Publish the release to distributions", diff --git a/github/github.go b/github/github.go index 772a543..c3e619e 100644 --- a/github/github.go +++ b/github/github.go @@ -662,6 +662,28 @@ func (c *Client) GetWorkflowRunLogs(owner, repo string, id int64) (*WorkflowRunL return logs, nil } +func (c *Client) GetLatestRelease(owner, repo string) (*github.RepositoryRelease, error) { + log.WithFields(log.Fields{ + "owner": owner, + "repo": repo, + }).Debug("Searching for latest release...") + + r, _, err := c.v3.Repositories.GetLatestRelease(context.Background(), owner, repo) + if err != nil && strings.Contains(err.Error(), "404") { + return nil, nil + } + + if r != nil { + log.WithFields(log.Fields{ + "url": r.GetHTMLURL(), + }).Debug("Found latest release") + } else { + log.Debug("Latest release not found") + } + + return r, err +} + func (c *Client) GetRelease(owner, repo, tag string) (*github.RepositoryRelease, error) { log.WithFields(log.Fields{ "owner": owner, @@ -685,7 +707,7 @@ func (c *Client) GetRelease(owner, repo, tag string) (*github.RepositoryRelease, return r, err } -func (c *Client) CreateRelease(owner, repo, tag, name, body string, prerelease bool) (*github.RepositoryRelease, error) { +func (c *Client) CreateRelease(owner, repo, tag, name, body string, prerelease bool, latest bool) (*github.RepositoryRelease, error) { log.WithFields(log.Fields{ "owner": owner, "repo": repo, @@ -693,13 +715,20 @@ func (c *Client) CreateRelease(owner, repo, tag, name, body string, prerelease b "name": name, "body": body, "prerelease": prerelease, + "latest": latest, }).Debug("Creating release...") + makeLatest := "false" + if latest { + makeLatest = "true" + } + r, _, err := c.v3.Repositories.CreateRelease(context.Background(), owner, repo, &github.RepositoryRelease{ TagName: &tag, Name: &name, Body: &body, Prerelease: &prerelease, + MakeLatest: &makeLatest, }) if r != nil { @@ -713,13 +742,13 @@ func (c *Client) CreateRelease(owner, repo, tag, name, body string, prerelease b return r, err } -func (c *Client) GetOrCreateRelease(owner, repo, tag, name, body string, prerelease bool) (*github.RepositoryRelease, error) { +func (c *Client) GetOrCreateRelease(owner, repo, tag, name, body string, prerelease bool, latest bool) (*github.RepositoryRelease, error) { r, err := c.GetRelease(owner, repo, tag) if err != nil { return nil, err } if r == nil { - return c.CreateRelease(owner, repo, tag, name, body, prerelease) + return c.CreateRelease(owner, repo, tag, name, body, prerelease, latest) } return r, nil } diff --git a/util/command.go b/util/command.go index 5e9b46a..4ed82e5 100644 --- a/util/command.go +++ b/util/command.go @@ -23,6 +23,8 @@ type Command struct { Dir string Stdout io.Writer Stderr io.Writer + Stdin io.Reader + Env []string } func (c *Command) Run() error { @@ -46,5 +48,11 @@ func (c *Command) Run() error { } else { cmd.Stderr = os.Stderr } + if c.Stdin != nil { + cmd.Stdin = c.Stdin + } + if c.Env != nil { + cmd.Env = c.Env + } return cmd.Run() } diff --git a/util/version.go b/util/version.go index 50710dc..7eed6cf 100644 --- a/util/version.go +++ b/util/version.go @@ -19,6 +19,10 @@ func NewVersion(version string) (*Version, error) { return &Version{Version: version}, nil } +func (v Version) Compare(other *Version) int { + return semver.Compare(v.Version, other.Version) +} + func (v Version) MajorMinor() string { return semver.MajorMinor(v.Version) }