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..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,18 +27,8 @@ updates:
- lopopolo
labels:
- A-deps
- - package-ecosystem: bundler
- directory: "/"
- schedule:
- interval: monthly
- groups:
- bundler-deps:
- patterns:
- - "*"
- assignees:
- - lopopolo
- labels:
- - A-deps
+ cooldown:
+ default-days: 7
- package-ecosystem: npm
directory: "/"
schedule:
@@ -49,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 b06fa031..1df27cc5 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 # required to add findings via sarif
+ 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..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-latest
+ 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/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..380bf5cb 100644
--- a/.github/workflows/fuzz.yaml
+++ b/.github/workflows/fuzz.yaml
@@ -3,22 +3,29 @@ 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
- 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
-
- - name: Install Rust toolchain
- uses: artichoke/setup-rust/build-and-test@v2.0.1
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
- toolchain: nightly
+ persist-credentials: false
+
+ - 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 +38,22 @@ 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
-
- - name: Install Rust toolchain
- uses: artichoke/setup-rust/build-and-test@v2.0.1
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
- toolchain: nightly
+ persist-credentials: false
+
+ - 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..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-latest
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read # required to checkout
+ issues: write # required to create and destroy labels
steps:
- - uses: actions/checkout@v5.0.0
+ - 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 2dd5b6b8..bc87530f 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 # required to deploy documentation to gh-pages
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
[](https://github.com/artichoke/strftime-ruby/actions)
-[](https://codecov.artichokeruby.org/strftime-ruby/index.html)
[](https://twitter.com/artichokeruby)
[](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..97d630d9
--- /dev/null
+++ b/mise.toml
@@ -0,0 +1,79 @@
+[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");
}
}
}
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.