Skip to content

Commit

Permalink
build: add script to rehearse a release publish (#1012)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlj95 authored Oct 31, 2024
1 parent ccb2c12 commit f6cc1b5
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 69 deletions.
38 changes: 34 additions & 4 deletions .releaserc.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
const { execSync } = require('child_process')

const useLocalBranch = Boolean(process.env.LOCAL_SEMANTIC_RELEASE_BRANCH)
const getCurrentBranch = () =>
execSync('git rev-parse --abbrev-ref HEAD', {
encoding: 'utf-8',
}).trim()
const repositoryUrl = process.env.LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL?.trim()
const isDotRepositoryUrl = repositoryUrl === '.'

/**
* @type {import('semantic-release').GlobalConfig}
*/
module.exports = {
repositoryUrl,
branches: [
//👇 Fake branch so that we can release beta versions in `main`
// until we can release 1.0.0
'semantic-release',
{ name: 'main', prerelease: 'beta', channel: false },
{
name: useLocalBranch ? getCurrentBranch() : 'main',
prerelease: 'beta',
// ⚠️ Default channel is `undefined` for first release branch, but branch name for the rest.
// Using `false` to indicate default distribution channel
// https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#branches-properties
channel: false,
},
],
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
// When running locally with repository URL set to `.`, it fails
// As tries to read parts from `.` which is not a URL:
// https://github.com/semantic-release/release-notes-generator/blob/v13.0.0/index.js#L37-L39
!isDotRepositoryUrl
? '@semantic-release/release-notes-generator'
: undefined,
[
'@semantic-release/npm',
//👇 Publish built version
Expand All @@ -20,14 +43,21 @@ module.exports = {
npmPublish: true,
},
],
['@semantic-release/github', { assets: './projects/ngx-meta/dist/*.tgz' }],
// When using `.` as repository, interest is in publishing
// Hence there's no need for GitHub release, issue comments...
!isDotRepositoryUrl
? [
'@semantic-release/github',
{ assets: './projects/ngx-meta/dist/*.tgz' },
]
: undefined,
[
'@semantic-release/changelog',
{
changelogFile: 'projects/ngx-meta/src/CHANGELOG.md',
},
],
],
].filter((plugin) => !!plugin),
//👇 Add library name in tag
tagFormat: 'ngx-meta-v${version}',
preset: 'conventionalcommits',
Expand Down
82 changes: 18 additions & 64 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,30 +366,22 @@ Check out [`ngx-meta` docs `README.md` to operate docs project](projects/ngx-met

[Semantic Release] is used to create releases by analyzing [commit messages' semantic information](#commit-messages). Here you'll find how to simulate a release.

#### Dry run: working on a branch
#### Working on a branch

Semantic Release [behaves differently depending on the checked out branch](https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#workflow-configuration).

If you're working on a branch and want to force a release, you can temporarily
add your branch to the release configuration.
If you're working on a branch and want to force a release as if that branch was the main branch, you can export an environment variable:

For instance, add the following to the `branches` configuration in the `.releaserc.js` file to simulate a beta release as if the branch was in the main branch

```json
{
"name": "current-branch-where-work-is-being-done",
"prerelease": "beta",
"channel": false
}
```shell
export LOCAL_SEMANTIC_RELEASE_BRANCH=true
```

##### Beware of the `channel` option

Default is `undefined` for first release branch, but branch name for the rest. So in order to properly simulate the release process, you'll have to manually configure that. [`false` can be used to indicate default distribution channel](https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#branches-properties).
This way, configuration will use the local branch as if it was the `main` branch. Keep reading to see the commands to run to actually simulate a release.

##### Branch needs to be pushed

Otherwise, it [does not exist for semantic release](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/branches/expand.js#L11). Same for commits on that branch. If not pushed, it's like they don't exist.
> [!NOTE] > **Branch needs to be pushed**.
> Otherwise, it [does not exist for semantic release](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/branches/expand.js#L11).
> Same for commits on that branch. If not pushed, it's like they don't exist.
> Unless you use `.` as repository URL as can be seen below in [dry run: publish](#dry-run-publish). Though beware of that.
#### Dry run: minimal

Expand All @@ -400,10 +392,6 @@ To have a minimal idea of what would happen when running the release process, yo
> That's a [hardcoded check](https://github.com/semantic-release/semantic-release/blob/v23.1.1/index.js#L86-L102). So there's unfortunately no way to bypass this
> If you don't have permissions, check out the [next dry run approach](#dry-run-publish)
> [!TIP]
> 👇 Env vars are needed for plugins which check authentication (GitHub & NPM).
> You can also comment them out in the configuration file if you're not interested in how they behave. If interested in them and worrying about security, don't: read-only tokens are okay.
First, some env vars need to be defined. Check out [sample release env file] to see which ones with current configuration.

[sample release env file]: .env.release.sample
Expand All @@ -428,7 +416,7 @@ This is useful to know the version bumped and release notes generated, but prepa

#### "Dry run": publish

What if you want to simulate what would be published to the NPM registry? Or how the generated release notes would look in GitHub? Or how the generated CHANGELOG.md would look like? Keep reading to see how to safely test that.
What if you want to simulate what would be published to the NPM registry? Or how the generated release notes would look in GitHub? Or how the generated `CHANGELOG.md` would look like? Keep reading to see how to safely test that.

However, bear in mind that it won't be a dry run (hence the quotes). Semantic Release will be called as if actually performing a release, but with a different registry and repository. There's [no way of dry running a release with semantic release that includes prepare, publish, success or failure steps](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/definitions/plugins.js#L72).

Expand Down Expand Up @@ -492,7 +480,7 @@ To copy all existing tags needed to simulate a release at the current state. Or
Then, when using [Semantic Release], set the repository URL to this new repository:

```shell
pnpm semantic-release --repository-url https://github.com/user/ngx.git
export LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL=https://github.com/user/ngx.git
```

**⚡️ TRICKY (but fastest): Use a noop git remote**
Expand All @@ -504,16 +492,9 @@ A trick that can be done is to [create a dummy remote that does nothing](https:/
This way Semantic Release will push everything to this no-op repository URL / remote URL.

```shell
pnpm semantic-release --repository-url .
export LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL=.
```

> [!CAUTION]
> Disable the GitHub plugin in the configuration if using this approach.
> Otherwise release notes & comments will be created as usual. That's another reason not to use this approach.
> [!IMPORTANT]
> Semantic Release's release notes generator plugin needs to be disabled if using this approach. As otherwise [it fails when trying to read parts from `.` which is not a URL](https://github.com/semantic-release/release-notes-generator/blob/v13.0.0/index.js#L37-L39)
> [!NOTE]
> You could use `.` as the URL for the `origin` git remote. [Semantic Release would grab that and use it for the repository URL](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/get-config.js#L73) if not set anywhere else. But it would end up [trying to be converted to a URL](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/get-git-auth-url.js#L79) anyway. Plus the repository URL may be grabbed from somewhere else therefore using the real repository to operate, which would end up making real changes in there. Also the remote to change would have to be `origin`, which is [hardcoded in the implementation](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/git.js#L174). Finally, [`pushURL` is not respected by semantic release](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/git.js#L234-L236) so the `origin` URL has to be changed.
Expand All @@ -529,55 +510,28 @@ git notes prune -v # to clear notes about non-existing tags
# 👆 Seems tag deletion already deletes the note though
```

If using Semantic Release's git plugin (or another that writes to git), you'll need to remove the created commit by it too.
If using Semantic Release's `git` plugin (or another that writes to `git`), you'll need to remove the created commit by it too.

##### Running the release

With the private registry setup and the new repository created, you can run the release.

Bear in mind to [mangle the configuration if working on a branch](#dry-run-working-on-a-branch). Set up the needed env vars [as seen in minimal dry run](#dry-run-minimal). With following differences:
Bear in mind to [configure if working on a branch](#working-on-a-branch). Set up the needed env vars [as seen in minimal dry run](#dry-run-minimal). With following differences:

- **GitHub**: if just interested in NPM publish or using the tricky approach for the repository to use, you can skip that part by commenting out the plugin in the configuration. Otherwise, provide a token that can write to the repository created for running the release simulation.
- **GitHub**: if using `.` as repository URL, you can skip configuring it. If just interested in NPM publish, you can skip that part by commenting out the plugin in the configuration. Otherwise, provide a token that can write to the repository created for running the release simulation.
- **NPM**: provide the auth token instead obtained in the [private local registry step](#private-local-registry)

Let's check out everything is ready:

```shell
npm config get registry
# 👆 Output should be local private registry URL, not NPMjs one
echo $NPM_TOKEN
# 👆 Output doesn't start with `npm_`
# Should return NPM auth token created for local registry
cat .releaserc.js
# 👆 GitHub plugin commented if:
# - Env var not configured
# - Read-only access token provided
# - Using local repository trick
# Release notes plugin generator commented if using local repository trick
# Branch configuration altered if working on a branch
```

> ![IMPORTANT]
> Just [one more thing](https://www.youtube.com/watch?v=hvlHi7iTdaw&t=6s) 😜
> [Verdaccio doesn't support `provenance`](https://github.com/orgs/verdaccio/discussions/3903)
> So disable `publishConfig.provenance` in the built `package.json`. Otherwise, Verdaccio won't accept the package submission.
Finally, run the release rehearsal
Finally, run the release rehearsal. It is a script wrapping a Semantic Release command that will ensure to publish to the local private registry.

```shell
pnpm semantic-release \
--repository-url <anotherRepository> \
--no-ci
pnpm semantic-release:local
```

> [!NOTE]
> If using the local repository trick [as a different repository](#different-repository), remember to do the clean-up!
> [!NOTE]
> To try again releasing the same release, you can [unpublish it from Verdaccio](https://github.com/verdaccio/verdaccio/issues/365#issuecomment-337901629):
>
> ```shell
> npm unpublish --force <pkg>@<version> --registry http://localhost:4873
> npm unpublish <pkg>@<version> --registry http://localhost:4873
> ```
>
> Be careful of specifying the registry. You don't want the package to be actually removed from NPM registry!
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"commit": "pnpx @commitlint/prompt-cli",
"validate-codecov-yml": "curl -X POST --data-binary @codecov.yml https://codecov.io/validate",
"instrument-for-coverage": "./instrument-for-coverage.sh",
"update": "ng update @angular/cli @angular/core @angular-eslint/schematics"
"update": "ng update @angular/cli @angular/core @angular-eslint/schematics",
"semantic-release:local": "./semantic-release-local.sh"
},
"private": true,
"packageManager": "[email protected]",
Expand Down
93 changes: 93 additions & 0 deletions semantic-release-local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env sh
# Runs Semantic Release with a local private registry
# See contributing docs for more information

# Warn if not using local branch just in case
current_branch="$(git rev-parse --abbrev-ref HEAD)"
if [ -z "$LOCAL_SEMANTIC_RELEASE_BRANCH" ] && [ "$current_branch" != "main" ]; then
echo "💡 Publishing from local branch hasn't been configured. It's probable no release will be done"
echo " export LOCAL_SEMANTIC_RELEASE_BRANCH=true # to use local branch as if it was main one"
fi

# Ensure repository URL set
if [ -z "$LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL" ]; then
echo "❌ Repository URL hasn't been configured. Set one to avoid publishing to real one"
echo " export LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL=. # for the tricky one to work locally"
echo " export LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL=https://github.com/user/ngx.git # to use a copy/fork"
exit 1
else
echo "👍 Repository URL set to \"$LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL\""
fi

set -eu

# Change cwd to this script's directory
cd "$(dirname "$0")"

DIST_PACKAGE_JSON='projects/ngx-meta/dist/package.json'

# Ensure package.json exists
if ! [ -r "$DIST_PACKAGE_JSON" ]; then
echo "❌ Package JSON file '$DIST_PACKAGE_JSON' does not exist or is not readable"
echo " Build the library first"
exit 1
else
echo "👍 Package JSON file exists"
fi

# Ensure not logged in to `npm` registry
NPM_REGISTRY='https://registry.npmjs.org/'
if npm whoami --registry "$NPM_REGISTRY" &>/dev/null; then
echo "⚠️ You are authorized to use the real NPM registry"
echo " For safety, you should comment the .npmrc file where credentials are set"
echo " Otherwise if something goes wrong, something could be published there"
read -p " Do you want to continue anyway (yes/no)? " yn
case $yn in
[Yy]*) continue ;;
[Nn]*) exit 1 ;;
*) echo "Please answer yes or no." ;;
esac
else
echo "👍 Not logged in to real NPM registry. Nice caution"
fi

# Tweak the package.json file
# - Registry: local registry
# - Provenance: disabled as Verdaccio doesn't support `provenance`:
# https://github.com/orgs/verdaccio/discussions/3903)
package_json="$(cat "$DIST_PACKAGE_JSON")"
cleanup() {
echo "ℹ️ Restoring original package JSON file"
echo "$package_json" >"$DIST_PACKAGE_JSON"
echo "✅ Done"
if [ "$LOCAL_SEMANTIC_RELEASE_REPOSITORY_URL" == "." ]; then
echo "ℹ️ If run was successful, remember to clear created objects"
echo " Like git tags if any: 'git tag -d <tag>'"
fi
}
trap cleanup EXIT INT HUP

LOCAL_REGISTRY_URL='http://localhost:4873'
tweaked_package_json="$(
jq ".publishConfig.provenance=false | .publishConfig.registry=\"${LOCAL_REGISTRY_URL}\"" \
$DIST_PACKAGE_JSON
)"
echo "ℹ️ Tweaking package JSON file"
echo " Setting local registry and disabling provenance"
echo "$tweaked_package_json" >"$DIST_PACKAGE_JSON"

# Verify tweak
provenance="$(jq -r ".publishConfig.provenance" "$DIST_PACKAGE_JSON")"
if [ "$provenance" != "false" ]; then
echo "❌ Provenance wasn't updated. Can't proceed."
exit 1
fi
registry="$(jq -r ".publishConfig.registry" "$DIST_PACKAGE_JSON")"
if [ "$registry" != "$LOCAL_REGISTRY_URL" ]; then
echo "❌ Registry wasn't updated. Can't proceed."
exit 1
fi

# Run it
echo "ℹ️ Running semantic release"
pnpm semantic-release --no-ci

0 comments on commit f6cc1b5

Please sign in to comment.