From a2f530161f10ae031ce51297e8f9a8c3cbd80cab Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 21 Jan 2022 16:28:56 -0700 Subject: [PATCH] feat: publish beta and development tags on npm (#4769) * feat: add logic to publish beta/dev npm This adds new log to publish the npm package both while working on PRs and when PRs are merged into main, allowing us to easily test changes in a production-like setting. Co-authored-by: Asher --- .github/workflows/ci.yaml | 9 +++ .github/workflows/npm-brew.yaml | 6 +- .github/workflows/npm-dev.yaml | 27 +++++++++ ci/steps/publish-npm.sh | 97 ++++++++++++++++++++++++++++++--- package.json | 1 + test/unit/node/settings.test.ts | 4 +- 6 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/npm-dev.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aed480e3..fdf7fe65 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -177,6 +177,15 @@ jobs: name: npm-package path: ./package.tar.gz + - name: Publish npm package with PR number and commit SHA + run: yarn publish:npm + env: + ENVIRONMENT: "development" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TAG: ${{ github.event.number }} + PR_NUMBER_AND_COMMIT_SHA: ${{ github.event.number }}-${{ github.event.pull_request.head.sha }} + # TODO: cache building yarn --production # possibly 2m30s of savings(?) # this requires refactoring our release scripts diff --git a/.github/workflows/npm-brew.yaml b/.github/workflows/npm-brew.yaml index fbc276a5..1bb54281 100644 --- a/.github/workflows/npm-brew.yaml +++ b/.github/workflows/npm-brew.yaml @@ -16,11 +16,13 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Run ./ci/steps/publish-npm.sh - run: ./ci/steps/publish-npm.sh + - name: Publish npm package with PR number and commit SHA + run: yarn publish:npm env: + ENVIRONMENT: "production" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TAG: "latest" homebrew: # The newest version of code-server needs to be available on npm when this runs diff --git a/.github/workflows/npm-dev.yaml b/.github/workflows/npm-dev.yaml new file mode 100644 index 00000000..b466c15a --- /dev/null +++ b/.github/workflows/npm-dev.yaml @@ -0,0 +1,27 @@ +name: Publish on npm and tag with "beta" + +on: + # Shows the manual trigger in GitHub UI + # helpful as a back-up in case the GitHub Actions Workflow fails + workflow_dispatch: + + push: + branches: + - main + +jobs: + # NOTE: this job requires curl, jq and yarn + # All of them are included in ubuntu-latest. + npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Run ./ci/steps/publish-npm.sh + run: yarn publish:npm + env: + ENVIRONMENT: "staging" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TAG: "beta" + PR_NUMBER_AND_COMMIT_SHA: ${{ github.event.number }}-${{ github.event.pull_request.head.sha }} diff --git a/ci/steps/publish-npm.sh b/ci/steps/publish-npm.sh index 0b3d8bd0..a4636db5 100755 --- a/ci/steps/publish-npm.sh +++ b/ci/steps/publish-npm.sh @@ -4,15 +4,60 @@ set -euo pipefail main() { cd "$(dirname "$0")/../.." source ./ci/lib.sh + source ./ci/steps/steps-lib.sh - # npm view won't exit with non-zero so we have to check the output. - local hasVersion - hasVersion=$(npm view "code-server@$VERSION" version) - if [[ $hasVersion == "$VERSION" ]]; then - echo "$VERSION is already published" - return + ## Authentication tokens + # Needed to publish on NPM + if ! is_env_var_set "NPM_TOKEN"; then + echo "NPM_TOKEN is not set. Cannot publish to npm without credentials." + exit 1 fi + # NOTE@jsjoeio - only needed if we use the download_artifact + # because we talk to the GitHub API. + # Needed to use GitHub API + if ! is_env_var_set "GITHUB_TOKEN"; then + echo "GITHUB_TOKEN is not set. Cannot download npm release artifact without GitHub credentials." + exit 1 + fi + + ## Environment + # This string is used to determine how we should tag the npm release. + # Environment can be one of three choices: + # "development" - this means we tag with the PR number, allowing + # a developer to install this version with `yarn add code-server@` + # "staging" - this means we tag with `beta`, allowing + # a developer to install this version with `yarn add code-server@beta` + # "production" - this means we tag with `latest` (default), allowing + # a developer to install this version with `yarn add code-server@latest` + if ! is_env_var_set "ENVIRONMENT"; then + echo "ENVIRONMENT is not set. Cannot determine npm tag without ENVIRONMENT." + exit 1 + fi + + ## Publishing Information + # All the variables below are used to determine how we should publish + # the npm package. We also use this information for bumping the version. + # This is because npm won't publish your package unless it's a new version. + # i.e. for development, we bump the version to -- + # example: "version": "4.0.1-4769-ad7b23cfe6ffd72914e34781ef7721b129a23040" + # We need the current package.json VERSION + if ! is_env_var_set "VERSION"; then + echo "VERSION is not set. Cannot publish to npm without VERSION." + exit 1 + fi + + # We need TAG to know what to publish under on npm + # Options are "latest", "beta", or "" + # See Environment comments above to know when each is used. + if ! is_env_var_set "NPM_TAG"; then + echo "NPM_TAG is not set. This is needed for tagging the npm release." + exit 1 + fi + + echo "using tag: $NPM_TAG" + + # This allows us to publish to npm in CI workflows if [[ ${CI-} ]]; then echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc fi @@ -24,7 +69,45 @@ main() { # Ignore symlink when publishing npm package # See: https://github.com/cdr/code-server/pull/3935 echo "node_modules.asar" > release/.npmignore - yarn publish --non-interactive release + + # NOTES:@jsjoeio + # We only need to run npm version for "development" and "staging". + # This is because our release:prep script automatically bumps the version + # in the package.json and we commit it as part of the release PR. + if [[ "$ENVIRONMENT" == "production" ]]; then + NPM_VERSION="$VERSION" + else + echo "Not a production environment" + echo "Found environment: $ENVIRONMENT" + echo "Manually bumping npm version..." + + if ! is_env_var_set "PR_NUMBER_AND_COMMIT_SHA"; then + echo "PR_NUMBER_AND_COMMIT_SHA is not set. This is needed for setting the npm version in non-production environments." + exit 1 + fi + + # We modify the version in the package.json + # to be the current version + the PR number + commit SHA + # Example: "version": "4.0.1-4769-ad7b23cfe6ffd72914e34781ef7721b129a23040" + NPM_VERSION="$VERSION-$PR_NUMBER_AND_COMMIT_SHA" + pushd release + # NOTE:@jsjoeio + # I originally tried to use `yarn version` but ran into issues and abandoned it. + npm version "$NPM_VERSION" + popd + fi + + # We need to make sure we haven't already published the version. + # This is because npm view won't exit with non-zero so we have + # to check the output. + local hasVersion + hasVersion=$(npm view "code-server@$NPM_VERSION" version) + if [[ $hasVersion == "$NPM_VERSION" ]]; then + echo "$NPM_VERSION is already published" + return + fi + + yarn publish --non-interactive release --tag "$NPM_TAG" } main "$@" diff --git a/package.json b/package.json index bb9ca607..595a3465 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test:scripts": "./ci/dev/test-scripts.sh", "package": "./ci/build/build-packages.sh", "postinstall": "./ci/dev/postinstall.sh", + "publish:npm": "./ci/steps/publish-npm.sh", "_audit": "./ci/dev/audit.sh", "fmt": "./ci/dev/fmt.sh", "lint": "./ci/dev/lint.sh", diff --git a/test/unit/node/settings.test.ts b/test/unit/node/settings.test.ts index 5931c799..68bf5178 100644 --- a/test/unit/node/settings.test.ts +++ b/test/unit/node/settings.test.ts @@ -14,7 +14,7 @@ describe("settings", () => { testDir = await tmpdir(testName) }) describe("with invalid JSON in settings file", () => { - let mockSettingsFile = "coder.json" + const mockSettingsFile = "coder.json" let pathToMockSettingsFile = "" beforeEach(async () => { @@ -33,7 +33,7 @@ describe("settings", () => { }) }) describe("with invalid settings file path", () => { - let mockSettingsFile = "nonExistent.json" + const mockSettingsFile = "nonExistent.json" let pathToMockSettingsFile = "" beforeEach(async () => {