diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6cf45350 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test-root: + name: Test @toppynl/twing + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10.30.0 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build library + run: npm run build:lib + + - name: Build test suite + run: npm run build:test + + - name: Run tests + run: npm test + + test-workspaces: + name: Test ${{ matrix.package.filter }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: + - filter: '@toppynl/twing-html-extra' + path: packages/html-extra + - filter: '@toppynl/twing-components' + path: packages/components + - filter: '@toppynl/twing-cache-extra' + path: packages/cache + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10.30.0 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build root (workspace dependency) + run: npm run build:lib + + - name: Build and test ${{ matrix.package.filter }} + run: | + pnpm --filter="${{ matrix.package.filter }}" run build:test + pnpm --filter="${{ matrix.package.filter }}" run test diff --git a/.github/workflows/release-core.yml b/.github/workflows/release-core.yml new file mode 100644 index 00000000..64a98a9c --- /dev/null +++ b/.github/workflows/release-core.yml @@ -0,0 +1,103 @@ +name: Release @toppynl/twing + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g. 8.0.0)' + required: true + type: string + dry_run: + description: 'Dry run — build and verify without publishing' + required: false + type: boolean + default: false + +jobs: + release-core: + name: Publish @toppynl/twing@${{ inputs.version }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + with: + version: 10.30.0 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Validate version + run: | + if ! echo "${{ inputs.version }}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then + echo "ERROR: '${{ inputs.version }}' is not a valid semver string" >&2 + exit 1 + fi + + - name: Update package.json version + run: | + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + pkg.version = '${{ inputs.version }}'; + fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + + - name: Build @toppynl/twing + env: + VERSION: ${{ inputs.version }} + run: npm run build:main + + - name: Verify target version + run: | + TARGET_VERSION=$(node -e "console.log(require('./src/main/target/package.json').version)") + if [[ "${TARGET_VERSION}" != "${{ inputs.version }}" ]]; then + echo "ERROR: target version ${TARGET_VERSION} != input ${{ inputs.version }}" >&2 + exit 1 + fi + echo "Verified: src/main/target/package.json@${TARGET_VERSION}" + + - name: Dry run publish + if: inputs.dry_run == true + run: | + cd src/main/target + npm publish --access public --dry-run + echo "DRY RUN complete — nothing published" + + - name: Publish @toppynl/twing + if: inputs.dry_run == false + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_TOPPYNL_ORG }} + run: | + cd src/main/target + npm publish --access public + + - name: Tag and commit + if: inputs.dry_run == false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add package.json + git commit -m "chore: bump @toppynl/twing to ${{ inputs.version }} [skip ci]" || true + git tag "twing-v${{ inputs.version }}" -m "Release @toppynl/twing@${{ inputs.version }}" + git push origin main --follow-tags + + - name: Create GitHub Release + if: inputs.dry_run == false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "twing-v${{ inputs.version }}" \ + --title "@toppynl/twing@${{ inputs.version }}" \ + --generate-notes \ + --notes-start-tag "twing-v$(git tag --sort=-version:refname | grep '^twing-v' | sed -n '2p')" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..3776b076 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,58 @@ +name: Release Workspace Packages + +on: + push: + branches: [main] + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release-workspaces: + name: Release workspace packages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: pnpm/action-setup@v4 + with: + version: 10.30.0 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build root (workspace dependency) + run: npm run build:lib + + - name: Release @toppynl/twing-html-extra + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_TOPPYNL_ORG }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_TOPPYNL_ORG }} + run: npx semantic-release --extends ./.releaserc/html-extra.json + + - name: Release @toppynl/twing-components + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_TOPPYNL_ORG }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_TOPPYNL_ORG }} + run: npx semantic-release --extends ./.releaserc/components.json + + - name: Release @toppynl/twing-cache-extra + if: hashFiles('packages/cache/package.json') != '' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_TOPPYNL_ORG }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_TOPPYNL_ORG }} + run: npx semantic-release --extends ./.releaserc/cache.json diff --git a/.releaserc/cache.json b/.releaserc/cache.json new file mode 100644 index 00000000..dcea4f13 --- /dev/null +++ b/.releaserc/cache.json @@ -0,0 +1,40 @@ +{ + "extends": "semantic-release-monorepo", + "pkgRoot": "packages/cache", + "tagFormat": "twing-cache-extra-v${version}", + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "packages/cache/CHANGELOG.md" + } + ], + [ + "@semantic-release/npm", + { + "npmPublish": false, + "pkgRoot": "packages/cache" + } + ], + [ + "@semantic-release/exec", + { + "publishCmd": "NEXT_VERSION=${nextRelease.version} bash scripts/publish-workspaces.sh @toppynl/twing-cache-extra packages/cache" + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "packages/cache/package.json", + "packages/cache/CHANGELOG.md" + ], + "message": "chore(release): @toppynl/twing-cache-extra@${nextRelease.version} [skip ci]" + } + ], + "@semantic-release/github" + ] +} diff --git a/.releaserc/components.json b/.releaserc/components.json new file mode 100644 index 00000000..8e10bcb1 --- /dev/null +++ b/.releaserc/components.json @@ -0,0 +1,40 @@ +{ + "extends": "semantic-release-monorepo", + "pkgRoot": "packages/components", + "tagFormat": "twing-components-v${version}", + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "packages/components/CHANGELOG.md" + } + ], + [ + "@semantic-release/npm", + { + "npmPublish": false, + "pkgRoot": "packages/components" + } + ], + [ + "@semantic-release/exec", + { + "publishCmd": "NEXT_VERSION=${nextRelease.version} bash scripts/publish-workspaces.sh @toppynl/twing-components packages/components" + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "packages/components/package.json", + "packages/components/CHANGELOG.md" + ], + "message": "chore(release): @toppynl/twing-components@${nextRelease.version} [skip ci]" + } + ], + "@semantic-release/github" + ] +} diff --git a/.releaserc/html-extra.json b/.releaserc/html-extra.json new file mode 100644 index 00000000..e2c3ceca --- /dev/null +++ b/.releaserc/html-extra.json @@ -0,0 +1,40 @@ +{ + "extends": "semantic-release-monorepo", + "pkgRoot": "packages/html-extra", + "tagFormat": "twing-html-extra-v${version}", + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "packages/html-extra/CHANGELOG.md" + } + ], + [ + "@semantic-release/npm", + { + "npmPublish": false, + "pkgRoot": "packages/html-extra" + } + ], + [ + "@semantic-release/exec", + { + "publishCmd": "NEXT_VERSION=${nextRelease.version} bash scripts/publish-workspaces.sh @toppynl/twing-html-extra packages/html-extra" + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "packages/html-extra/package.json", + "packages/html-extra/CHANGELOG.md" + ], + "message": "chore(release): @toppynl/twing-html-extra@${nextRelease.version} [skip ci]" + } + ], + "@semantic-release/github" + ] +} diff --git a/package.json b/package.json index 96db402b..7026f6a5 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,12 @@ "prebuild:test:browser": "npm run prebuild:test", "test": "node src/test/target/index.cjs && node src/test/target/entry.cjs", "test:browser": "browserify src/test/target/index.cjs | tape-run --sandbox=false", - "build:main": "(cd src/main && rollup -c) && npm run bundle && cp README.md src/main/target/README.md && cp LICENSE src/main/target/LICENSE", + "build:lib": "(cd src/main && rollup -c) && cp README.md src/main/target/README.md && cp LICENSE src/main/target/LICENSE", + "build:main": "npm run build:lib && npm run bundle", "build:test": "(cd src/test && rollup -c)", - "bundle": "browserify src/main/target/index.cjs -g uglifyify -s Twing -o bundle/lib.min.js", + "bundle": "mkdir -p bundle && browserify src/main/target/index.cjs -g uglifyify -s Twing -o bundle/lib.min.js", "build:workspaces": "pnpm -r --filter=\"./packages/*\" build:main", - "test:workspaces": "pnpm -r --filter=\"./packages/*\" test", - "changeset": "changeset", - "release": "changeset publish" + "test:workspaces": "pnpm -r --filter=\"./packages/*\" test" }, "dependencies": { "@nightlycommit/rollup-plugin-package-manifest": "^1.0.0-alpha.2", @@ -66,7 +65,17 @@ "tape": "^5.6.1", "tape-run": "^11.0.0", "twig-lexer": "^0.9.0", - "uglifyify": "^5.0.2", - "@changesets/cli": "^2.27.0" + "uglifyify": "^5.0.2" + }, + "devDependencies": { + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/commit-analyzer": "^13.0.1", + "@semantic-release/exec": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^11.0.1", + "@semantic-release/npm": "^12.0.1", + "@semantic-release/release-notes-generator": "^14.0.3", + "semantic-release": "^24.2.3", + "semantic-release-monorepo": "^8.0.3" } } diff --git a/scripts/publish-workspaces.sh b/scripts/publish-workspaces.sh new file mode 100755 index 00000000..b4f8315a --- /dev/null +++ b/scripts/publish-workspaces.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Usage: scripts/publish-workspaces.sh +# Env: NEXT_VERSION — set by @semantic-release/exec via ${nextRelease.version} + +PACKAGE_FILTER="${1}" +PACKAGE_PATH="${2}" +TARGET_DIR="${PACKAGE_PATH}/src/main/target" + +if [[ -z "${NEXT_VERSION:-}" ]]; then + echo "ERROR: NEXT_VERSION is not set" >&2 + exit 1 +fi + +echo "Publishing ${PACKAGE_FILTER}@${NEXT_VERSION} from ${TARGET_DIR}" + +VERSION="${NEXT_VERSION}" pnpm --filter="${PACKAGE_FILTER}" run build:main + +cd "${TARGET_DIR}" +npm publish --access public