diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bab4b7e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,105 @@ +name: Release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+*" + +permissions: + contents: write + id-token: write + +concurrency: + group: release-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + publish: + name: Publish npm package and GitHub release + runs-on: ubuntu-latest + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + cache: npm + + - name: Read package metadata + id: package + shell: bash + run: | + name="$(node -p "require('./package.json').name")" + version="$(node -p "require('./package.json').version")" + tag="${GITHUB_REF_NAME}" + + if [ "$tag" != "v$version" ]; then + echo "Tag $tag does not match package version $version." >&2 + exit 1 + fi + + { + echo "name=$name" + echo "version=$version" + echo "tag=$tag" + } >> "$GITHUB_OUTPUT" + + - run: npm ci + - run: npm test + - run: npm run build + - run: npm run lint + - run: npm run typecheck + - run: npm run format:check + + - name: Create release tarball + id: pack + shell: bash + run: | + mkdir -p .release + npm pack --pack-destination .release + tarball="$(find .release -maxdepth 1 -name '*.tgz' -print -quit)" + + if [ -z "$tarball" ]; then + echo "npm pack did not create a tarball." >&2 + exit 1 + fi + + echo "tarball=$tarball" >> "$GITHUB_OUTPUT" + + - name: Check npm version + id: npm + shell: bash + run: | + if npm view "${{ steps.package.outputs.name }}@${{ steps.package.outputs.version }}" version --registry=https://registry.npmjs.org >/dev/null 2>&1; then + echo "published=true" >> "$GITHUB_OUTPUT" + else + echo "published=false" >> "$GITHUB_OUTPUT" + fi + + - name: Configure npm token fallback + if: ${{ env.NPM_TOKEN != '' }} + run: npm config set //registry.npmjs.org/:_authToken "$NPM_TOKEN" + + - name: Publish to npm + if: steps.npm.outputs.published == 'false' + run: npm publish "${{ steps.pack.outputs.tarball }}" --access public --provenance + + - name: Create or update GitHub release + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + tag="${{ steps.package.outputs.tag }}" + tarball="${{ steps.pack.outputs.tarball }}" + + if gh release view "$tag" >/dev/null 2>&1; then + gh release upload "$tag" "$tarball" --clobber + else + gh release create "$tag" "$tarball" \ + --verify-tag \ + --title "SpecGov $tag" \ + --generate-notes + fi diff --git a/.gitignore b/.gitignore index 2734d98..b9c4441 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ coverage/ .specgov.trace.json .specgov.report.json *.log +*.tgz diff --git a/.specgov.yml b/.specgov.yml index a6cf9dd..5fa7bcf 100644 --- a/.specgov.yml +++ b/.specgov.yml @@ -2,10 +2,26 @@ version: 1 mode: advisory artifacts: + - path: ".specgov.yml" + kind: configuration + owner: maintainers + status: active - path: "README.md" kind: documentation owner: maintainers status: active + - path: "CHANGELOG.md" + kind: documentation + owner: maintainers + status: active + - path: "CONTRIBUTING.md" + kind: documentation + owner: maintainers + status: active + - path: "RELEASING.md" + kind: documentation + owner: maintainers + status: active - path: "docs/**/*" kind: documentation owner: maintainers @@ -32,6 +48,17 @@ mappings: specs: - ".specs/features/specgov-core/**" - "README.md" + - "RELEASING.md" + - code: "package.json" + specs: + - ".specs/project/**" + - "README.md" + - "RELEASING.md" + - code: "package-lock.json" + specs: + - ".specs/project/**" + - "README.md" + - "RELEASING.md" rules: require_spec_impact_for_code_changes: true @@ -44,3 +71,4 @@ ignore: - "dist/**" - ".git/**" - "coverage/**" + - ".gitignore" diff --git a/.specs/project/ROADMAP.md b/.specs/project/ROADMAP.md index b80fc2c..aea6dc5 100644 --- a/.specs/project/ROADMAP.md +++ b/.specs/project/ROADMAP.md @@ -20,6 +20,7 @@ - README quickstart. - GitHub Pages product site for global project adoption. - Contribution and security docs. +- npm package distribution with tag-driven GitHub Releases. ## Later diff --git a/.specs/project/STATE.md b/.specs/project/STATE.md index 84fb81f..6bbd831 100644 --- a/.specs/project/STATE.md +++ b/.specs/project/STATE.md @@ -20,6 +20,9 @@ - GitHub's official checkout and setup-node v5 tags exist and avoid the Node 20 runtime warning emitted by v4. - Public project education lives in the README and the static GitHub Pages site under `docs/`. The Pages workflow deploys the `docs` directory from `main`. +- Public distribution is npm-first. Version tags drive `.github/workflows/release.yml`, + which validates the package, publishes npm when needed, and mirrors the + version as a GitHub Release. ## Deferred Ideas diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..03884bd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 0.1.0 - 2026-06-28 + +- Initial public CLI with `init`, `scan`, `check-pr`, `trace`, and `drift`. +- GitHub Action for advisory or strict pull request governance checks. +- Example manifests for docs-only, ADR-heavy, and framework-folder projects. +- Static documentation site for adoption guidance. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15903e7..2f0cfe8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,8 +10,14 @@ npm test npm run build npm run lint npm run typecheck +npm run format:check ``` +## Releases + +Releases are tag-driven. Follow [`RELEASING.md`](RELEASING.md) when publishing +a new npm version or GitHub Release. + ## Spec Governance SpecGov uses TLC Spec Driven internally, but the product itself is framework diff --git a/README.md b/README.md index aad9fa8..bbbf451 100644 --- a/README.md +++ b/README.md @@ -101,27 +101,22 @@ declared mappings, and reports whether the review has enough spec context. ## Installation -SpecGov is pre-release. Until the first npm package and version tag are -published, install it from source: +Install the CLI from npm: ```bash -git clone https://github.com/paladini/specgov.git -cd specgov -npm ci -npm run build -npm link +npm install -g specgov ``` -After `npm link`, the `specgov` command is available on your machine: +You can also run it without a global install: ```bash -specgov --help +npx specgov --help ``` -You can also run the local build directly: +After installation, the `specgov` command is available on your machine: ```bash -node dist/cli.js --help +specgov --help ``` ## Quick start @@ -260,15 +255,14 @@ jobs: with: fetch-depth: 0 - - uses: paladini/specgov@main + - uses: paladini/specgov@v0.1.0 with: mode: advisory base-ref: ${{ github.event.pull_request.base.sha }} head-ref: ${{ github.event.pull_request.head.sha }} ``` -Use `paladini/specgov@main` only while the project is pre-release. After a -version tag exists, pin the Action to that tag. +Pin the Action to a version tag for repeatable CI behavior. Use `mode: strict` when governance findings should block the pull request. @@ -388,6 +382,8 @@ are usable today, but may still change before the first tagged release. Current project links: - Website: +- npm: - Repository: - Roadmap: [`.specs/project/ROADMAP.md`](.specs/project/ROADMAP.md) +- Release process: [`RELEASING.md`](RELEASING.md) - Contribution guide: [`CONTRIBUTING.md`](CONTRIBUTING.md) diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..1a5fa1a --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,50 @@ +# Releasing SpecGov + +SpecGov releases are tag-driven. A pushed `vX.Y.Z` tag runs the release +workflow, validates the package, publishes the npm version when it is not +already present, and creates or updates the matching GitHub Release. + +## One-time npm setup + +The release workflow supports both npm Trusted Publishing and an `NPM_TOKEN` +repository secret. Trusted Publishing is preferred because it uses GitHub OIDC +instead of a long-lived token. + +For Trusted Publishing, configure the npm package with: + +- Package: `specgov` +- Repository: `paladini/specgov` +- Workflow: `release.yml` +- Environment: leave blank unless the workflow is later changed to use one + +Docs: + +If Trusted Publishing is not configured, add an `NPM_TOKEN` secret to the +GitHub repository before pushing a release tag. + +## Release a new version + +From a clean `main` branch: + +```bash +npm version patch +git push origin main --follow-tags +``` + +Use `npm version minor` or `npm version major` when the change warrants it. The +version commit updates `package.json` and `package-lock.json`, and the tag +triggers `.github/workflows/release.yml`. + +## Verify a release + +After the workflow completes, check: + +```bash +npm view specgov version +gh release view vX.Y.Z --repo paladini/specgov +npx specgov@latest --help +``` + +The first release can be bootstrapped locally with `npm publish --access public` +when the package has not yet been created on npm. After that, prefer the tag +workflow. diff --git a/docs/index.html b/docs/index.html index db50cb7..96c58ba 100644 --- a/docs/index.html +++ b/docs/index.html @@ -294,23 +294,15 @@

Check pull requests

>

Quick start

-

- Install from source, then govern any repo. -

+

Install from npm, then govern any repo.

- SpecGov is pre-release. Until the first npm package and version tag - are published, use the source build. + Use the CLI locally or in CI without adopting a new spec format.

Install
-
git clone https://github.com/paladini/specgov.git
-cd specgov
-npm ci
-npm run build
-npm link
+
npm install -g specgov
+npx specgov --help
Use in your repository
specgov init
 specgov scan
@@ -414,7 +406,7 @@ 

Put governance in the pull request.

- uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: paladini/specgov@main + - uses: paladini/specgov@v0.1.0 with: mode: advisory base-ref: ${{ github.event.pull_request.base.sha }} diff --git a/package.json b/package.json index 833e02a..b52e931 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,16 @@ "dist", "action.yml", "README.md", + "RELEASING.md", + "SECURITY.md", + "CHANGELOG.md", "LICENSE" ], "scripts": { "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"", "build": "npm run clean && tsc -p tsconfig.json && npm run build:action", "build:action": "ncc build src/action.ts -o dist/action -m --license licenses.txt", + "prepack": "npm run build", "test": "vitest run", "lint": "eslint .", "typecheck": "tsc -p tsconfig.json --noEmit", @@ -32,6 +36,17 @@ ], "author": "Fernando Paladini", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/paladini/specgov.git" + }, + "bugs": { + "url": "https://github.com/paladini/specgov/issues" + }, + "homepage": "https://github.com/paladini/specgov#readme", + "publishConfig": { + "access": "public" + }, "engines": { "node": ">=20" },