diff --git a/.github/release-rules/checks.json b/.github/release-rules/checks.json new file mode 100644 index 0000000..bc5d123 --- /dev/null +++ b/.github/release-rules/checks.json @@ -0,0 +1,49 @@ +{ + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main" + ], + "exclude": [] + } + }, + "name": "Gem release checks", + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + } + ], + "rules": [ + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [ + { + "context": "Test Ruby 3.3" + }, + { + "context": "Test Ruby 3.4" + }, + { + "context": "Test Ruby 4.0" + }, + { + "context": "RuboCop" + }, + { + "context": "Documentation coverage" + }, + { + "context": "Release validation" + } + ] + } + } + ] +} diff --git a/.github/release-rules/history.json b/.github/release-rules/history.json new file mode 100644 index 0000000..e6f4af1 --- /dev/null +++ b/.github/release-rules/history.json @@ -0,0 +1,22 @@ +{ + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main" + ], + "exclude": [] + } + }, + "name": "Gem release history", + "bypass_actors": [], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + } + ] +} diff --git a/.github/release-rules/reviews.json b/.github/release-rules/reviews.json new file mode 100644 index 0000000..a6bda35 --- /dev/null +++ b/.github/release-rules/reviews.json @@ -0,0 +1,36 @@ +{ + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main" + ], + "exclude": [] + } + }, + "name": "Gem release reviews", + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + } + ], + "rules": [ + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 2, + "dismiss_stale_reviews_on_push": true, + "require_last_push_approval": true, + "required_review_thread_resolution": true, + "require_code_owner_review": false, + "allowed_merge_methods": [ + "merge", + "squash" + ] + } + } + ] +} diff --git a/.github/release-rules/tags.json b/.github/release-rules/tags.json new file mode 100644 index 0000000..8f7a46f --- /dev/null +++ b/.github/release-rules/tags.json @@ -0,0 +1,22 @@ +{ + "name": "Gem release tags", + "target": "tag", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { + "include": [ + "refs/tags/v*" + ], + "exclude": [] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + } + ] +} diff --git a/templates/RELEASING.md b/.github/releasing.md similarity index 91% rename from templates/RELEASING.md rename to .github/releasing.md index 59d424e..47343ae 100644 --- a/templates/RELEASING.md +++ b/.github/releasing.md @@ -4,7 +4,7 @@ Prepare releases with `bundle exec bake gem:github:release:patch` (or `minor` / Review the entire release diff. Native GitHub rules require the configured reviews and CI, including an up-to-date branch and **Release validation**. Administrators can explicitly bypass the review/check rules. A rebase is sufficient only if regenerated content still matches. If notes are stale, prepare again from the current default branch and preserve any manual edits separately for review. -Merging a version increase publishes the actual merged commit through `release-publish.yaml`. There is no second release approval. The public `release.cert` stays in Git; the optional private key belongs in the `rubygems` environment secret `GEM_SIGNING_KEY`. RubyGems authentication uses Trusted Publishing. +Merging a version increase publishes the actual merged commit through `release-publish.yaml`. There is no second release approval. The public `release.cert` stays in Git; the optional private key belongs in the Actions secret `GEM_SIGNING_KEY`, supplied by the `rubygems` environment or an organization secret. RubyGems authentication uses Trusted Publishing. If a publishing job fails, use **Re-run all jobs** on that same workflow run, or `bundle exec bake gem:github:release:resume run=RUN_ID`. The original artifact is downloaded before another upload is attempted. Do not create a replacement version bump or rebuild an already-published version. Conflicting bytes or tags require investigation; never automatically yank a version or move a tag. diff --git a/.github/workflows/documentation-coverage.yaml b/.github/workflows/documentation-coverage.yaml new file mode 100644 index 0000000..41e79fa --- /dev/null +++ b/.github/workflows/documentation-coverage.yaml @@ -0,0 +1,23 @@ +name: Documentation coverage + +on: [push, pull_request] + +permissions: + contents: read + +env: + BUNDLE_WITH: maintenance + +jobs: + validate: + name: Documentation coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Check library documentation + timeout-minutes: 5 + run: bundle exec bake decode:index:coverage lib diff --git a/.github/workflows/release-prepare.yaml b/.github/workflows/release-prepare.yaml new file mode 100644 index 0000000..bfaa291 --- /dev/null +++ b/.github/workflows/release-prepare.yaml @@ -0,0 +1,45 @@ +name: Prepare release + +on: + workflow_dispatch: + inputs: + bump: + description: Version increment + required: true + type: choice + options: [patch, minor, major] + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-prepare + cancel-in-progress: false + +env: + BUNDLE_WITH: maintenance + +jobs: + prepare: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: main + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Create release PR + env: + GH_TOKEN: ${{ github.token }} + BUMP: ${{ inputs.bump }} + run: | + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + case "$BUMP" in patch|minor|major) ;; *) exit 1 ;; esac + bundle exec bake "gem:github:release:$BUMP" diff --git a/.github/workflows/release-publish.yaml b/.github/workflows/release-publish.yaml new file mode 100644 index 0000000..1b8d710 --- /dev/null +++ b/.github/workflows/release-publish.yaml @@ -0,0 +1,101 @@ +name: Publish release + +# Only merged source is executed. Validation of unmerged PRs lives in its own +# read-only pull_request workflow. This trigger also supports reviewed fork PRs. +on: + pull_request_target: + types: [closed] + branches: ["main"] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: release-publish + cancel-in-progress: false + +env: + BUNDLE_WITH: maintenance + RELEASE_PR: ${{ github.event.pull_request.number }} + +jobs: + inspect: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + outputs: + release: ${{ steps.inspect.outputs.release }} + commit: ${{ steps.inspect.outputs.commit }} + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + with: + ruby-version: "3.4" + bundler-cache: true + - id: inspect + env: + GH_TOKEN: ${{ github.token }} + run: bundle exec bake gem:github:release:resolve + + publish: + needs: inspect + if: needs.inspect.outputs.release == 'true' + runs-on: ubuntu-latest + environment: rubygems + permissions: + contents: write + pull-requests: read + actions: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: ${{ needs.inspect.outputs.commit }} + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + with: + ruby-version: "3.4" + rubygems: '4.0.21' + bundler-cache: true + - name: Build or restore artifact + id: build + env: + GH_TOKEN: ${{ github.token }} + GEM_SIGNING_KEY: ${{ secrets.GEM_SIGNING_KEY }} + run: bundle exec bake gem:github:release:build + - name: Sign RubyGems attestation + if: steps.build.outputs.restored != 'true' + env: + PACKAGE: ${{ steps.build.outputs.package }} + run: gem exec sigstore-cli:0.2.3 sign "$PACKAGE" --bundle "$PACKAGE.sigstore.json" + - name: Attest exact source and artifact + if: steps.build.outputs.restored != 'true' + id: attest + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 + with: + subject-path: ${{ steps.build.outputs.package }} + predicate-type: https://slsa.dev/provenance/v1 + predicate-path: pkg/provenance.json + - name: Retain provenance bundle + if: steps.build.outputs.restored != 'true' + env: + ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }} + run: cp "$ATTESTATION_BUNDLE" pkg/provenance.sigstore.json + - name: Preserve release before upload + if: steps.build.outputs.restored != 'true' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: ${{ steps.build.outputs.artifact }} + path: pkg/ + if-no-files-found: error + retention-days: 90 + - uses: rubygems/configure-rubygems-credentials@2a7221c7c44c30ebc68eda38fa25c60b918245c7 + - name: Verify, publish, and finalize + env: + GH_TOKEN: ${{ github.token }} + run: bundle exec bake gem:github:release:publish diff --git a/.github/workflows/release-validate.yaml b/.github/workflows/release-validate.yaml new file mode 100644 index 0000000..3700ebc --- /dev/null +++ b/.github/workflows/release-validate.yaml @@ -0,0 +1,32 @@ +name: Validate release + +on: + pull_request: + branches: ["main"] + +permissions: + contents: read + +env: + BUNDLE_WITH: maintenance + +jobs: + validate: + name: Release validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Regenerate release content + env: + RELEASE_BASE: ${{ github.event.pull_request.base.sha }} + run: bundle exec bake gem:release:validate "base=$RELEASE_BASE" optional=true + - name: Build unsigned package + run: bundle exec bake gem:build signing_key=false diff --git a/.github/workflows/rubocop.yaml b/.github/workflows/rubocop.yaml new file mode 100644 index 0000000..be83c16 --- /dev/null +++ b/.github/workflows/rubocop.yaml @@ -0,0 +1,20 @@ +name: RuboCop + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + check: + name: RuboCop + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Check Ruby and documentation style + timeout-minutes: 10 + run: bundle exec rubocop diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..ddd6ab8 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,24 @@ +name: Test + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + test: + name: Test Ruby ${{ matrix.ruby }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby: ["3.3", "3.4", "4.0"] + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run tests + timeout-minutes: 10 + run: bundle exec sus-parallel diff --git a/.gitignore b/.gitignore index de77375..03d3b8a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /coverage/ /gems.locked /pkg/ +/vendor/bundle/ diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..d8ab4c9 --- /dev/null +++ b/agents.md @@ -0,0 +1,437 @@ +# Agent + +## Context + +This section provides links to documentation from installed packages. It is automatically generated and may be updated by running `bake agent:context:install`. + +**Important:** Before performing any code, documentation, or analysis tasks, always read and apply the full content of any relevant documentation referenced in the following sections. These context files contain authoritative standards and best practices for documentation, code style, and project-specific workflows. **Do not proceed with any actions until you have read and incorporated the guidance from relevant context files.** + +**Setup Instructions:** If the referenced files are not present or if dependencies have been updated, run `bake agent:context:install` to install the latest context files. + +### agent-context + +Install and manage context files from Ruby gems. + +#### [Getting Started](.context/agent-context/getting-started.md) + +This guide explains how to use `agent-context`, a tool for discovering and installing contextual information from Ruby gems to help AI agents. + +### async + +A concurrency framework for Ruby. + +#### [Getting Started](.context/async/getting-started.md) + +This guide shows how to add async to your project and run code asynchronously. + +#### [Scheduler](.context/async/scheduler.md) + +This guide gives an overview of how the scheduler is implemented. + +#### [Tasks](.context/async/tasks.md) + +This guide explains how asynchronous tasks work and how to use them. + +#### [Best Practices](.context/async/best-practices.md) + +This guide gives an overview of best practices for using Async. + +#### [Debugging](.context/async/debugging.md) + +This guide explains how to debug issues with programs that use Async. + +#### [Thread safety](.context/async/thread-safety.md) + +This guide explains thread safety in Ruby, focusing on fibers and threads, common pitfalls, and best practices to avoid problems like data corruption, race conditions, and deadlocks. + +### async-container + +Abstract container-based parallelism using threads and processes where appropriate. + +#### [Getting Started](.context/async-container/getting-started.md) + +This guide explains how to use `async-container` to build basic scalable systems. + +#### [Container Policies](.context/async-container/policies.md) + +This guide explains how to use policies to monitor container health and implement custom failure handling strategies. + +#### [Systemd Integration](.context/async-container/systemd-integration.md) + +This guide explains how to use `async-container` with systemd to manage your application as a service. + +#### [Kubernetes Integration](.context/async-container/kubernetes-integration.md) + +This guide explains how to use `async-container` with Kubernetes to manage your application as a containerized service. + +### async-http + +A HTTP client and server library. + +#### [Getting Started](.context/async-http/getting-started.md) + +This guide explains how to make HTTP requests and serve HTTP responses with `Async::HTTP`. + +#### [Choosing a Client](.context/async-http/choosing-a-client.md) + +This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and higher-level interfaces for libraries. + +#### [Concurrent Requests and Connection Pooling](.context/async-http/concurrent-requests.md) + +This guide explains how to run HTTP requests concurrently while keeping request fan-out, connection usage, and resource life cycles bounded. + +#### [Testing](.context/async-http/testing.md) + +This guide explains how to test `Async::HTTP` clients and servers without depending on external HTTP services. + +### async-http-cache + +Standard-compliant cache for async-http. + +#### [Getting Started](.context/async-http-cache/getting-started.md) + +This guide explains how to get started with `async-http-cache`, a cache middleware for `Async::HTTP` clients and servers. + +### async-service + +A service layer for Async. + +#### [Getting Started](.context/async-service/getting-started.md) + +This guide explains how to get started with `async-service` to create and run services in Ruby. + +#### [Container Policies](.context/async-service/policies.md) + +This guide explains how to configure container policies for your services and understand the default failure handling behavior. + +#### [Service Architecture](.context/async-service/service-architecture.md) + +This guide explains the key architectural components of `async-service` and how they work together to provide a clean separation of concerns. + +#### [Best Practices](.context/async-service/best-practices.md) + +This guide outlines recommended patterns and practices for building robust, maintainable services with `async-service`. + +#### [Deployment](.context/async-service/deployment.md) + +This guide explains how to deploy `async-service` applications using systemd and Kubernetes. We'll use a simple example service to demonstrate deployment configurations. + +### bake + +A replacement for rake with a simpler syntax. + +#### [Getting Started](.context/bake/getting-started.md) + +This guide gives a general overview of `bake` and how to use it. + +#### [Command Line Interface](.context/bake/command-line-interface.md) + +The `bake` command is broken up into two main functions: `list` and `call`. + +#### [Project Integration](.context/bake/project-integration.md) + +This guide explains how to add `bake` to a Ruby project. + +#### [Gem Integration](.context/bake/gem-integration.md) + +This guide explains how to add `bake` to a Ruby gem and export standardised tasks for use by other gems and projects. + +#### [Input and Output](.context/bake/input-and-output.md) + +`bake` has built in tasks for reading input and writing output in different formats. While this can be useful for general processing, there are some limitations, notably that rich object representations like `json` and `yaml` often don't support stream processing. + +### bake-gem + +Release management for Ruby gems. + +#### [Getting Started](.context/bake-gem/getting-started.md) + +This guide explains how to use `bake-gem` to release gems safely and efficiently. + +### bake-releases + +Releases document management. + +#### [Getting Started](.context/bake-releases/getting-started.md) + +This guide explains how to use `bake-releases` to manage release documentation for your Ruby gem. + +### console + +Beautiful logging for Ruby. + +#### [Getting Started](.context/console/getting-started.md) + +This guide explains how to use `console` for logging. + +#### [Command Line](.context/console/command-line.md) + +This guide explains how the `console` gem can be controlled using environment variables. + +#### [Configuration](.context/console/configuration.md) + +This guide explains how to implement per-project configuration for the `console` gem. + +#### [Integration](.context/console/integration.md) + +This guide explains how to integrate the `console` output into different systems. + +#### [Events](.context/console/events.md) + +This guide explains how to log structured events with a well-defined schema. + +### decode + +Code analysis for documentation generation. + +#### [Getting Started](.context/decode/getting-started.md) + +This guide explains how to use `decode` for source code analysis. + +#### [Documentation Coverage](.context/decode/documentation-coverage.md) + +This guide explains how to test and monitor documentation coverage in your Ruby projects using the Decode gem's built-in bake tasks. + +#### [Ruby Documentation](.context/decode/ruby-documentation.md) + +This guide covers documentation practices and pragmas supported by the Decode gem for documenting Ruby code. These pragmas provide structured documentation that can be parsed and used to generate API documentation and achieve complete documentation coverage. + +### falcon + +A fast, asynchronous, rack-compatible web server. + +#### [Getting Started](.context/falcon/getting-started.md) + +This guide gives an overview of how to use Falcon for running Ruby web applications. + +#### [Rails Integration](.context/falcon/rails-integration.md) + +This guide explains how to host Rails applications with Falcon. + +#### [Deployment](.context/falcon/deployment.md) + +This guide explains how to deploy applications using the Falcon web server. It covers the recommended deployment methods, configuration options, and examples for different environments, including systemd and kubernetes. + +#### [Dynamic Clusters with Envoy](.context/falcon/cluster-deployment.md) + +This guide explains how to run Falcon workers with independently bound endpoints, publish them dynamically using xDS, and balance requests according to their current load using ORCA. + +#### [Performance Tuning](.context/falcon/performance-tuning.md) + +This guide explains the performance characteristics of Falcon. + +#### [WebSockets](.context/falcon/websockets.md) + +This guide explains how to use WebSockets with Falcon. + +#### [Interim Responses](.context/falcon/interim-responses.md) + +This guide explains how to use interim responses in Falcon to send early hints to the client. + +#### [How It Works](.context/falcon/how-it-works.md) + +This guide gives an overview of how Falcon handles an incoming web request. + +### io-endpoint + +Provides a separation of concerns interface for IO endpoints. + +#### [Getting Started](.context/io-endpoint/getting-started.md) + +This guide explains how to get started with `io-endpoint`, a library that provides a separation of concerns interface for network I/O endpoints. + +#### [Named Endpoints](.context/io-endpoint/named-endpoints.md) + +This guide explains how to use `IO::Endpoint::NamedEndpoints` to manage multiple endpoints by name, enabling scenarios like running the same application on different protocols or ports. + +### io-event + +An event loop. + +#### [Getting Started](.context/io-event/getting-started.md) + +This guide explains how to use `io-event` for non-blocking IO. + +### io-stream + +Provides a generic stream wrapper for IO instances. + +#### [Getting Started](.context/io-stream/getting-started.md) + +This guide explains how to use `io-stream` to add efficient buffering to Ruby IO objects. + +#### [High Performance IO](.context/io-stream/high-performance-io.md) + +This guide explains how to achieve optimal performance when using `io-stream` by understanding and controlling flush behavior. + +### markly + +CommonMark parser and renderer. Written in C, wrapped in Ruby. + +#### [Getting Started](.context/markly/getting-started.md) + +This guide explains now to install and use Markly. + +#### [Extensions](.context/markly/extensions.md) + +This guide explains how to enable and use Markly's Markdown extensions. + +#### [Abstract Syntax Tree](.context/markly/abstract-syntax-tree.md) + +This guide explains how to use Markly's abstract syntax tree (AST) to parse and manipulate Markdown documents. + +#### [Headings](.context/markly/headings.md) + +This guide explains how to work with headings in Markly, including extracting them for navigation and handling duplicate heading text. + +### protocol-http + +Provides abstractions to handle HTTP protocols. + +#### [Getting Started](.context/protocol-http/getting-started.md) + +This guide explains how to use `protocol-http` for building abstract HTTP interfaces. + +#### [Message Body](.context/protocol-http/message-body.md) + +This guide explains how to work with HTTP request and response message bodies using `Protocol::HTTP::Body` classes. + +#### [Headers](.context/protocol-http/headers.md) + +This guide explains how to work with HTTP headers using `protocol-http`. + +#### [Middleware](.context/protocol-http/middleware.md) + +This guide explains how to build and use HTTP middleware with `Protocol::HTTP::Middleware`. + +#### [Streaming](.context/protocol-http/streaming.md) + +This guide gives an overview of how to implement streaming requests and responses. + +#### [Design Overview](.context/protocol-http/design-overview.md) + +This guide explains the high level design of `protocol-http` in the context of wider design patterns that can be used to implement HTTP clients and servers. + +### protocol-http1 + +A low level implementation of the HTTP/1 protocol. + +#### [Getting Started](.context/protocol-http1/getting-started.md) + +This guide explains how to get started with `protocol-http1`, a low-level implementation of the HTTP/1 protocol for building HTTP clients and servers. + +### protocol-http2 + +A low level implementation of the HTTP/2 protocol. + +#### [Getting Started](.context/protocol-http2/getting-started.md) + +This guide explains how to use the `protocol-http2` gem to implement a basic HTTP/2 client. + +### protocol-rack + +An implementation of the Rack protocol/specification. + +#### [Getting Started](.context/protocol-rack/getting-started.md) + +This guide explains how to get started with `protocol-rack` and integrate Rack applications with `Protocol::HTTP` servers. + +#### [Request and Response Handling](.context/protocol-rack/request-response.md) + +This guide explains how to work with requests and responses when bridging between Rack and `Protocol::HTTP`, covering advanced use cases and edge cases. + +### samovar + +Samovar is a flexible option parser excellent support for sub-commands and help documentation. + +#### [Getting Started](.context/samovar/getting-started.md) + +This guide explains how to use `samovar` to build command-line tools and applications. + +#### [Completion](.context/samovar/completion.md) + +This guide explains how to add shell completion to commands built with `samovar`. + +### sus + +A fast and scalable test runner. + +#### [Getting Started](.context/sus/getting-started.md) + +This guide explains how to use the `sus` gem to write tests for your Ruby projects. + +#### [Mocking](.context/sus/mocking.md) + +This guide explains how to use mocking in sus to isolate dependencies and verify interactions in your tests. + +#### [Shared Test Behaviors and Fixtures](.context/sus/shared-contexts.md) + +This guide explains how to use shared test contexts and fixtures in sus to reduce duplication and ensure consistent test behavior across your test suite. + +### traces + +Application instrumentation and tracing. + +#### [Getting Started](.context/traces/getting-started.md) + +This guide explains how to use `traces` for tracing code execution. + +#### [Context Propagation](.context/traces/context-propagation.md) + +This guide explains how to propagate trace context between different execution contexts within your application using `Traces.current_context` and `Traces.with_context`. + +#### [Testing](.context/traces/testing.md) + +This guide explains how to test traces in your code. + +#### [Capture](.context/traces/capture.md) + +This guide explains how to use `traces` for exporting traces from your application. This can be used to document all possible traces. + +### utopia + +Utopia is a framework for building dynamic content-driven websites. + +#### [Getting Started](.context/utopia/getting-started.md) + +This guide explains how to set up a `utopia` website for local development and deployment. + +#### [Middleware](.context/utopia/middleware.md) + +This guide gives an overview of the different middleware used by Utopia. + +#### [Server Setup](.context/utopia/server-setup.md) + +This guide explains how to deploy a `utopia` web application. + +#### [Integrating with JavaScript](.context/utopia/integrating-with-javascript.md) + +This guide explains how to integrate JavaScript into your Utopia application. + +#### [What is XNode?](.context/utopia/what-is-xnode.md) + +This guide explains the `xnode` view layer and how it can be used to build efficient websites. + +#### [Updating Utopia](.context/utopia/updating-utopia.md) + +This guide explains how to update existing `utopia` websites. + +### utopia-project + +A project documentation tool based on Utopia. + +#### [Getting Started](.context/utopia-project/getting-started.md) + +This guide explains how to use `utopia-project` to add documentation to your project. + +#### [Documentation Guides](.context/utopia-project/documentation-guidelines.md) + +This guide explains how to create and maintain documentation for your project using `utopia-project`. + +#### [Mermaid Diagrams](.context/utopia-project/mermaid-diagrams.md) + +This guide explains how to use Mermaid diagrams in your documentation to visualize complex relationships, flows, and architectures. + +#### [GitHub Pages Integration](.context/utopia-project/github-pages-integration.md) + +This guide shows you how to use `utopia-project` with GitHub Pages to deploy documentation. diff --git a/bake-gem-github.gemspec b/bake-gem-github.gemspec index f2dc2d5..1e33ad7 100644 --- a/bake-gem-github.gemspec +++ b/bake-gem-github.gemspec @@ -9,7 +9,9 @@ Gem::Specification.new do |spec| spec.authors = ["Samuel Williams"] spec.license = "MIT" spec.homepage = "https://github.com/socketry/bake-gem-github" - spec.files = Dir.glob(["{bake,context,lib,templates}/**/*", "*.md"], base: __dir__) + spec.cert_chain = ["release.cert"] + spec.signing_key = File.expand_path("~/.gem/socketry-release.pem") + spec.files = Dir.glob(["{bake,context,lib,templates}/**/*", "*.md", "release.cert"], base: __dir__) spec.required_ruby_version = ">= 3.3" spec.add_dependency "bake-gem", ">= 0.15.0" spec.add_dependency "net-http" diff --git a/bake.rb b/bake.rb index 700c112..88e8076 100644 --- a/bake.rb +++ b/bake.rb @@ -5,5 +5,6 @@ # Regenerate documentation when preparing a release. def after_gem_release_version_increment(version) + context["releases:update"].call(version) context["utopia:project:agent:context:update"].call end diff --git a/config/release.yaml b/config/release.yaml new file mode 100644 index 0000000..857e0a4 --- /dev/null +++ b/config/release.yaml @@ -0,0 +1,15 @@ +--- +schema: 1 +repository: socketry/bake-gem-github +branch: main +checks: +- Test Ruby 3.3 +- Test Ruby 3.4 +- Test Ruby 4.0 +- RuboCop +- Documentation coverage +- Release validation +approvals: 2 +signing: true +ruby: '3.4' +environment: rubygems diff --git a/context/getting-started.md b/context/getting-started.md index a13b263..c48f5c1 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -18,7 +18,7 @@ bundle exec bake agent:context:install bundle exec bake gem:github:setup:plan ``` -Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/RELEASING.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. For later policy changes, edit the configuration and review the generated workflow/policy changes explicitly. Setup does not replace other publishers: remove conflicting release workflows during migration. +Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. For later policy changes, edit the configuration and review the generated workflow/policy changes explicitly. Setup does not replace other publishers: remove conflicting release workflows during migration. The default is two approvals with explicit administrator bypass, dismissed stale reviews, approval of the last push, strict up-to-date CI, and immutable default-branch history/release tags. These branch rules affect **all PRs** into the default branch. Ordinary administrator reviews count as one review. A human who dispatches a bot-authored PR is not its author under GitHub's native rules. @@ -32,7 +32,7 @@ This command changes remote rulesets and preserves unrelated rulesets. Existing Create a `rubygems` GitHub environment restricted to the default branch. Do not add a second routine reviewer gate. On RubyGems, an owner must configure a Trusted Publisher with the owner/repository, workflow filename **`release-publish.yaml`**, and environment **`rubygems`** shown by `doctor`. Ownership/MFA and environment/signing bootstrap are deliberate manual steps in this first implementation; `doctor` prints desired and observed GitHub settings, not a claim that RubyGems ownership or publisher trust has been verified. See [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/). -If `release.cert` exists, setup enables legacy signing. Keep this public certificate in Git and install its matching **private key** as the environment secret `GEM_SIGNING_KEY`. The workflow checks the certificate validity, key match, and resulting package signatures. To opt out explicitly, pass `signing=false` during setup. No long-lived RubyGems publishing key is required. +If `release.cert` exists, setup enables legacy signing. Keep this public certificate in Git and install its matching **private key** as the Actions secret `GEM_SIGNING_KEY`. Use the `rubygems` environment or an organization secret available to the release repositories. The workflow checks the certificate validity, key match, and resulting package signatures. To opt out explicitly, pass `signing=false` during setup. No long-lived RubyGems publishing key is required. Before enabling releases, confirm two people can administer the repository and recover the RubyGems account/signing key, and enough maintainers can satisfy the review policy. Pilot on one low-risk gem and prove publishing, administrator bypass, fork merges, and recovery before rolling out broadly. No organization-wide migration or live publisher setup is performed by these tasks. diff --git a/gems.rb b/gems.rb index c068398..0dfc430 100644 --- a/gems.rb +++ b/gems.rb @@ -13,5 +13,6 @@ group :maintenance, optional: true do gem "agent-context" + gem "bake-releases" gem "utopia-project" end diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index a13b263..c48f5c1 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -18,7 +18,7 @@ bundle exec bake agent:context:install bundle exec bake gem:github:setup:plan ``` -Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/RELEASING.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. For later policy changes, edit the configuration and review the generated workflow/policy changes explicitly. Setup does not replace other publishers: remove conflicting release workflows during migration. +Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. For later policy changes, edit the configuration and review the generated workflow/policy changes explicitly. Setup does not replace other publishers: remove conflicting release workflows during migration. The default is two approvals with explicit administrator bypass, dismissed stale reviews, approval of the last push, strict up-to-date CI, and immutable default-branch history/release tags. These branch rules affect **all PRs** into the default branch. Ordinary administrator reviews count as one review. A human who dispatches a bot-authored PR is not its author under GitHub's native rules. @@ -32,7 +32,7 @@ This command changes remote rulesets and preserves unrelated rulesets. Existing Create a `rubygems` GitHub environment restricted to the default branch. Do not add a second routine reviewer gate. On RubyGems, an owner must configure a Trusted Publisher with the owner/repository, workflow filename **`release-publish.yaml`**, and environment **`rubygems`** shown by `doctor`. Ownership/MFA and environment/signing bootstrap are deliberate manual steps in this first implementation; `doctor` prints desired and observed GitHub settings, not a claim that RubyGems ownership or publisher trust has been verified. See [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/). -If `release.cert` exists, setup enables legacy signing. Keep this public certificate in Git and install its matching **private key** as the environment secret `GEM_SIGNING_KEY`. The workflow checks the certificate validity, key match, and resulting package signatures. To opt out explicitly, pass `signing=false` during setup. No long-lived RubyGems publishing key is required. +If `release.cert` exists, setup enables legacy signing. Keep this public certificate in Git and install its matching **private key** as the Actions secret `GEM_SIGNING_KEY`. Use the `rubygems` environment or an organization secret available to the release repositories. The workflow checks the certificate validity, key match, and resulting package signatures. To opt out explicitly, pass `signing=false` during setup. No long-lived RubyGems publishing key is required. Before enabling releases, confirm two people can administer the repository and recover the RubyGems account/signing key, and enough maintainers can satisfy the review policy. Pilot on one low-risk gem and prove publishing, administrator bypass, fork merges, and recovery before rolling out broadly. No organization-wide migration or live publisher setup is performed by these tasks. diff --git a/lib/bake/gem/github/setup.rb b/lib/bake/gem/github/setup.rb index e29fd71..321335a 100644 --- a/lib/bake/gem/github/setup.rb +++ b/lib/bake/gem/github/setup.rb @@ -33,7 +33,7 @@ def generate(repository:, branch: "main", checks:, approvals: 2, signing: File.f self.class.rules(config).each do |name, rule| files[".github/release-rules/#{name}.json"] = JSON.pretty_generate(rule) + "\n" end - files[".github/RELEASING.md"] = File.read(File.join(templates, "RELEASING.md")) + files[".github/releasing.md"] = File.read(File.join(templates, "releasing.md")) conflicts = files.keys.select{|name| File.exist?(File.join(@root, name)) && File.read(File.join(@root, name)) != files[name]} raise "Existing files differ; review them before regenerating: #{conflicts.join(', ')}" unless conflicts.empty? files.each do |name, content| diff --git a/readme.md b/readme.md index 00aaaef..c77dc9f 100644 --- a/readme.md +++ b/readme.md @@ -8,3 +8,5 @@ Reviewed GitHub releases for Ruby gems, using `bake-gem` for branch preparation, - `gem:github:release:resume run=ID`: retry with the original artifact. Read [the setup, release and recovery guide](guides/getting-started/readme.md) before enabling publishing. Context is distributed through `agent-context`. This initial implementation requires `bake-gem` 0.15 or later and a live pilot before wider rollout. + +For this gem's releases, follow [the maintainer instructions](.github/releasing.md). Release workflows use the source checkout through Bundler's `gemspec` dependency, so the first release does not require an already published copy of `bake-gem-github`. diff --git a/release.cert b/release.cert new file mode 100644 index 0000000..d88bb2f --- /dev/null +++ b/release.cert @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEFDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMREwDwYDVQQKDAhTb2Nr +ZXRyeTEbMBkGA1UEAwwSU29ja2V0cnkgUnVieSBHZW1zMB4XDTI2MDkyMTA3NTgx +NFoXDTI3MDkyMTA3NTgxNFowMDERMA8GA1UECgwIU29ja2V0cnkxGzAZBgNVBAMM +ElNvY2tldHJ5IFJ1YnkgR2VtczCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC +ggGBAM/QgjVgDzDo/xJEQoFvAzcVFP7msnswkhPJB2UDsbxXCZmers5jV512TM5s +NwLHfzJiC4DcI5ax9ZYKM9Q+dS21YYagNqjtg3YPyDqR6phoibEA0VoMuInUQW68 +i5OkCJzYKGD2pYYVZnuFNqyM2ECUvXh/fBmvPoHbncAHhWPaCBH8mQJ8sRNc6+RV +SoZZu1Yo/aW1zQ+SpZwad7s1ZmigfDNHKhJ22KwZHk+/5Zw1QXVfcajjswV0Mm2P +irgjKN3fyNVkwlly63EBlR4VydT+g9QtJtqh7ee0ThhI+v4wYovf4dbLNcEBTHu1 +x1pGbXGx/2fBAST5eVwaXIx1V8VU22AJcBk1H2o5/1F0HfbD2HQjmFOA112KUwBW +9TtspZD3g6rCQFX53XvL9h6j2y1ukl7s5AdEzOe/x6r1MO8tSDtgcYoF89wgF/88 +Q/Eski0FckQ+znfESZcFcpzs2sPDoYW6SNVX4tZcNty85Y6WBHfHKio14x63PeyC +CG1gMwIDAQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU +SuMc0x24Xshtaa8SQVkBr2Z5WzIwDQYJKoZIhvcNAQELBQADggGBABI62ey5lDWm +j8jsTQaNBMBb7LbKS3XkBwyL1UNGEwlicp399rJDeuNgVSHqBCZ/nE4yleNjgW9Y +N6DKdxXCvXmFUoHvUINeAvThHxmlEGhSfXl1x55xHEig0rEP4jgdjnYdQI4fqeOM +zIDN0F3ruArke4Y62SQgWVN7vspdAA41hBIRl1vsvs0KWG8DIVQ8cmR+TG6zjOmK +iUiUZGNPnStV1O1xW83c+Ba4Am0krP6/forxDPhRvTehqkVYvMdPfYcICNb5IM58 +5vaYvuj5AtfZuZWN0F/0KtSphhNC17rh6h8QmuYdMJ1clvlrkiEaO1UjPVwjgtxC +x/ARh5X6q0/YTXl4r7EfSJEeqW7qhgu9EUj3mGIPHYdX0Dqih/NgK20F3GvQtTfB +7sse0duKBHXONYVwKAsceUVvqtCfyF608bCilCkbhw3QaInRj+BkRQKJ5Tuj2PZR +76a/hwaV2wWn5RtAFBaUQzQdo/xTqJ7IkwaFjOZe/QyvyoRjCK9Rdg== +-----END CERTIFICATE----- diff --git a/releases.md b/releases.md new file mode 100644 index 0000000..9d06c95 --- /dev/null +++ b/releases.md @@ -0,0 +1,5 @@ +# Releases + +## Unreleased + + - Initial implementation. diff --git a/templates/release-publish.yaml.erb b/templates/release-publish.yaml.erb index 04e7bec..ebe8563 100644 --- a/templates/release-publish.yaml.erb +++ b/templates/release-publish.yaml.erb @@ -9,6 +9,7 @@ on: permissions: contents: read + pull-requests: read concurrency: group: release-publish @@ -46,6 +47,7 @@ jobs: environment: rubygems permissions: contents: write + pull-requests: read actions: read id-token: write attestations: write @@ -84,8 +86,8 @@ jobs: - name: Retain provenance bundle if: steps.build.outputs.restored != 'true' env: - BUNDLE_PATH: ${{ steps.attest.outputs.bundle-path }} - run: cp "$BUNDLE_PATH" pkg/provenance.sigstore.json + ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }} + run: cp "$ATTESTATION_BUNDLE" pkg/provenance.sigstore.json - name: Preserve release before upload if: steps.build.outputs.restored != 'true' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 diff --git a/templates/releasing.md b/templates/releasing.md new file mode 100644 index 0000000..47343ae --- /dev/null +++ b/templates/releasing.md @@ -0,0 +1,13 @@ +# Releases + +Prepare releases with `bundle exec bake gem:github:release:patch` (or `minor` / `major`), or dispatch `release-prepare.yaml` on the default branch. Core `gem:release:branch:*` tasks only create a local branch and commit; they do not push or publish. + +Review the entire release diff. Native GitHub rules require the configured reviews and CI, including an up-to-date branch and **Release validation**. Administrators can explicitly bypass the review/check rules. A rebase is sufficient only if regenerated content still matches. If notes are stale, prepare again from the current default branch and preserve any manual edits separately for review. + +Merging a version increase publishes the actual merged commit through `release-publish.yaml`. There is no second release approval. The public `release.cert` stays in Git; the optional private key belongs in the Actions secret `GEM_SIGNING_KEY`, supplied by the `rubygems` environment or an organization secret. RubyGems authentication uses Trusted Publishing. + +If a publishing job fails, use **Re-run all jobs** on that same workflow run, or `bundle exec bake gem:github:release:resume run=RUN_ID`. The original artifact is downloaded before another upload is attempted. Do not create a replacement version bump or rebuild an already-published version. Conflicting bytes or tags require investigation; never automatically yank a version or move a tag. + +Verify downloaded gems with `gh attestation verify FILE --repo OWNER/REPOSITORY --source-digest MERGED_SHA --signer-workflow OWNER/REPOSITORY/.github/workflows/release-publish.yaml`. Release assets include the gem, RubyGems Sigstore bundle, GitHub provenance bundle, and source/digest receipt. + +The initial implementation supports public repositories, a single gemspec, stable patch/minor/major versions, and merge/squash commits. Keep rebase merges and merge queues disabled. Generation hooks must be repeatable from the same source and version. Run setup and account recovery checks before enabling publishing; see the companion gem's setup guide.