diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml new file mode 100644 index 0000000..4685aa2 --- /dev/null +++ b/.github/workflows/canary.yml @@ -0,0 +1,37 @@ +name: Canary + +on: + workflow_dispatch: + inputs: + preid: + description: 'Pre-release identifier' + required: false + default: 'canary' + +permissions: + contents: read + id-token: write + +jobs: + canary: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + - run: npm install -g npm@latest + - run: npm ci + - run: npm run build + - run: lerna run test + - name: Publish canary packages + run: lerna publish --canary --preid ${{ inputs.preid }} --yes --no-private --ignore @hellocoop/web-identity + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a9540b0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + - run: npm ci + - run: npm run build + - run: npm run lint + - run: lerna run test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3c89815 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release + +on: + release: + types: [published] + +permissions: + contents: read + id-token: write + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + - run: npm ci + - run: npm run build + - run: lerna run test + + publish: + needs: test + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + - run: npm install -g npm@latest + - run: npm ci + - run: npm run build + - name: Publish packages with provenance (OIDC Trusted Publishing) + run: lerna publish from-package --yes --no-private --ignore @hellocoop/web-identity diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..b528d26 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +prettier --check $(git diff --cached --name-only --diff-filter=ACMR) diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..c3d8ad8 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +provenance=true +access=public diff --git a/lerna.json b/lerna.json index f650345..dac9aad 100644 --- a/lerna.json +++ b/lerna.json @@ -1,4 +1,9 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "independent" + "version": "independent", + "command": { + "publish": { + "ignoreChanges": ["web-identity/**"] + } + } } diff --git a/package-lock.json b/package-lock.json index c715c10..34a6457 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "eslint-plugin-svelte": "^3.3.3", "eslint-plugin-vue": "^10.0.0", "esm": "^3.2.25", + "husky": "^9.1.7", "lerna": "^8.1.8", "mocha": "^10.6.0", "npm-run-all": "^4.1.5", @@ -11221,6 +11222,22 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", diff --git a/package.json b/package.json index 461808d..0bc8d31 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,13 @@ "scripts": { "clean": "npm run prebuild --workspaces", "build": "lerna run build --sort", - "publish": "npm run build && lerna publish", - "publish:dry": "lerna publish --no-push --no-git-tag-version", - "publish:canary": "npm run build && lerna publish --canary", - "---": "-following may be out of date--", - "patch-sample": "npm run build && cp -r quickstart ../hello-nextjs-sample/node_modules/@hellocoop/ && cp -r nextjs ../hello-nextjs-sample/node_modules/@hellocoop/ && cp -r react ../hello-nextjs-sample/node_modules/@hellocoop/ && cp -r core ../hello-nextjs-sample/node_modules/@hellocoop/ && cp -r types ../hello-nextjs-sample/node_modules/@hellocoop/ && rm -rf ../hello-nextjs-sample/.next", + "test": "lerna run test", + "release": "./scripts/release.sh", + "publish:canary": "npm run build && lerna publish --canary --yes --no-private", "format": "prettier --write .", "lint": "prettier --check . && eslint --flag unstable_config_lookup_from_file . --fix", - "check": "npm run format && npm run lint" + "check": "npm run format && npm run lint", + "prepare": "husky" }, "author": { "name": "Hello Identity Co-op", @@ -54,6 +53,7 @@ "eslint-plugin-svelte": "^3.3.3", "eslint-plugin-vue": "^10.0.0", "esm": "^3.2.25", + "husky": "^9.1.7", "lerna": "^8.1.8", "mocha": "^10.6.0", "npm-run-all": "^4.1.5", diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..e826a7c --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -e + +# Usage: npm run release [-- patch|minor|major] +# Defaults to "patch" if no argument given. +# +# Runs tests, uses lerna to bump versions of changed packages, +# pushes commits and tags, then creates a GitHub Release +# which triggers the release.yml workflow to publish to npm with provenance. + +BUMP="${1:-patch}" + +if [[ "$BUMP" != "patch" && "$BUMP" != "minor" && "$BUMP" != "major" ]]; then + echo "Usage: npm run release [-- patch|minor|major]" + exit 1 +fi + +# Check for clean working tree +if ! git diff-index --quiet HEAD --; then + echo "Error: uncommitted changes. Please commit or stash them first." + exit 1 +fi + +# Check we're on main +BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [ "$BRANCH" != "main" ]; then + echo "Error: must be on main branch (currently on $BRANCH)" + exit 1 +fi + +# Check gh CLI is available +if ! command -v gh &> /dev/null; then + echo "Error: gh CLI not found. Install it: https://cli.github.com" + exit 1 +fi + +# Pull latest +echo "Pulling latest from origin..." +git pull origin main + +# Build all packages +echo "Building..." +npm run build + +# Run tests +echo "Running tests..." +lerna run test + +# Bump versions of changed packages (creates git commits and tags) +echo "Bumping $BUMP versions for changed packages..." +lerna version "$BUMP" --yes + +# Push commits and tags +echo "Pushing to origin..." +git push origin main --follow-tags + +# Get the tags that were just created +TAGS=$(git tag --points-at HEAD) +if [ -z "$TAGS" ]; then + echo "No tags found at HEAD. Nothing was versioned." + exit 0 +fi + +# Create a GitHub Release from the first tag (triggers release.yml) +RELEASE_TAG=$(echo "$TAGS" | head -1) +echo "Creating GitHub Release for $RELEASE_TAG..." +gh release create "$RELEASE_TAG" \ + --title "$RELEASE_TAG" \ + --generate-notes + +echo "" +echo "Release $RELEASE_TAG created!" +echo "The GitHub Actions workflow will now publish to npm with provenance." +echo "" +echo "All tags created:" +echo "$TAGS" +echo "" +echo "Monitor at: https://github.com/hellocoop/packages-js/actions"