From 84bab66d5a352ebde51b01116359a562d0d71f02 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 30 Aug 2026 13:49:22 -0700 Subject: [PATCH 1/4] Modernize maintenance workflows --- .github/actions/mise-install/action.yaml | 24 +++ .github/actions/mise-install/install.sh | 47 ++++++ .../actions/mise-tool-versions/action.yaml | 24 +++ .github/actions/mise-tool-versions/resolve.sh | 40 +++++ .github/actions/setup-mise/action.yaml | 29 ++++ .github/actions/setup-mise/setup.sh | 97 ++++++++++++ .github/dependabot.yml | 12 -- .github/workflows/audit.yaml | 114 +++++++++++--- .github/workflows/block-merge.yaml | 2 +- .github/workflows/ci.yaml | 135 +++++++++------- .github/workflows/code-coverage.yaml | 130 ---------------- .github/workflows/fuzz.yaml | 26 ++-- .github/workflows/publish.yaml | 145 ++++++++++++++++++ .github/workflows/repo-labels.yaml | 2 +- .github/workflows/rustdoc.yaml | 32 ++-- .rubocop.yml | 20 --- .ruby-version | 1 - .yamllint.yaml | 11 -- Gemfile | 8 - Gemfile.lock | 54 ------- README.md | 1 - Rakefile | 92 ----------- mise.lock | 77 ++++++++++ mise.toml | 80 ++++++++++ package-lock.json | 30 ---- package.json | 4 +- pnpm-lock.yaml | 24 +++ pnpm-workspace.yaml | 7 + src/format/mod.rs | 2 +- 29 files changed, 801 insertions(+), 469 deletions(-) create mode 100644 .github/actions/mise-install/action.yaml create mode 100755 .github/actions/mise-install/install.sh create mode 100644 .github/actions/mise-tool-versions/action.yaml create mode 100755 .github/actions/mise-tool-versions/resolve.sh create mode 100644 .github/actions/setup-mise/action.yaml create mode 100755 .github/actions/setup-mise/setup.sh delete mode 100644 .github/workflows/code-coverage.yaml create mode 100644 .github/workflows/publish.yaml delete mode 100644 .rubocop.yml delete mode 100644 .ruby-version delete mode 100644 .yamllint.yaml delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 Rakefile create mode 100644 mise.lock create mode 100644 mise.toml delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml diff --git a/.github/actions/mise-install/action.yaml b/.github/actions/mise-install/action.yaml new file mode 100644 index 00000000..5605322c --- /dev/null +++ b/.github/actions/mise-install/action.yaml @@ -0,0 +1,24 @@ +--- +name: Install mise tools +description: Install a declared subset of the locked mise toolchain +inputs: + executable: + description: Absolute path to the verified mise executable + required: true + github-token: + description: GitHub token used by mise's GitHub-backed tool providers + required: true + tools: + description: Newline-delimited mise.toml tool names to install + required: true +runs: + using: composite + steps: + - name: Install locked tools + shell: bash + env: + ACTION_PATH: ${{ github.action_path }} + MISE: ${{ inputs.executable }} + MISE_GITHUB_TOKEN: ${{ inputs.github-token }} + MISE_TOOLS: ${{ inputs.tools }} + run: '"$ACTION_PATH/install.sh"' diff --git a/.github/actions/mise-install/install.sh b/.github/actions/mise-install/install.sh new file mode 100755 index 00000000..38bf3c05 --- /dev/null +++ b/.github/actions/mise-install/install.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Install an explicitly requested subset of the repository's locked mise tools. + +set -Eeuo pipefail +IFS=$'\n\t' + +main() { + local -r mise=${MISE:?MISE is required} + local -r requested_tools=${MISE_TOOLS:?MISE_TOOLS is required} + local -r github_path=${GITHUB_PATH:?GITHUB_PATH is required} + local bin_path enabled_tools repo_root tool + local -a tools=() + + repo_root=$(git rev-parse --show-toplevel) + readonly repo_root + + while IFS= read -r tool; do + [[ -n $tool ]] || continue + if [[ ! $tool =~ ^[a-zA-Z0-9][a-zA-Z0-9:._/-]*$ ]]; then + echo "invalid mise tool name: $tool" >&2 + return 1 + fi + + "$mise" config get --file "$repo_root/mise.toml" "tools.$tool" > /dev/null + tools+=("$tool") + done <<< "$requested_tools" + + if [[ ${#tools[@]} -eq 0 ]]; then + echo "mise-install requires at least one declared tool" >&2 + return 1 + fi + + enabled_tools=$( + IFS=, + echo "${tools[*]}" + ) + readonly enabled_tools + + MISE_ENABLE_TOOLS=$enabled_tools MISE_NO_HOOKS=1 \ + "$mise" install --cd "$repo_root" --locked "${tools[@]}" + while IFS= read -r bin_path; do + printf '%s\n' "$bin_path" >> "$github_path" + done < <(MISE_ENABLE_TOOLS=$enabled_tools \ + "$mise" bin-paths --cd "$repo_root" "${tools[@]}") +} + +main "$@" diff --git a/.github/actions/mise-tool-versions/action.yaml b/.github/actions/mise-tool-versions/action.yaml new file mode 100644 index 00000000..dceb0250 --- /dev/null +++ b/.github/actions/mise-tool-versions/action.yaml @@ -0,0 +1,24 @@ +--- +name: Resolve mise tool versions +description: Expose exact mise.toml tool versions as GitHub Action outputs +inputs: + executable: + description: Absolute path to the verified mise executable + required: true +outputs: + node: + description: Node.js toolchain version + value: ${{ steps.resolve.outputs.node }} + zizmor: + description: zizmor tool version + value: ${{ steps.resolve.outputs.zizmor }} +runs: + using: composite + steps: + - name: Resolve tool versions + id: resolve + shell: bash + env: + ACTION_PATH: ${{ github.action_path }} + MISE: ${{ inputs.executable }} + run: '"$ACTION_PATH/resolve.sh"' diff --git a/.github/actions/mise-tool-versions/resolve.sh b/.github/actions/mise-tool-versions/resolve.sh new file mode 100755 index 00000000..6f73bffd --- /dev/null +++ b/.github/actions/mise-tool-versions/resolve.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Resolve exact mise.toml tool versions into trusted GitHub Action outputs. + +set -Eeuo pipefail +IFS=$'\n\t' + +emit_version() { + local -r output_name=$1 + local -r tool_name=$2 + local -r config_path=$3 + local -r github_output=$4 + local -r mise=$5 + local version + + version=$("$mise" config get --file "$config_path" "tools.$tool_name") + readonly version + + if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "mise tool $tool_name must be an exact release, got $version" >&2 + return 1 + fi + + printf '%s=%s\n' "$output_name" "$version" >> "$github_output" +} + +main() { + local -r mise=${MISE:?MISE is required} + local -r github_output=${GITHUB_OUTPUT:?GITHUB_OUTPUT is required} + local config_path repo_root + + repo_root=$(git rev-parse --show-toplevel) + readonly repo_root + config_path="$repo_root/mise.toml" + readonly config_path + + emit_version node node "$config_path" "$github_output" "$mise" + emit_version zizmor aqua:zizmorcore/zizmor "$config_path" "$github_output" "$mise" +} + +main "$@" diff --git a/.github/actions/setup-mise/action.yaml b/.github/actions/setup-mise/action.yaml new file mode 100644 index 00000000..aec12270 --- /dev/null +++ b/.github/actions/setup-mise/action.yaml @@ -0,0 +1,29 @@ +--- +name: Setup mise +description: Install the repository-pinned mise bootstrap after checksum verification +inputs: + github-token: + description: GitHub token used to authenticate the release download + required: true +outputs: + executable: + description: Absolute path to the verified mise executable + value: ${{ steps.install.outputs.executable }} +runs: + using: composite + steps: + - name: Disable mise version download tracking + shell: bash + run: printf '%s\n' 'MISE_USE_VERSIONS_HOST_TRACK=false' >> "$GITHUB_ENV" + + - name: Install mise + id: install + shell: bash + env: + ACTION_PATH: ${{ github.action_path }} + GITHUB_TOKEN: ${{ inputs.github-token }} + MISE_VERSION: "2026.8.10" + MISE_SHA256_LINUX_X64: "db4afe188fc405489fe6a04e159d46eb2053499c12e133240c790d988f248710" + MISE_SHA256_MACOS_ARM64: "a8190378f6c0b394c54068405260282dcff2af14a153b327198cf4c44364e1b1" + MISE_SHA256_MACOS_X64: "cda2095e2e71a2f046c2c6628d7e21e08a79933aa1a04fd3114e9284b72db423" + run: '"$ACTION_PATH/setup.sh"' diff --git a/.github/actions/setup-mise/setup.sh b/.github/actions/setup-mise/setup.sh new file mode 100755 index 00000000..4186ff1e --- /dev/null +++ b/.github/actions/setup-mise/setup.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# Install a pinned mise archive after SHA-256 verification. + +set -Eeuo pipefail +IFS=$'\n\t' + +SETUP_MISE_SCRATCH= +SETUP_MISE_ARCHIVE= + +cleanup() { + if [[ -n $SETUP_MISE_ARCHIVE ]]; then + rm -f -- "$SETUP_MISE_ARCHIVE" + fi + if [[ -n $SETUP_MISE_SCRATCH ]]; then + rmdir -- "$SETUP_MISE_SCRATCH" + fi +} + +platform_and_checksum() { + local -r runner_os=$1 + local -r runner_arch=$2 + + case "$runner_os/$runner_arch" in + Linux/X64) + printf '%s\t%s\n' linux-x64 \ + "${MISE_SHA256_LINUX_X64:?MISE_SHA256_LINUX_X64 is required}" + ;; + macOS/ARM64) + printf '%s\t%s\n' macos-arm64 \ + "${MISE_SHA256_MACOS_ARM64:?MISE_SHA256_MACOS_ARM64 is required}" + ;; + macOS/X64) + printf '%s\t%s\n' macos-x64 \ + "${MISE_SHA256_MACOS_X64:?MISE_SHA256_MACOS_X64 is required}" + ;; + *) + echo "setup-mise does not support $runner_os $runner_arch" >&2 + return 1 + ;; + esac +} + +verify_checksum() { + local -r runner_os=$1 + local -r checksum=$2 + local -r archive=$3 + + if [[ $runner_os == macOS ]]; then + printf '%s %s\n' "$checksum" "$archive" | shasum -a 256 --check + else + printf '%s %s\n' "$checksum" "$archive" | sha256sum --check + fi +} + +main() { + local -r version=${MISE_VERSION:?MISE_VERSION is required} + local -r runner_os=${RUNNER_OS:?RUNNER_OS is required} + local -r runner_arch=${RUNNER_ARCH:?RUNNER_ARCH is required} + local -r runner_temp=${RUNNER_TEMP:?RUNNER_TEMP is required} + local -r github_output=${GITHUB_OUTPUT:?GITHUB_OUTPUT is required} + local archive checksum install_root platform scratch + local -a curl_args=(--fail --location --silent --show-error) + + if [[ -n ${GITHUB_TOKEN:-} ]]; then + curl_args+=(--header "Authorization: Bearer $GITHUB_TOKEN") + fi + + IFS=$'\t' read -r platform checksum < <( + platform_and_checksum "$runner_os" "$runner_arch" + ) + readonly platform checksum + archive="mise-v${version}-${platform}.tar.xz" + readonly archive + install_root="$runner_temp/mise-$version" + readonly install_root + scratch=$(mktemp -d "$runner_temp/setup-mise.XXXXXX") + readonly scratch + SETUP_MISE_SCRATCH=$scratch + readonly SETUP_MISE_SCRATCH + SETUP_MISE_ARCHIVE="$scratch/$archive" + readonly SETUP_MISE_ARCHIVE + trap cleanup EXIT + + curl "${curl_args[@]}" \ + --output "$SETUP_MISE_ARCHIVE" \ + "https://github.com/jdx/mise/releases/download/v${version}/${archive}" + verify_checksum "$runner_os" "$checksum" "$SETUP_MISE_ARCHIVE" + + mkdir -p "$install_root" + tar --extract --xz --file "$SETUP_MISE_ARCHIVE" \ + --directory "$install_root" --strip-components=1 + + "$install_root/bin/mise" --version + printf 'executable=%s\n' "$install_root/bin/mise" >> "$github_output" +} + +main "$@" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 83d21d57..ad8480b5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,18 +25,6 @@ updates: - lopopolo labels: - A-deps - - package-ecosystem: bundler - directory: "/" - schedule: - interval: monthly - groups: - bundler-deps: - patterns: - - "*" - assignees: - - lopopolo - labels: - - A-deps - package-ecosystem: npm directory: "/" schedule: diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml index b06fa031..8b45e937 100644 --- a/.github/workflows/audit.yaml +++ b/.github/workflows/audit.yaml @@ -9,42 +9,37 @@ name: Audit - trunk schedule: - cron: "0 0 * * TUE" +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: - ruby: - name: Audit Ruby Dependencies - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v5.0.0 - - - name: Install Ruby toolchain - uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 - with: - ruby-version: ".ruby-version" - bundler-cache: true - - - name: bundler-audit - run: bundle exec bundle-audit check --update - rust: name: Audit Rust Dependencies - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + permissions: + contents: read strategy: matrix: checks: - advisories - bans licenses sources - - # Prevent sudden announcement of a new advisory from failing ci: continue-on-error: ${{ matrix.checks == 'advisories' }} - steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: artichoke/setup-rust/audit@v2.0.1 + shell: bash + run: | + set -euo pipefail + rustup set profile minimal + rustup toolchain install stable --no-self-update + rustup default stable + rustc -Vv + cargo -V - name: Generate Cargo.lock run: | @@ -52,8 +47,79 @@ jobs: cargo generate-lockfile --verbose fi - - uses: EmbarkStudios/cargo-deny-action@f2ba7abc2abebaf185c833c3961145a3c275caad # v2.0.13 + - name: Run cargo-deny + uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1 with: arguments: --locked --all-features command: check ${{ matrix.checks }} command-arguments: --show-stats + + js: + name: Audit JS Dependencies + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup mise + id: mise + uses: ./.github/actions/setup-mise + with: + github-token: ${{ github.token }} + + - name: Resolve toolchain versions + id: toolchains + uses: ./.github/actions/mise-tool-versions + with: + executable: ${{ steps.mise.outputs.executable }} + + - name: Setup pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + with: + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ steps.toolchains.outputs.node }} + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: pnpm audit + run: pnpm audit + + zizmor: + name: Run zizmor 🌈 + runs-on: ubuntu-24.04 + permissions: + contents: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup mise + id: mise + uses: ./.github/actions/setup-mise + with: + github-token: ${{ github.token }} + + - name: Resolve toolchain versions + id: toolchains + uses: ./.github/actions/mise-tool-versions + with: + executable: ${{ steps.mise.outputs.executable }} + + - name: Run zizmor 🌈 + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + with: + persona: "pedantic" + version: ${{ steps.toolchains.outputs.zizmor }} diff --git a/.github/workflows/block-merge.yaml b/.github/workflows/block-merge.yaml index aed2e8a3..0a029a84 100644 --- a/.github/workflows/block-merge.yaml +++ b/.github/workflows/block-merge.yaml @@ -6,7 +6,7 @@ name: Merge jobs: labels: name: Labels - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aaa5a840..34696553 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,33 +9,43 @@ name: CI - trunk schedule: - cron: "0 0 * * WED" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +permissions: + contents: read jobs: build: - name: Build - runs-on: ${{ matrix.os }} + name: Build (${{ matrix.name }}) + runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] include: - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - os: windows-latest - target: x86_64-pc-windows-msvc - - os: macos-latest - target: x86_64-apple-darwin + - name: ubuntu-latest + runner: ubuntu-24.04 + - name: windows-latest + runner: windows-2025-vs2026 + - name: macos-latest + runner: macos-26 env: RUSTFLAGS: -D warnings RUST_BACKTRACE: 1 - CARGO_NET_GIT_FETCH_WITH_CLI: true steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: artichoke/setup-rust/build-and-test@v2.0.1 - with: - toolchain: stable + shell: bash + run: | + set -euo pipefail + rustup set profile minimal + rustup toolchain install stable --no-self-update + rustup default stable + rustc -Vv + cargo -V - name: Compile run: cargo build --verbose @@ -51,29 +61,33 @@ jobs: - name: Test with no default features run: cargo test --no-default-features - - - name: Test with only alloc feature + - name: Test additional feature configuration 1 run: cargo test --no-default-features --features alloc - - - name: Test with only alloc + std features + - name: Test additional feature configuration 2 run: cargo test --no-default-features --features alloc,std build-msrv: name: Build (MSRV) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: CARGO_INCREMENTAL: 0 RUSTFLAGS: -D warnings RUST_BACKTRACE: 1 - CARGO_NET_GIT_FETCH_WITH_CLI: true steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: artichoke/setup-rust/build-and-test@v2.0.1 - with: - toolchain: "1.84.0" + shell: bash + run: | + set -euo pipefail + rustup set profile minimal + rustup toolchain install 1.84.0 --no-self-update + rustup default 1.84.0 + rustc -Vv + cargo -V - name: Compile run: cargo build --verbose @@ -92,19 +106,25 @@ jobs: rust: name: Lint and format Rust - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: RUSTFLAGS: -D warnings RUST_BACKTRACE: 1 - CARGO_NET_GIT_FETCH_WITH_CLI: true steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: artichoke/setup-rust/lint-and-format@v2.0.1 - with: - toolchain: stable + shell: bash + run: | + set -euo pipefail + rustup set profile minimal + rustup toolchain install stable --component rustfmt --component clippy --no-self-update + rustup default stable + rustc -Vv + cargo -V - name: Check formatting run: cargo fmt --check @@ -112,43 +132,40 @@ jobs: - name: Lint with Clippy run: cargo clippy --workspace --all-features --all-targets - ruby: - name: Lint and format Ruby - runs-on: ubuntu-latest + text: + name: Lint and format text + runs-on: ubuntu-24.04 steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - - name: Install Ruby toolchain - uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 + - name: Setup mise + id: mise + uses: ./.github/actions/setup-mise with: - ruby-version: ".ruby-version" - bundler-cache: true + github-token: ${{ github.token }} - - name: Lint and check formatting with Rubocop - run: bundle exec rubocop --format github + - name: Resolve toolchain versions + id: toolchains + uses: ./.github/actions/mise-tool-versions + with: + executable: ${{ steps.mise.outputs.executable }} - text: - name: Lint and format text - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v5.0.0 + - name: Setup pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + with: + run_install: false - name: Setup Node.js runtime - uses: actions/setup-node@v6.0.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "lts/*" + node-version: ${{ steps.toolchains.outputs.node }} + cache: pnpm - - name: Install toolchain - run: npm ci + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Format with prettier - run: npx prettier --check '**/*' - - - name: Lint YAML sources with yamllint - run: | - yamllint --version - echo "Linting YAML sources with yamllint ..." - yamllint --strict --format github . - echo "OK" + - name: Format with Prettier + run: pnpm run fmt:check diff --git a/.github/workflows/code-coverage.yaml b/.github/workflows/code-coverage.yaml deleted file mode 100644 index 985c01b7..00000000 --- a/.github/workflows/code-coverage.yaml +++ /dev/null @@ -1,130 +0,0 @@ ---- -name: Code Coverage -"on": - push: - branches: - - trunk - pull_request: - branches: - - trunk -jobs: - generate: - name: Generate - permissions: - id-token: write - contents: read - runs-on: ubuntu-latest - env: - RUST_BACKTRACE: 1 - CARGO_NET_GIT_FETCH_WITH_CLI: true - CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse - steps: - - name: Checkout repository - uses: actions/checkout@v5.0.0 - with: - persist-credentials: false - - - name: Install nightly Rust toolchain - uses: artichoke/setup-rust/code-coverage@v2.0.1 - - - name: Setup grcov - run: | - release_url="$(curl \ - --url https://api.github.com/repos/mozilla/grcov/releases \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --silent \ - --fail \ - --retry 5 \ - | jq -r '.[0].assets - | map(select(.browser_download_url | test(".*x86_64-unknown-linux-musl.tar.bz2$"))) - | .[0].browser_download_url' - )" - curl -sL "$release_url" | sudo tar xvj -C /usr/local/bin/ - - - name: Show grcov version - run: grcov --version - - - name: Generate coverage - env: - LLVM_PROFILE_FILE: "strftime-ruby-%m-%p.profraw" - RUSTFLAGS: "-C instrument-coverage" - # Unstable feature: `--persist-doctests`: persist doctest executables after running - # https://rustwiki.org/en/rustdoc/unstable-features.html#--persist-doctests-persist-doctest-executables-after-running - # - # Used to allow grcov to use these sources to generate coverage metrics. - RUSTDOCFLAGS: "-C instrument-coverage -Z unstable-options --persist-doctests target/debug/doctests" - run: cargo test - - - name: Generate HTML report - run: grcov strftime-ruby*.profraw --source-dir . --keep-only 'src/**/*.rs' --binary-path target/debug -t html --filter covered -o target/coverage - - - name: Generate detailed JSON report - run: grcov strftime-ruby*.profraw --source-dir . --keep-only 'src/**/*.rs' --binary-path target/debug -t covdir --filter covered -o target/coverage/coverage.json - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0 - if: github.ref == 'refs/heads/trunk' - with: - aws-region: us-west-2 - role-to-assume: arn:aws:iam::447522982029:role/gha-strftime-ruby-s3-backup-20220817011212567800000002 - role-session-name: GitHubActionsRustCodeCoverage@strftime-ruby - - - name: Show AWS caller identity - if: github.ref == 'refs/heads/trunk' - run: aws sts get-caller-identity - - - name: Upload archives to S3 - if: github.ref == 'refs/heads/trunk' - run: | - aws s3 sync target/coverage/ s3://artichoke-forge-code-coverage-us-west-2/strftime-ruby/ --delete --sse AES256 --exclude '*' --include '*.svg' --content-type 'image/svg+xml' - aws s3 sync target/coverage/ s3://artichoke-forge-code-coverage-us-west-2/strftime-ruby/ --delete --sse AES256 --exclude '*' --include '*.html' --content-type 'text/html' - aws s3 sync target/coverage/ s3://artichoke-forge-code-coverage-us-west-2/strftime-ruby/ --delete --sse AES256 --exclude '*' --include '*.json' --content-type 'application/json' - aws s3 sync target/coverage/ s3://artichoke-forge-code-coverage-us-west-2/strftime-ruby/ --delete --sse AES256 --include '*' --exclude '*.svg' --exclude '*.html' --exclude '*.json' - - - name: Check missed lines - shell: python - env: - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_EVENT_NAME: ${{ github.event_name }} - GITHUB_EVENT_NUMBER: ${{ github.event.number }} - run: | - import json - import os - from urllib.request import urlopen - - trunk_coverage_url = "https://codecov.artichokeruby.org/strftime-ruby/coverage.json" - - - def print_report(coverage, *, on=None): - if on is None: - raise ValueError("must provide `on` kwarg") - - print(f"On {on}:") - print("coveragePercent =", coverage["coveragePercent"]) - print("linesCovered =", coverage["linesCovered"]) - print("linesMissed =", coverage["linesMissed"]) - print("linesTotal =", coverage["linesTotal"]) - print("") - - - if os.environ.get("GITHUB_REF_NAME") == "trunk": - # We don't need to compare trunk coverage to itself - exit(0) - - with urlopen(trunk_coverage_url, data=None, timeout=3) as remote: - trunk_coverage = json.load(remote) - - print_report(trunk_coverage, on="branch trunk") - - with open("target/coverage/coverage.json") as local: - branch_coverage = json.load(local) - - on = None - if os.environ.get("GITHUB_EVENT_NAME") == "pull_request": - on = "PR artichoke/strftime-ruby#" + os.environ.get("GITHUB_EVENT_NUMBER") - - print_report(branch_coverage, on=on) - - is_ok = branch_coverage["linesMissed"] <= trunk_coverage["linesMissed"] - exit(0) if is_ok else exit(1) diff --git a/.github/workflows/fuzz.yaml b/.github/workflows/fuzz.yaml index 6bee3a8e..2e81a534 100644 --- a/.github/workflows/fuzz.yaml +++ b/.github/workflows/fuzz.yaml @@ -6,19 +6,20 @@ name: Fuzz jobs: bytes: name: Fuzz bytes - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: RUST_BACKTRACE: 1 CARGO_NET_GIT_FETCH_WITH_CLI: true steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Install Rust toolchain - uses: artichoke/setup-rust/build-and-test@v2.0.1 - with: - toolchain: nightly + - name: Install nightly Rust toolchain + run: | + rustup set profile minimal + rustup toolchain install nightly --no-self-update + rustup default nightly - name: Install cargo-fuzz run: cargo install cargo-fuzz @@ -31,19 +32,20 @@ jobs: string: name: Fuzz string - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: RUST_BACKTRACE: 1 CARGO_NET_GIT_FETCH_WITH_CLI: true steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Install Rust toolchain - uses: artichoke/setup-rust/build-and-test@v2.0.1 - with: - toolchain: nightly + - name: Install nightly Rust toolchain + run: | + rustup set profile minimal + rustup toolchain install nightly --no-self-update + rustup default nightly - name: Install cargo-fuzz run: cargo install cargo-fuzz diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..459d6225 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,145 @@ +--- +name: Publish +"on": + push: + tags: + - "v*.*.*" + +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + crates-io: + name: Publish to crates.io + if: github.event.created == true + runs-on: ubuntu-24.04 + environment: + name: crates-io-publish + permissions: + actions: read # required to inspect CI workflow run status + contents: read # required to checkout repository + id-token: write # required for trusted publishing to crates.io + env: + CARGO_INCREMENTAL: 0 + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + RUST_BACKTRACE: 1 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 1 + + - name: Configure ephemeral Rust homes + shell: bash + run: | + set -euo pipefail + # Use per-job temporary directories and avoid cross-run caches. + mkdir -p "${RUNNER_TEMP}/cargo-home" "${RUNNER_TEMP}/rustup-home" + echo "CARGO_HOME=${RUNNER_TEMP}/cargo-home" >> "$GITHUB_ENV" + echo "RUSTUP_HOME=${RUNNER_TEMP}/rustup-home" >> "$GITHUB_ENV" + + - name: Validate release tag + shell: bash + env: + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "release tag '$TAG' does not match vX.Y.Z" + exit 1 + fi + + - name: Install Rust toolchain + shell: bash + run: | + set -euo pipefail + rustup set profile minimal + rustup toolchain install stable --no-self-update + rustup default stable + rustc -Vv + cargo -V + + - name: Ensure Cargo.toml version matches tag + shell: bash + env: + TAG_VERSION: ${{ github.ref_name }} + run: | + set -euo pipefail + TAG_VERSION="${TAG_VERSION#v}" + CARGO_VERSION="$(sed -n 's/^version = "\([0-9][0-9.]*\)".*/\1/p' Cargo.toml | head -n1)" + if [[ -z "$CARGO_VERSION" ]]; then + echo "failed to parse crate version from Cargo.toml" + exit 1 + fi + if [[ "$TAG_VERSION" != "$CARGO_VERSION" ]]; then + echo "tag version '$TAG_VERSION' does not match crate version '$CARGO_VERSION'" + exit 1 + fi + + - name: Wait for CI on tagged commit + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + SHA: ${{ github.sha }} + CI_WORKFLOW_NAME: CI + POLL_INTERVAL_SECONDS: 20 + MAX_WAIT_SECONDS: 10800 + run: | + set -euo pipefail + + deadline="$(( $(date +%s) + MAX_WAIT_SECONDS ))" + run_list=( + gh run list + --repo "${REPO}" + --workflow "${CI_WORKFLOW_NAME}" + --commit "${SHA}" + --limit 50 + ) + + while true; do + ci_count="$( + "${run_list[@]}" --json status --jq 'length' + )" + + if [[ "${ci_count}" -eq 0 ]]; then + echo "no '${CI_WORKFLOW_NAME}' workflow runs found yet for ${SHA}; waiting..." + else + pending_count="$( + "${run_list[@]}" --json status --jq '[.[] | select(.status != "completed")] | length' + )" + if [[ "${pending_count}" -gt 0 ]]; then + echo "${pending_count} '${CI_WORKFLOW_NAME}' runs pending for ${SHA}; waiting..." + else + non_success_count="$( + "${run_list[@]}" --json conclusion --jq '[.[] | select(.conclusion != "success")] | length' + )" + if [[ "${non_success_count}" -eq 0 ]]; then + echo "all '${CI_WORKFLOW_NAME}' runs for ${SHA} completed successfully." + break + fi + echo "'${CI_WORKFLOW_NAME}' is not green for ${SHA}:" + "${run_list[@]}" --json conclusion,url --jq '.[] | select(.conclusion != "success") | "- \(.url) => \(.conclusion)"' + exit 1 + fi + fi + + if [[ "$(date +%s)" -ge "${deadline}" ]]; then + echo "timed out waiting for '${CI_WORKFLOW_NAME}' to complete for ${SHA}" + exit 1 + fi + sleep "${POLL_INTERVAL_SECONDS}" + done + + - name: Authenticate with crates.io + id: auth + uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 + + - name: Publish crate + shell: bash + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + run: cargo publish diff --git a/.github/workflows/repo-labels.yaml b/.github/workflows/repo-labels.yaml index 2ccb1f87..a48cc8a7 100644 --- a/.github/workflows/repo-labels.yaml +++ b/.github/workflows/repo-labels.yaml @@ -18,7 +18,7 @@ name: Create Repository Labels jobs: labels: name: Synchronize repository labels - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5.0.0 diff --git a/.github/workflows/rustdoc.yaml b/.github/workflows/rustdoc.yaml index 2dd5b6b8..e5efdca0 100644 --- a/.github/workflows/rustdoc.yaml +++ b/.github/workflows/rustdoc.yaml @@ -10,23 +10,35 @@ name: Documentation schedule: - cron: "0 0 * * TUE" concurrency: - group: docs-${{ github.head_ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true env: CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse +permissions: {} jobs: rustdoc: name: Build Rust API docs - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + permissions: + contents: write env: RUSTDOCFLAGS: -D warnings -D rustdoc::broken_intra_doc_links --cfg docsrs RUST_BACKTRACE: 1 - steps: - name: Checkout repository - uses: actions/checkout@v5.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - - name: Install Rust toolchain - uses: artichoke/setup-rust/rustdoc@v2.0.1 + - name: Install nightly Rust toolchain + shell: bash + run: | + set -euo pipefail + rustup set profile minimal + rustup toolchain install nightly --no-self-update + rustup default nightly + rustc -Vv + cargo -V - name: Check docs with no default features run: cargo doc --workspace --no-default-features @@ -34,11 +46,11 @@ jobs: - name: Clean docs run: cargo clean - - name: Build Documentation + - name: Build documentation run: cargo doc --workspace - - name: Deploy Docs - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v3.9.2 + - name: Deploy docs + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 if: github.ref == 'refs/heads/trunk' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -46,6 +58,4 @@ jobs: publish_branch: gh-pages user_name: artichoke-ci user_email: ci@artichokeruby.org - # only have the most recent docs in the `gh-pages` branch - # https://github.com/artichoke/artichoke/issues/1826 force_orphan: true diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index f7f981fe..00000000 --- a/.rubocop.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -plugins: rubocop-rake -AllCops: - NewCops: enable - TargetRubyVersion: 3.4 - DisplayCopNames: true - Exclude: - - "**/node_modules/**/*" - - "**/target/**/*" - - "**/vendor/**/*" -Layout/EndOfLine: - EnforcedStyle: lf -Layout/LineLength: - Enabled: true - Exclude: - - "**/Rakefile" -Metrics: - Enabled: false -Style/Documentation: - Enabled: false diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 2f4b6075..00000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.4 diff --git a/.yamllint.yaml b/.yamllint.yaml deleted file mode 100644 index c74eafc7..00000000 --- a/.yamllint.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -extends: default - -ignore: | - **/node_modules/**/* - **/vendor/** - **/target/**/* - -rules: - comments: disable - line-length: disable diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 268c2f74..00000000 --- a/Gemfile +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -gem 'bundler-audit', '~> 0.9', require: false -gem 'rake', '>= 12.3.3', require: false -gem 'rubocop', '~> 1.81', require: false -gem 'rubocop-rake', '~> 0.7', require: false diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 566a75ad..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,54 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - ast (2.4.3) - bundler-audit (0.9.2) - bundler (>= 1.2.0, < 3) - thor (~> 1.0) - json (2.15.2) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - prism (1.6.0) - racc (1.8.1) - rainbow (3.1.1) - rake (13.3.1) - regexp_parser (2.11.3) - rubocop (1.81.7) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.47.1, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.47.1) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-rake (0.7.1) - lint_roller (~> 1.1) - rubocop (>= 1.72.1) - ruby-progressbar (1.13.0) - thor (1.4.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) - -PLATFORMS - ruby - x64-mingw-ucrt - -DEPENDENCIES - bundler-audit (~> 0.9) - rake (>= 12.3.3) - rubocop (~> 1.81) - rubocop-rake (~> 0.7) - -BUNDLED WITH - 2.5.23 diff --git a/README.md b/README.md index fc15f866..08083891 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # strftime-ruby [![GitHub Actions](https://github.com/artichoke/strftime-ruby/workflows/CI/badge.svg)](https://github.com/artichoke/strftime-ruby/actions) -[![Code Coverage](https://codecov.artichokeruby.org/strftime-ruby/badges/flat.svg?nocache=2)](https://codecov.artichokeruby.org/strftime-ruby/index.html) [![Twitter](https://img.shields.io/twitter/follow/artichokeruby?label=Follow&style=social)](https://twitter.com/artichokeruby)
[![Crate](https://img.shields.io/crates/v/strftime-ruby.svg)](https://crates.io/crates/strftime-ruby) diff --git a/Rakefile b/Rakefile deleted file mode 100644 index d4042e82..00000000 --- a/Rakefile +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -require 'open-uri' -require 'shellwords' -require 'bundler/audit/task' -require 'rubocop/rake_task' - -task default: %i[format lint] - -desc 'Lint sources' -task lint: %i[lint:clippy lint:rubocop:autocorrect] - -namespace :lint do - RuboCop::RakeTask.new(:rubocop) - - desc 'Lint Rust sources with Clippy' - task :clippy do - sh 'cargo clippy --workspace --all-features --all-targets' - end - - desc 'Lint Rust sources with Clippy restriction pass (unenforced lints)' - task :'clippy:restriction' do - lints = [ - 'clippy::dbg_macro', - 'clippy::get_unwrap', - 'clippy::indexing_slicing', - 'clippy::panic', - 'clippy::print_stdout', - 'clippy::expect_used', - 'clippy::unwrap_used', - 'clippy::todo', - 'clippy::unimplemented', - 'clippy::unreachable' - ] - command = ['cargo', 'clippy', '--'] + lints.flat_map { |lint| ['-W', lint] } - sh command.shelljoin - end -end - -desc 'Format sources' -task format: %i[format:rust format:text] - -namespace :format do - desc 'Format Rust sources with rustfmt' - task :rust do - sh 'cargo fmt -- --color=auto' - end - - desc 'Format text, YAML, and Markdown sources with prettier' - task :text do - sh 'npm run fmt' - end -end - -desc 'Format sources' -task fmt: %i[fmt:rust fmt:text] - -namespace :fmt do - desc 'Format Rust sources with rustfmt' - task :rust do - sh 'cargo fmt -- --color=auto' - end - - desc 'Format text, YAML, and Markdown sources with prettier' - task :text do - sh 'npm run fmt' - end -end - -desc 'Build Rust workspace' -task :build do - sh 'cargo build --workspace' -end - -desc 'Generate Rust API documentation' -task :doc do - ENV['RUSTDOCFLAGS'] = '-D warnings -D rustdoc::broken_intra_doc_links --cfg docsrs' - sh 'rustup run --install nightly cargo doc --workspace' -end - -desc 'Generate Rust API documentation and open it in a web browser' -task :'doc:open' do - ENV['RUSTDOCFLAGS'] = '-D warnings -D rustdoc::broken_intra_doc_links --cfg docsrs' - sh 'rustup run --install nightly cargo doc --workspace --open' -end - -desc 'Run strftime-ruby unit tests' -task :test do - sh 'cargo test --workspace' -end - -Bundler::Audit::Task.new diff --git a/mise.lock b/mise.lock new file mode 100644 index 00000000..3ccfd6c1 --- /dev/null +++ b/mise.lock @@ -0,0 +1,77 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + +lockfile_version = 1 + +[[tools."aqua:zizmorcore/zizmor"]] +version = "1.29.0" +backend = "aqua:zizmorcore/zizmor" +specifiers = ["1.29.0"] + +[tools."aqua:zizmorcore/zizmor"."platforms.linux-x64"] +checksum = "sha256:dd96df044a6e8538d5f423790f453bdd03d49e5b2bcc38214acc41a2f1297839" +url = "https://github.com/zizmorcore/zizmor/releases/download/v1.29.0/zizmor-x86_64-unknown-linux-gnu.tar.gz" +url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/498263145" +provenance = "github-attestations" + +[tools."aqua:zizmorcore/zizmor"."platforms.macos-arm64"] +checksum = "sha256:720322fade9e83a9c7953944c438f2ba942636b86b96a8f0e6b15ce94c8a6b6f" +url = "https://github.com/zizmorcore/zizmor/releases/download/v1.29.0/zizmor-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/498263141" +provenance = "github-attestations" + +[tools."aqua:zizmorcore/zizmor"."platforms.macos-x64"] +checksum = "sha256:648b72ab9941a7f2a8d65d7b68a8e76cef789538c8df3a3950384d38423375b0" +url = "https://github.com/zizmorcore/zizmor/releases/download/v1.29.0/zizmor-x86_64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/498263144" +provenance = "github-attestations" + +[tools."aqua:zizmorcore/zizmor"."platforms.windows-x64"] +checksum = "sha256:68a6bc6888f10bf0d53658c75885e7c1b7a0588d4c1fbc3f0ca280ad7324bf06" +url = "https://github.com/zizmorcore/zizmor/releases/download/v1.29.0/zizmor-x86_64-pc-windows-msvc.zip" +url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/498263142" +provenance = "github-attestations" + +[[tools."cargo:cargo-deny"]] +version = "0.20.2" +backend = "cargo:cargo-deny" +specifiers = ["0.20.2"] + +[[tools."cargo:cargo-mutants"]] +version = "27.1.0" +backend = "cargo:cargo-mutants" +specifiers = ["27.1.0"] + +[[tools."cargo:cargo-outdated"]] +version = "0.19.0" +backend = "cargo:cargo-outdated" +specifiers = ["0.19.0"] + +[[tools.node]] +version = "24.19.0" +backend = "core:node" +specifiers = ["24.19.0"] + +[tools.node."platforms.linux-x64"] +checksum = "sha256:f625d97cd707df4ff96254916fbc5ff014f09c09effe5a1e0ca8f6d41a8789d4" +url = "https://nodejs.org/dist/v24.19.0/node-v24.19.0-linux-x64.tar.gz" + +[tools.node."platforms.macos-arm64"] +checksum = "sha256:8294b7aa9b03997481c06babf1e8b270c859358f27da57a11509afe537ac381d" +url = "https://nodejs.org/dist/v24.19.0/node-v24.19.0-darwin-arm64.tar.gz" + +[tools.node."platforms.macos-x64"] +checksum = "sha256:d1b5e999db158c62fe8f7267a4476b035d8bd93b1a605bac24a3f0dd166e3316" +url = "https://nodejs.org/dist/v24.19.0/node-v24.19.0-darwin-x64.tar.gz" + +[tools.node."platforms.windows-x64"] +checksum = "sha256:57f71ab3652e797d84acddc79c81cc9ff1c6ddb2a1974cdb83f00fee9bff4c73" +url = "https://nodejs.org/dist/v24.19.0/node-v24.19.0-win-x64.zip" + +[[tools.rust]] +version = "stable" +backend = "core:rust" +specifiers = ["stable"] + +[tools.rust.options] +components = "clippy,rustfmt" +profile = "minimal" diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..ed30fbe2 --- /dev/null +++ b/mise.toml @@ -0,0 +1,80 @@ +[tools] +node = "24.19.0" +# Rust intentionally tracks the stable channel for this Rust library. +rust = { version = "stable", profile = "minimal", components = "clippy,rustfmt" } +"aqua:zizmorcore/zizmor" = "1.29.0" + +"cargo:cargo-deny" = "0.20.2" +"cargo:cargo-mutants" = "27.1.0" +"cargo:cargo-outdated" = "0.19.0" + +[hooks] +postinstall = "corepack enable" + +[settings] +experimental = true +use_versions_host_track = false + +[env] +_.path = ["{{config_root}}/node_modules/.bin"] + +[tasks.pnpm-install] +description = "Install dependencies with pnpm when lockfile changes" +run = "pnpm install" +sources = ["package.json", "pnpm-lock.yaml", "mise.toml"] +outputs = ["node_modules/.pnpm/lock.yaml"] + + +[tasks.build] +description = "Build Rust workspace" +run = "cargo build --workspace" + +[tasks.doc] +description = "Generate Rust API documentation" +env = { RUSTDOCFLAGS = "-D warnings -D rustdoc::broken_intra_doc_links --cfg docsrs" } +run = "rustup run --install nightly cargo doc --workspace" + +[tasks."doc:open"] +description = "Generate Rust API documentation and open it in a browser" +env = { RUSTDOCFLAGS = "-D warnings -D rustdoc::broken_intra_doc_links --cfg docsrs" } +run = "rustup run --install nightly cargo doc --workspace --open" + +[tasks."fmt:rust"] +description = "Format Rust sources" +run = "cargo fmt --all" + +[tasks."fmt:text"] +description = "Format text sources" +run = "pnpm run fmt" + +[tasks.fmt] +description = "Format all sources" +depends = ["fmt:rust", "fmt:text"] + +[tasks."fmt:check:rust"] +description = "Check Rust formatting" +run = "cargo fmt --all -- --check" + +[tasks."fmt:check:text"] +description = "Check text formatting" +run = "pnpm run fmt:check" + +[tasks."fmt:check"] +description = "Check formatting for all sources" +depends = ["fmt:check:rust", "fmt:check:text"] + +[tasks."lint:clippy"] +description = "Lint Rust sources with Clippy" +run = "cargo clippy --workspace --all-features --all-targets" + +[tasks."lint:clippy:restriction"] +description = "Run the unenforced Clippy restriction lint pass" +run = "cargo clippy --workspace --all-features --all-targets -- -W clippy::dbg_macro -W clippy::get_unwrap -W clippy::indexing_slicing -W clippy::panic -W clippy::print_stdout -W clippy::expect_used -W clippy::unwrap_used -W clippy::todo -W clippy::unimplemented -W clippy::unreachable" + +[tasks.lint] +description = "Lint all sources" +depends = ["fmt:check", "lint:clippy"] + +[tasks.test] +description = "Run Rust tests" +run = "cargo test --workspace" diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 3bb52111..00000000 --- a/package-lock.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@artichokeruby/ruby-strftime", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@artichokeruby/ruby-strftime", - "version": "0.0.0", - "devDependencies": { - "prettier": "3.6.2" - } - }, - "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - } - } -} diff --git a/package.json b/package.json index 39aa8aa8..776eb598 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "rust" ], "repository": "github:artichoke/ruby-strftime", + "packageManager": "pnpm@11.22.0+sha512.1ff870c4c6133dfd88fb2afc46dd13d47f09c9794b438c6fdb47ca98caf3bc16381ee0be93a091b8e3824cf01f889f46d7d9e20910fb0be1ab0fb5baa80dd621", "author": "Ryan Lopopolo (https://hyperbo.la/)", "homepage": "https://www.artichokeruby.org", "bugs": "https://github.com/artichoke/ruby-strftime/issues", @@ -17,6 +18,7 @@ "prettier": "3.6.2" }, "scripts": { - "fmt": "prettier --write \"**/*\"" + "fmt": "prettier --write \"**/*\"", + "fmt:check": "prettier --check \"**/*\"" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..35b8c2eb --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,24 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + devDependencies: + prettier: + specifier: 3.6.2 + version: 3.6.2 + +packages: + prettier@3.6.2: + resolution: + { + integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==, + } + engines: { node: ">=14" } + hasBin: true + +snapshots: + prettier@3.6.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..47fc7b3f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +packages: + - . + +blockExoticSubdeps: true + +# minimumReleaseAge is measured in minutes (10080m = 7 days). +minimumReleaseAge: 10080 diff --git a/src/format/mod.rs b/src/format/mod.rs index e3ad7934..fac47c43 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -1025,7 +1025,7 @@ mod tests { // Ensure each flag uses a unique bit to prevent conflicts // during bitwise operations. let overlap = (left as u8) & (right as u8); - assert_eq!(overlap, 0, "Flags {left:?} and {right:?} overlap",); + assert_eq!(overlap, 0, "Flags {left:?} and {right:?} overlap"); } } } From ab345273bcb8b56e21b9560faff5a303ca16e79b Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 30 Aug 2026 13:56:08 -0700 Subject: [PATCH 2/4] Remove obsolete rustdoc feature gate --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index bc70ed1f..c1146efa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,6 @@ // // This approach is borrowed from tokio. #![cfg_attr(docsrs, feature(doc_cfg))] -#![cfg_attr(docsrs, feature(doc_alias))] //! This crate provides a Ruby 3.1.2 compatible `strftime` function, which //! formats time according to the directives in the given format string. From ac50072664b7c3c941f3e904c77c91b018be6d09 Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 30 Aug 2026 14:02:03 -0700 Subject: [PATCH 3/4] Pin checkout action by commit --- .github/workflows/repo-labels.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/repo-labels.yaml b/.github/workflows/repo-labels.yaml index a48cc8a7..a9aaf212 100644 --- a/.github/workflows/repo-labels.yaml +++ b/.github/workflows/repo-labels.yaml @@ -20,7 +20,7 @@ jobs: name: Synchronize repository labels runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Sync GitHub Issue Labels uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5.3.0 From 313ed8c8ee184429994be5dff14d007fcbfc25ac Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Sun, 30 Aug 2026 14:07:04 -0700 Subject: [PATCH 4/4] Address zizmor security findings --- .github/dependabot.yml | 6 ++++++ .github/workflows/audit.yaml | 2 +- .github/workflows/block-merge.yaml | 9 ++++++++- .github/workflows/fuzz.yaml | 8 ++++++++ .github/workflows/repo-labels.yaml | 11 ++++++++++- .github/workflows/rustdoc.yaml | 2 +- mise.toml | 1 - 7 files changed, 34 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ad8480b5..b5b04466 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,6 +13,8 @@ updates: - lopopolo labels: - A-deps + cooldown: + default-days: 7 - package-ecosystem: cargo directory: "/" schedule: @@ -25,6 +27,8 @@ updates: - lopopolo labels: - A-deps + cooldown: + default-days: 7 - package-ecosystem: npm directory: "/" schedule: @@ -37,3 +41,5 @@ updates: - lopopolo labels: - A-deps + cooldown: + default-days: 7 diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml index 8b45e937..1df27cc5 100644 --- a/.github/workflows/audit.yaml +++ b/.github/workflows/audit.yaml @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-24.04 permissions: contents: read - security-events: write + security-events: write # required to add findings via sarif steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/block-merge.yaml b/.github/workflows/block-merge.yaml index 0a029a84..f7488ce4 100644 --- a/.github/workflows/block-merge.yaml +++ b/.github/workflows/block-merge.yaml @@ -3,13 +3,20 @@ name: Merge "on": pull_request: types: [opened, labeled, unlabeled, synchronize] +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: labels: name: Labels runs-on: ubuntu-24.04 + permissions: + issues: write # required to read and write labels + pull-requests: write # required to read and write labels steps: - - uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5 + - uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5.2 with: mode: exactly count: 0 diff --git a/.github/workflows/fuzz.yaml b/.github/workflows/fuzz.yaml index 2e81a534..380bf5cb 100644 --- a/.github/workflows/fuzz.yaml +++ b/.github/workflows/fuzz.yaml @@ -3,6 +3,10 @@ name: Fuzz "on": schedule: - cron: "0 0 * * *" +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: bytes: name: Fuzz bytes @@ -14,6 +18,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install nightly Rust toolchain run: | @@ -40,6 +46,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install nightly Rust toolchain run: | diff --git a/.github/workflows/repo-labels.yaml b/.github/workflows/repo-labels.yaml index a9aaf212..8f83a1d5 100644 --- a/.github/workflows/repo-labels.yaml +++ b/.github/workflows/repo-labels.yaml @@ -15,15 +15,24 @@ schedule: - cron: "0 0 * * TUE" name: Create Repository Labels +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: labels: name: Synchronize repository labels runs-on: ubuntu-24.04 + permissions: + contents: read # required to checkout + issues: write # required to create and destroy labels steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Sync GitHub Issue Labels - uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5.3.0 + uses: crazy-max/ghaction-github-labeler@548a7c3603594ec17c819e1239f281a3b801ab4d # v6.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} yaml-file: .github/labels.yaml diff --git a/.github/workflows/rustdoc.yaml b/.github/workflows/rustdoc.yaml index e5efdca0..bc87530f 100644 --- a/.github/workflows/rustdoc.yaml +++ b/.github/workflows/rustdoc.yaml @@ -20,7 +20,7 @@ jobs: name: Build Rust API docs runs-on: ubuntu-24.04 permissions: - contents: write + contents: write # required to deploy documentation to gh-pages env: RUSTDOCFLAGS: -D warnings -D rustdoc::broken_intra_doc_links --cfg docsrs RUST_BACKTRACE: 1 diff --git a/mise.toml b/mise.toml index ed30fbe2..97d630d9 100644 --- a/mise.toml +++ b/mise.toml @@ -24,7 +24,6 @@ run = "pnpm install" sources = ["package.json", "pnpm-lock.yaml", "mise.toml"] outputs = ["node_modules/.pnpm/lock.yaml"] - [tasks.build] description = "Build Rust workspace" run = "cargo build --workspace"