Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ jobs:
CHECK_BASE_REF: origin/main
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
Expand All @@ -47,7 +47,7 @@ jobs:
- run: npm run check:links
- name: Upload tested npm package
if: matrix.node-version == '24.x'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, Node.js 24
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: npm-package
path: artifacts/*.tgz
Expand All @@ -64,10 +64,10 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with:
node-version: 24.x
registry-url: 'https://registry.npmjs.org'
Expand All @@ -77,7 +77,7 @@ jobs:
- name: Verify package version matches release tag
run: test "v$(node -p "require('./package.json').version")" = "$GITHUB_REF_NAME"
- name: Download tested npm package
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1, Node.js 24
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: npm-package
path: artifacts
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Dependabot auto-merge

on: pull_request

permissions:
contents: write
pull-requests: write

jobs:
auto-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- id: metadata
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98
- name: Auto-merge non-major Dependabot update
if: steps.metadata.outputs.update-type != 'version-update:semver-major'
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 changes: 25 additions & 3 deletions test/unit/docs.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { it } = require('node:test');
const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const { readdirSync, readFileSync } = require('node:fs');

it('uses preflight wording in user-facing docs', () => {
for (const file of ['README.md', 'SKILL.md']) {
Expand Down Expand Up @@ -53,15 +53,37 @@ it('keeps CI and release safety guarantees documented and wired', () => {
assert.match(workflow, /actions\/download-artifact@/);
assert.match(workflow, /npm publish \.\/artifacts\/\*\.tgz/);
assert.match(workflow, /matrix:/);
assert.match(workflow, /upload-artifact@043fb46d/);
assert.match(workflow, /download-artifact@3e5f45b2/);
assert.match(release, /DRY_RUN=1/);
assert.match(release, /--generate-notes/);
assert.match(releaseNotes, /categories:/);
assert.match(release, /Uncommitted changes.*before running release\.sh/);
assert.doesNotMatch(release, /git commit -m "sync skill metadata/);
});

it('pins every GitHub Action to a full immutable commit SHA', () => {
// Supply-chain invariant: assert the property (full 40-char SHA, no @v6/@main
// mutable refs) not a specific value, so dependabot bumps keep CI green.
const files = readdirSync('.github/workflows').filter((f) => f.endsWith('.yml'));
const refs = [];
for (const file of files) {
const workflow = readFileSync(`.github/workflows/${file}`, 'utf8');
refs.push(
...[...workflow.matchAll(/(?:uses|with:\s*using):\s+([^\s#@]+)@([^\s#]+)/g)].map(
(m) => [file, m[1], m[2]],
),
);
}
assert.ok(refs.length > 0, 'workflows should reference at least one GitHub Action');

for (const [file, name, ref] of refs) {
assert.match(
ref,
/^[0-9a-f]{40}$/,
`${file}: ${name} must be pinned to a full 40-character immutable commit SHA, got ${JSON.stringify(ref)}`,
);
}
});

it('documents and configures dependency freshness checks', () => {
const dependabot = readFileSync('.github/dependabot.yml', 'utf8');
const readme = readFileSync('README.md', 'utf8');
Expand Down