diff --git a/.eslintignore b/.eslintignore index f221387..4bc4547 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ dist lib +coverage diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..5dfa701 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,23 @@ +name: Set up Node.js +description: Install Node.js and development dependencies + +inputs: + node-version: + description: Node.js version to install + default: "18" + install-command: + description: Install command to run + default: npm ci + +runs: + using: "composite" + steps: + - name: "Install Node.js" + uses: actions/setup-node@v3 + with: + node-version: ${{ inputs.node-version }} + cache: npm + + - name: "Install development dependencies" + shell: bash + run: ${{ inputs.install-command }} diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3c4886c..767cc5c 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,8 +1,3 @@ -# GitHub Actions workflow -# https://help.github.com/en/actions/automating-your-workflow-with-github-actions -# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions -# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions - name: CI-CD on: @@ -12,12 +7,28 @@ on: tags-ignore: - "*" + # run CI every Monday at 12:25 UTC schedule: - - cron: "0 0 1 * *" + - cron: "25 12 * * 1" jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout source + uses: actions/checkout@v3 + + - name: Install Node.js and dependencies + uses: ./.github/actions/setup + + - name: Run linters + run: npm run lint + test: - name: Node ${{ matrix.node }} on ${{ matrix.os }} + name: Run tests using Node ${{ matrix.node }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: @@ -36,17 +47,11 @@ jobs: - name: Checkout source uses: actions/checkout@v3 - - name: Install Node ${{ matrix.node }} - uses: actions/setup-node@v3 + - name: Install Node.js ${{ matrix.node }} and dependencies + uses: ./.github/actions/setup with: node-version: ${{ matrix.node }} - - name: Install dependencies - run: npm ci - - - name: Run linters - run: npm run lint - - name: Build the code run: npm run build @@ -54,7 +59,7 @@ jobs: run: npm run coverage - name: Send code coverage results to Coveralls - uses: coverallsapp/github-action@v1.1.0 + uses: coverallsapp/github-action@045a25193560dc194e405657f552faeb6b9433aa with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel: true @@ -66,31 +71,132 @@ jobs: needs: test steps: - name: Let Coveralls know that all tests have finished - uses: coverallsapp/github-action@v1.1.0 + uses: coverallsapp/github-action@045a25193560dc194e405657f552faeb6b9433aa with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true - deploy: - name: Publish to NPM - if: github.ref == 'refs/heads/master' + build: + name: Build runs-on: ubuntu-latest timeout-minutes: 10 - needs: test steps: - name: Checkout source uses: actions/checkout@v3 - - name: Install Node - uses: actions/setup-node@v3 - - - name: Install dependencies - run: npm ci + - name: Install Node.js and dependencies + uses: ./.github/actions/setup - - name: Build the code + - name: Build run: npm run build + - name: Upload publish artifact + uses: actions/upload-artifact@v3 + with: + name: publish-artifact + path: lib + + e2e: + name: Run end-to-end tests + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: build + + services: + verdaccio: + image: verdaccio/verdaccio:5 + ports: + - 4873:4873 + + steps: + - name: Checkout source + uses: actions/checkout@v3 + + - name: Install Node.js and dependencies + uses: ./.github/actions/setup + with: + install-command: npm install --production + + - name: Download publish artifact + uses: actions/download-artifact@v3 + with: + name: publish-artifact + path: lib + + - id: setup + name: Login to local registry and set up fixture package + shell: bash + run: | + echo "token=$(./e2e/00-login.sh)" >> "$GITHUB_OUTPUT" + echo "package=$(./e2e/01-setup-package.sh ./e2e/fixture 0.0.1)" >> "$GITHUB_OUTPUT" + + - name: Run CLI end-to-end test + shell: bash + env: + TOKEN: ${{ steps.setup.outputs.token }} + PACKAGE: ${{ steps.setup.outputs.package }} + run: | + ./e2e/02-publish.sh ${PACKAGE} ${TOKEN} + ./e2e/03-verify.sh ${PACKAGE} + ./e2e/02-publish.sh ${PACKAGE} ${TOKEN} + ./e2e/01-setup-package.sh ${PACKAGE} 0.0.2 + ./e2e/02-publish.sh ${PACKAGE} ${TOKEN} + ./e2e/03-verify.sh ${PACKAGE} + + - id: action-no-publish + name: Publish with already published version + uses: ./ + with: + registry: http://localhost:4873 + package: ${{ steps.setup.outputs.package }}/package.json + token: ${{ steps.setup.outputs.token }} + + - name: Check action output + if: ${{ steps.action-no-publish.outputs.type != 'none' }} + run: | + echo "::error::Expected release type to be 'none', got '${{ steps.action-no-publish.outputs.type }}'" + exit 1 + + - name: Create new version for Action end-to-end test + shell: bash + run: ./e2e/01-setup-package.sh ${{ steps.setup.outputs.package }} 0.0.3 + + - id: action-publish + name: Publish a new version + uses: ./ + with: + registry: http://localhost:4873 + package: ${{ steps.setup.outputs.package }}/package.json + token: ${{ steps.setup.outputs.token }} + + - name: Check release output + if: ${{ steps.action-publish.outputs.type != 'patch' }} + run: | + echo "::error::Expected release type to be 'patch', got '${{ steps.action-publish.outputs.type }}'" + exit 1 + + deploy: + if: ${{ github.ref == 'refs/heads/master' }} + name: Publish to NPM + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: + - lint + - test + - build + - e2e + + steps: + - name: Checkout source + uses: actions/checkout@v3 + + - name: Download publish artifact + uses: actions/download-artifact@v3 + with: + name: publish-artifact + path: lib + - name: Publish to NPM uses: ./ with: diff --git a/.gitignore b/.gitignore index d8a2384..f7ec77d 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ pids # Test output /.nyc_output /coverage +/e2e/fixture diff --git a/.prettierignore b/.prettierignore index b162b7b..1f2966d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,5 @@ dist lib +coverage +.nyc_output package-lock.json diff --git a/e2e/00-login.sh b/e2e/00-login.sh new file mode 100755 index 0000000..f85954c --- /dev/null +++ b/e2e/00-login.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Login into the registry and extract the auth token +# Usage: 00-login.sh + +set -e + +REGISTRY_HOSTNAME="localhost:4873" + +registry_url=http://${REGISTRY_HOSTNAME} +token_matcher='^\/\/'${REGISTRY_HOSTNAME}'\/:_authToken="(.+)"$' +temporary_dir=${RUNNER_TEMP:-${TMPDIR}} +npmrc=${temporary_dir}.npmrc-e2e + +{ +echo -e "test" +sleep 1 +echo -e "test" +} | NPM_CONFIG_USERCONFIG=${npmrc} npm login --registry ${registry_url} > /dev/null 2>&1 + +echo "DEBUG: wrote config to temporary file: ${npmrc}" 1>&2 + +npmrc_contents=$(<${npmrc}) +rm -f ${npmrc} + +if [[ "${npmrc_contents}" =~ ${token_matcher} ]]; then + echo "${BASH_REMATCH[1]}" + exit 0 +fi + +echo "ERROR: No token found" 1>&2 +exit 1 diff --git a/e2e/01-setup-package.sh b/e2e/01-setup-package.sh new file mode 100755 index 0000000..4a37df2 --- /dev/null +++ b/e2e/01-setup-package.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Create a test fixture package +# Usage: 01-setup-package.sh + +set -e + +PACKAGE_SPEC=$1 +PACKAGE_VERSION=$2 + +package_manifest=${PACKAGE_SPEC}/package.json + +mkdir -p ${PACKAGE_SPEC} + +echo "{" > ${package_manifest} +echo " \"name\": \"@jsdevtools/fixture\"," >> ${package_manifest} +echo " \"version\": \"${PACKAGE_VERSION}\"" >> ${package_manifest} +echo "}" >> ${package_manifest} + +echo "DEBUG: wrote ${package_manifest}" 1>&2 +echo ${PACKAGE_SPEC} diff --git a/e2e/02-publish.sh b/e2e/02-publish.sh new file mode 100755 index 0000000..cf38104 --- /dev/null +++ b/e2e/02-publish.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Publish a package to the registry using the CLI +# Usage: 02-publish.sh + +set -e + +REGISTRY_URL="http://localhost:4873" +PACKAGE_SPEC=$1 +TOKEN=$2 + +node ./bin/npm-publish --token=${TOKEN} --registry=${REGISTRY_URL} ${PACKAGE_SPEC}/package.json diff --git a/e2e/03-verify.sh b/e2e/03-verify.sh new file mode 100755 index 0000000..6b1ae7e --- /dev/null +++ b/e2e/03-verify.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Verify that a package was published +# Usage: 03-verify.sh + +set -e + +REGISTRY_URL="http://localhost:4873" +PACKAGE_SPEC=$1 + +package_name=$(cd $1 && npm pkg get name | sed 's/"//g') +package_version=$(cd $1 && npm pkg get version | sed 's/"//g') + +npm view $package_name@$package_version diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 0000000..b56edb0 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,44 @@ +# End-to-end tests + +This directory contains scripts to run end-to-end tests against a locally running [Verdaccio][] registry. + +These test are run automatically in CI, but can be run locally, too. + +[Verdaccio]: https://verdaccio.org/ + +## Usage + +### Launch a Verdaccio registry + +We use Docker to run a default instance of the Verdaccio server. + +```shell +docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio +``` + +### Setup + +Login to the local registry and create a fixture package. + +```shell +export TOKEN=$(./e2e/00-login.sh) +export PACKAGE=$(./e2e/01-setup-package.sh ./e2e/fixture 0.0.1) +``` + +### Test the CLI + +1. Publish the package to the registry. +2. Verify the package was published. +3. Try to publish again, verify publish is skipped. +4. Create a new version. +5. Publish the new version. +6. Verify the new version was published. + +```shell +./e2e/02-publish.sh ${PACKAGE} ${TOKEN} +./e2e/03-verify.sh ${PACKAGE} +./e2e/02-publish.sh ${PACKAGE} ${TOKEN} +./e2e/01-setup-package.sh ${PACKAGE} 0.0.2 +./e2e/02-publish.sh ${PACKAGE} ${TOKEN} +./e2e/03-verify.sh ${PACKAGE} +``` diff --git a/package.json b/package.json index ffdc40d..13f1ee2 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "lib" ], "scripts": { - "clean": "shx rm -rf .nyc_output coverage lib dist", + "clean": "shx rm -rf .nyc_output coverage lib dist e2e/fixture", "lint": "npm run _eslint && npm run _prettier -- --check", "format": "npm run _eslint -- --fix && npm run _prettier -- --write", "build": "npm run build:typescript && npm run build:ncc && npm run build:node_modules",