diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..27d50b1b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference + +version: 2 + +updates: + - package-ecosystem: npm + directory: / + target-branch: develop + schedule: + interval: weekly + open-pull-requests-limit: 10 + commit-message: + prefix: "chore(deps)" + prefix-development: "chore(deps-dev)" + groups: + dev-dependencies: + dependency-type: development + update-types: + - minor + - patch + + - package-ecosystem: github-actions + directory: / + target-branch: develop + schedule: + interval: weekly + commit-message: + prefix: "ci" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..1528147c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +# Pull request and branch checks: types, lint, formatting, tests with coverage, build, +# generated documentation, production dependency audit and production licence allow-list. + +name: ci + +on: + pull_request: + push: + branches: + - main + - develop + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + checks: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: setup-node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 24 + cache: npm + + - name: install + run: npm ci + + - name: type-check + run: npm run type:check + + - name: lint + run: npm run lint + + - name: format + run: npm run format + + - name: test + run: npm run test:cov + + - name: build + run: npm run build + + # The reference pages are generated from src/; this fails when they are stale. + - name: docs + run: npm run docs:check + + - name: readme-translations + run: npm run docs:check-i18n + + - name: audit + run: npm audit --omit=dev --audit-level=high + + # Production dependencies must use a permissive licence compatible with Apache-2.0. + - name: licences + run: >- + npx --yes license-checker-rseidelsohn@5.0.1 + --production + --excludePrivatePackages + --summary + --onlyAllow "Apache-2.0;MIT;BSD-2-Clause;BSD-3-Clause;ISC;0BSD" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 16db0d2c..a702f3ea 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,6 +17,10 @@ on: - ".github/workflows/docs.yml" workflow_dispatch: +# Read-only by default; only the deploy job is granted more. +permissions: + contents: read + # Allow one concurrent deployment; queued runs supersede each other rather than # racing to publish. concurrency: @@ -45,10 +49,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 22 cache: npm @@ -89,17 +95,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: Build docs using the Writerside Docker builder - uses: JetBrains/writerside-github-action@v4 + uses: JetBrains/writerside-github-action@738910d7cf197d67188300d0e0f84b2e708db3cf # v4 with: location: ${{ env.LOCATION }} instance: ${{ matrix.language.module }}/asu docker-version: ${{ env.DOCKER_VERSION }} - name: Upload build results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: docs-${{ matrix.language.code }} path: | @@ -124,13 +132,13 @@ jobs: steps: - name: Download build results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: docs-${{ matrix.language.code }} path: artifacts - name: Check for broken links and other problems - uses: JetBrains/writerside-checker-action@v1 + uses: JetBrains/writerside-checker-action@79ff89902dcd3d5c5a3f26a79bc35830377f38c5 # v1 with: instance: ${{ matrix.language.module }}/asu @@ -146,10 +154,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 22 cache: npm @@ -158,7 +168,7 @@ jobs: run: npm ci - name: Download build results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: docs-* path: artifacts @@ -205,6 +215,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 + # contents: write pushes the archive of earlier versions to the docs-archive branch. permissions: contents: write pages: write @@ -216,10 +227,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: Download build results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: docs-* path: artifacts @@ -300,13 +313,13 @@ jobs: run: node scripts/docs/write-help-versions.mjs dir "$(node -p "require('./package.json').version")" - name: Setup Pages - uses: actions/configure-pages@v4 + uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0 - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 with: path: dir - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 86901e8e..47006083 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -5,19 +5,30 @@ on: push: branches: - main + # Publishes an existing release by hand (for example one released before this job existed). + workflow_dispatch: + inputs: + tag: + description: "Release tag to publish to npm, e.g. apps-script-utils-v3.0.0" + required: true -permissions: - contents: write - issues: write - pull-requests: write +# Every job declares only the permissions it needs. +permissions: {} name: release-please jobs: release-please: - if: github.server_url == 'https://github.com' && github.actor != 'github-actions[bot]' + if: github.event_name == 'push' && github.server_url == 'https://github.com' && github.actor != 'github-actions[bot]' runs-on: ubuntu-latest timeout-minutes: 8 + permissions: + contents: write + issues: write + pull-requests: write + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: # Manifest mode, not `release-type`. Without a manifest the action looks for @@ -25,10 +36,85 @@ jobs: # window it searches no longer reaches v1.10.0 — it concluded nothing had # ever been released and proposed 1.0.0. - name: release-please-action - uses: googleapis/release-please-action@v4.4.0 + id: release + uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1 with: token: ${{ secrets.GITHUB_TOKEN }} config-file: release-please-config.json manifest-file: .release-please-manifest.json -# versioning-strategy: auto -# changelog-path: CHANGELOG.md + + # npm trusted publishing (OIDC): no npm token is stored in GitHub. Needs a trusted publisher on + # npmjs.com → @bootgs/utils → Settings → Trusted publishing: GitHub Actions, bootgs/utils, + # workflow release-please.yml, environment npm. + publish: + needs: release-please + if: >- + always() && github.server_url == 'https://github.com' && + ((github.event_name == 'push' && needs.release-please.outputs.release_created == 'true') || + github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: npm + permissions: + contents: read + id-token: write + + steps: + - name: checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.release-please.outputs.tag_name }} + persist-credentials: false + + - name: setup-node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + - name: install + run: npm ci + + - name: build + run: npm run build + + - name: test + run: npm test + + - name: publish + run: npm publish --provenance --access public + + # CycloneDX SBOM of the production dependency tree, attached to the GitHub release. + sbom: + needs: [release-please, publish] + if: >- + always() && needs.publish.result == 'success' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + env: + TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.release-please.outputs.tag_name }} + + steps: + - name: checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ env.TAG }} + persist-credentials: false + + - name: setup-node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 24 + + - name: install + run: npm ci --ignore-scripts + + - name: sbom + run: npm sbom --sbom-format cyclonedx --omit dev > sbom.cdx.json + + - name: upload + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "$TAG" sbom.cdx.json --clobber --repo "$GITHUB_REPOSITORY" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 00000000..d32d11ef --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,52 @@ +# OpenSSF Scorecard supply-chain security analysis. +# https://github.com/ossf/scorecard-action + +name: scorecard + +on: + # For the Branch-Protection check; only the default branch is supported. + branch_protection_rule: + schedule: + - cron: "17 4 * * 1" + push: + branches: + - main + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + if: github.server_url == 'https://github.com' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + # Upload the results to code scanning. + security-events: write + # Publish the results and get a badge (see publish_results below). + id-token: write + + steps: + - name: checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: analysis + uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: artifact + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: upload-sarif + uses: github/codeql-action/upload-sarif@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2 + with: + sarif_file: results.sarif diff --git a/config/tsconfig.docs.json b/config/tsconfig.docs.json index 6246b36a..06db5d2b 100644 --- a/config/tsconfig.docs.json +++ b/config/tsconfig.docs.json @@ -22,7 +22,7 @@ "baseUrl": "..", "paths": { "@/*": ["src/*"], - "apps-script-utils": ["src/index.ts"] + "@bootgs/utils": ["src/index.ts"] } }, diff --git a/package.json b/package.json index d9092cea..bffef344 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "lint": "eslint .", "lint:fix": "eslint --fix .", "test": "vitest run", + "test:cov": "vitest run --coverage", "build": "rm -rf dist/* && tsc -b --force", "build:gas": "npm run build && node scripts/gas/bundle.mjs scripts/gas/Library.js", "docs:generate": "node scripts/docs/generate-topics.mjs",