From 6aceadbd90c55a9de2e4e96960719d1b2814f8f8 Mon Sep 17 00:00:00 2001 From: CruxExperts Date: Fri, 23 Oct 2026 15:30:47 -0500 Subject: [PATCH 1/2] ci(release): prepare guarded Release Please drafts Add draft-PR configuration and a local release preparation helper while preserving signed candidate and publication boundaries. Keep execution disabled pending a patched upstream Action bundle. Release-Type: none --- .github/CONTRIBUTING.md | 2 +- .github/workflows/release-please.yml | 33 ++ .release-please-manifest.json | 3 + CHANGELOG.md | 3 + docs/PUBLISHING_CHECKLIST.md | 2 + docs/README.md | 2 +- docs/VERSIONING.md | 84 ++++- release-please-config.json | 14 + scripts/check_version_sync.py | 11 + scripts/prepare_release.py | 468 +++++++++++++++++++++++++++ tests/test_prepare_release.py | 252 +++++++++++++++ 11 files changed, 867 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json create mode 100644 scripts/prepare_release.py create mode 100644 tests/test_prepare_release.py diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3964707..818387d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -41,7 +41,7 @@ The production CLI and dashboard share an operations service. Do not add an alte ## Commits and pull requests -Use conventional commit subjects such as `fix: reject unsafe restore targets` or `docs: clarify replica setup`. Normal batches use a patch increment unless the commit body includes a deliberate `Release-Type: major|minor|patch|none` trailer. See [release versioning](../docs/VERSIONING.md). +Use conventional commit subjects such as `fix: reject unsafe restore targets` or `docs: clarify replica setup`. Release Please prepares draft release PRs with a patch increment, including for `feat:` commits. Use a supported `Release-As: X.Y.Z` body trailer for an exact different version; historical `Release-Type` trailers are review notes and are not parsed by the bot. Prepare and sign the release branch before marking it ready. See [release versioning](../docs/VERSIONING.md#prepare-the-release-pull-request). Open pull requests against `main`, complete the [pull-request template](pull_request_template.md), and describe the exact checks and platforms exercised. Do not describe fixture or Garage testing as B2/AWS or Ubuntu package qualification. A successful file restore verifies file data; it does not demonstrate application recovery. diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..50c8efa --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,33 @@ +name: Release Please + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-please-main + cancel-in-progress: false + +jobs: + release-pr: + # Activation blocked: v5.0.0 bundles affected Handlebars (see VERSIONING.md). + # Re-audit a patched upstream lock AND dist before removing the false guard. + if: false + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + steps: + - name: Prepare a draft release pull request + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + target-branch: main + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + skip-github-release: true diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..2e9c48c --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "2.0.5" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 96b93ac..cee80ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ current uv-based install, development, and release instructions. ### Added +- A Release Please draft-PR configuration and local preparation command. + Activation is blocked pending a patched upstream Action; signed tags and + verified artifact publication retain their existing deliberate workflow. - Reviewed repository lock inspection and stale-lock removal through the native production dispatcher. Unlock requires an exact review token, explicit approval, a confirmed quiet window, and only own-host, same-user locks whose diff --git a/docs/PUBLISHING_CHECKLIST.md b/docs/PUBLISHING_CHECKLIST.md index 6ef88d3..5a21c44 100644 --- a/docs/PUBLISHING_CHECKLIST.md +++ b/docs/PUBLISHING_CHECKLIST.md @@ -62,6 +62,8 @@ Use this checklist before changing public project metadata or publishing a relea ## Version and release state +- [ ] The Release Please draft has been prepared with `uv run python scripts/prepare_release.py`, its curated notes reviewed, and its final commit signed and validated before it is marked ready. +- [ ] Release Please remains PR-only; signed tags and checksum publication use the existing release workflow. A bot-created draft is not evidence of a published release. - [ ] `VERSION` contains the intended semantic version. - [ ] Stable or prerelease version and tag match package metadata, release workflow, README badge, and dated changelog section. - [ ] `bbackup/__init__.py`, `pyproject.toml`, README badge, `CHANGELOG.md`, and generated CLI skills docs match `VERSION`. diff --git a/docs/README.md b/docs/README.md index ec66584..a1912c2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,7 +45,7 @@ until they migrate. ## Maintainer references -- [Versioning](VERSIONING.md): canonical package version, release tags and validation. +- [Versioning](VERSIONING.md): Release Please drafts, canonical package version, signed release tags and validation. - [Publishing checklist](PUBLISHING_CHECKLIST.md): community files and release readiness. - [Qualification record](development/version-2.md): candidate results and deferred assurance. - [Release dependency evidence](development/release-dependencies.json): reviewed versions, source timestamps, lock hash and audit limits. diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index bb6fce0..178cf0d 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -21,9 +21,11 @@ stable tag such as `v2.0.5` is not. Every tag must match `VERSION`, its dated changelog section, and generated schemas before release validation. Normal release batches use a patch bump by default, including commits with -`feat:` subjects. Use a `Release-Type: major|minor|patch|none` trailer in the -commit body when a release needs a non-default impact or an explicit no-bump -decision. +`feat:` subjects. Release Please uses `always-bump-patch`; use its supported +`Release-As: X.Y.Z` commit-body trailer to request an exact non-default version. +Historical `Release-Type: major|minor|patch|none` trailers are human review notes, +not instructions parsed by Release Please. Defer or close a draft release PR +when its changes are not ready for a release; publication is always deliberate. Run the version sync check: @@ -60,8 +62,80 @@ before push. ## Release flow -1. Update `VERSION`. -2. Sync the version references listed above. +### Prepare the release pull request + +**Activation is blocked as of 2026-10-23.** The SHA-pinned +`.github/workflows/release-please.yml` has an explicit false job guard. Its +triggers are defined for `main` pushes and manual dispatch, but the Action is +not executed. The audited v5.0.0 lock includes `handlebars@4.7.8`, affected by +[critical CVE-2026-33937](https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-2w6w-674q-4c4q) +and [critical CVE-2026-106445](https://github.com/advisories/GHSA-p8wg-vrv2-v86f). +The latter requires at least 4.7.10. Other transitive advisories are also +present. Exact-lock OSV and npm registry checks confirm the findings; the +upstream `main` lock remains affected. This is a dependency finding, not a +claim that this repository was exploited. + +Before activation, review a patched immutable upstream release, its lockfile +**and rebuilt bundled Action**, rerun advisory checks, replace the SHA, and +restore the job condition to permit only `refs/heads/main`. Then allow Actions +to create PRs in repository settings and verify the first draft. No repository +permission setting was changed during this blocked setup. To roll back the +setup, revert its task commit; no tag or published artifact needs removal. + +The configuration below describes the intended behavior after activation. +Its root manifest starts at `2.0.5`; it proposes +the next version in `VERSION` and `.release-please-manifest.json` through a +**draft PR**. It does not create tags, GitHub releases, or merge PRs. The +existing signed-tag and signed-checksum publication flow remains authoritative. + +Release Please deliberately leaves the curated changelog and generated files +to a maintainer. Its bot-created commit is unsigned. Preserve the default +branch's required signatures: create a candidate branch from current `main`, +apply the draft's changes, then prepare and sign them locally. Do not merge +the unsigned draft or relax branch protection. For example, after inspecting +the draft and choosing its exact branch name: + +```bash +git fetch origin +git switch -c release/candidate origin/main +git diff origin/main...origin/RELEASE_BRANCH -- VERSION .release-please-manifest.json | git apply +``` + +Run the preparation helper on that candidate branch: + +```bash +uv run python scripts/prepare_release.py +``` + +This synchronizes package metadata, the root lockfile version, the README +badge, this document's current version, and the existing CLI generators. It +moves the complete curated `[Unreleased]` content to the dated candidate +section, preserving historical release and support evidence. Review that +content and add any missing notes from the draft PR before marking it ready. +The dependency versions remain unchanged. + +Commit the prepared files with the required project signing key and push the +candidate branch. Open a normal PR containing that signed commit, referencing +the bot draft; close the bot draft as superseded once the candidate merges. +No private signing key or personal token is installed in +Actions. Release Please uses `GITHUB_TOKEN`; GitHub does not automatically run +PR workflows for its bot-created commits. The normal signed candidate PR runs +PR CI. If needed, explicitly dispatch the existing CI workflow for that branch: + +```bash +gh workflow run ci.yml --ref RELEASE_BRANCH +``` + +Verify the run's head SHA matches the final prepared commit. Draft creation +alone is not release readiness. Repository settings must allow GitHub Actions +to create PRs; default workflow token permissions may remain read-only because +this workflow requests only contents and pull-request writes. Required branch +protection, signatures, review conversations and checks still apply. + +### Validate and publish deliberately + +1. Review the draft's proposed `VERSION` and manifest, or update both for a manually selected release. +2. Run the preparation command above, then review all synchronized references and curated notes. 3. Run `uv lock` if dependencies or supported Python versions changed. 4. Add a dated `CHANGELOG.md` section for the release. 5. Run: diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..6b283d6 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "simple", + "version-file": "VERSION", + "versioning": "always-bump-patch", + "include-component-in-tag": false, + "skip-github-release": true, + "skip-changelog": true, + "draft-pull-request": true + } + } +} diff --git a/scripts/check_version_sync.py b/scripts/check_version_sync.py index 8aed8c0..00e4534 100644 --- a/scripts/check_version_sync.py +++ b/scripts/check_version_sync.py @@ -6,6 +6,7 @@ import argparse import ast +import json import re import sys import tomllib @@ -52,6 +53,16 @@ def main(argv: list[str]) -> int: errors: list[str] = [] version = read_version() + manifest = json.loads((REPO_ROOT / ".release-please-manifest.json").read_text(encoding="utf-8")) + if manifest.get(".") != version: + errors.append(f"Release Please manifest version is {manifest.get('.')!r}, expected {version!r}") + + lock = tomllib.loads((REPO_ROOT / "uv.lock").read_text(encoding="utf-8")) + root_packages = [package for package in lock.get("package", []) + if package.get("name") == "bbackup" and package.get("source") == {"editable": "."}] + if len(root_packages) != 1 or root_packages[0].get("version") != version: + errors.append("uv.lock root bbackup package version does not match VERSION") + package_version = read_python_assignment(REPO_ROOT / "bbackup" / "__init__.py", "__version__") if package_version != version: errors.append(f"bbackup/__init__.py __version__ is {package_version!r}, expected {version!r}") diff --git a/scripts/prepare_release.py b/scripts/prepare_release.py new file mode 100644 index 0000000..621311d --- /dev/null +++ b/scripts/prepare_release.py @@ -0,0 +1,468 @@ +"""Prepare a Release Please draft locally without publishing or committing it.""" + +from __future__ import annotations + +import argparse +import datetime as dt +import json +import re +import subprocess +import sys +import tomllib +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parent.parent +VERSION_PATTERN = r"\d+\.\d+\.\d+(?:-(?:alpha|beta|rc)\.\d+)?" +VERSION_RE = re.compile(rf"{VERSION_PATTERN}\Z") +CHANGELOG_BASE = "https://github.com/CruxExperts/BestBackup/compare/" + + +class ReleasePreparationError(ValueError): + """Raised when release inputs are malformed or ambiguous.""" + + +def _read(root: Path, relative: str) -> str: + try: + return (root / relative).read_bytes().decode("utf-8") + except (OSError, UnicodeError) as exc: + raise ReleasePreparationError(f"cannot read {relative}: {exc}") from exc + + +def _one_substitution(text: str, pattern: str, replacement: str, relative: str) -> str: + updated, count = re.subn(pattern, replacement, text, flags=re.MULTILINE) + if count != 1: + raise ReleasePreparationError( + f"expected exactly one current version field in {relative}; found {count}" + ) + return updated + + +def _replace_line_value( + text: str, + line_pattern: str, + expected: str, + replacement: str, + relative: str, +) -> str: + pattern = rf"^{line_pattern}{re.escape(expected)}(?=\r?$)" + return _one_substitution(text, pattern, lambda _match: replacement, relative) + + +def _version_from_file(text: str) -> str: + value = text.strip() + if not VERSION_RE.fullmatch(value): + raise ReleasePreparationError(f"VERSION has an invalid value: {value!r}") + return value + + +def _project_version(text: str) -> str: + try: + project = tomllib.loads(text).get("project") + except tomllib.TOMLDecodeError as exc: + raise ReleasePreparationError(f"pyproject.toml is malformed: {exc}") from exc + value = project.get("version") if isinstance(project, dict) else None + if not isinstance(value, str) or not VERSION_RE.fullmatch(value): + raise ReleasePreparationError("pyproject.toml [project].version is missing or invalid") + return value + + +def _manifest_version(text: str) -> str: + try: + manifest = json.loads(text) + except json.JSONDecodeError as exc: + raise ReleasePreparationError(f".release-please-manifest.json is malformed: {exc}") from exc + target = manifest.get(".") if isinstance(manifest, dict) else None + if not isinstance(target, str) or not VERSION_RE.fullmatch(target): + raise ReleasePreparationError( + ".release-please-manifest.json must contain a valid root package version at '.'" + ) + return target + + +def _replace_project_version(text: str, current: str, target: str) -> str: + section = re.search(r"(?ms)^\[project\]\r?\n(?P.*?)(?=^\[|\Z)", text) + if section is None: + raise ReleasePreparationError("pyproject.toml has no [project] table") + body = section.group("body") + updated_body = _replace_line_value( + body, + r'version = "', + f'{current}"', + f'version = "{target}"', + "pyproject.toml [project].version", + ) + return text[: section.start("body")] + updated_body + text[section.end("body") :] + + +def _replace_lock_package_version(text: str, current: str, target: str) -> str: + lines = text.splitlines(keepends=True) + starts = [index for index, line in enumerate(lines) if line.rstrip("\r\n") == "[[package]]"] + matching: list[tuple[int, int]] = [] + for offset, start in enumerate(starts): + end = starts[offset + 1] if offset + 1 < len(starts) else len(lines) + block = "".join(lines[start:end]) + if re.search(r'^name = "bbackup"\r?$', block, re.MULTILINE): + matching.append((start, end)) + if len(matching) != 1: + raise ReleasePreparationError( + f"expected one bbackup package block in uv.lock; found {len(matching)}" + ) + start, end = matching[0] + block = "".join(lines[start:end]) + updated = _replace_line_value( + block, + r'version = "', + f'{current}"', + f'version = "{target}"', + "uv.lock bbackup package version", + ) + return "".join(lines[:start]) + updated + "".join(lines[end:]) + + +def _replace_readme_badge(text: str, current: str, target: str) -> str: + old_url_version = current.replace("-", "--") + new_url_version = target.replace("-", "--") + pattern = ( + rf"(\[!\[Version ){re.escape(current)}" + rf"(\]\(https://img\.shields\.io/badge/version-)" + rf"{re.escape(old_url_version)}" + rf"(-22c55e\?style=flat-square\)\]" + rf"\(https://github\.com/CruxExperts/BestBackup/releases/latest\))" + ) + return _one_substitution( + text, + pattern, + rf"\g<1>{target}\g<2>{new_url_version}\g<3>", + "README.md version badge", + ) + + +def _validate_versioning(text: str, current: str, target: str) -> str: + return _replace_line_value( + text, + r"`VERSION` is the canonical release version\. The current version is `", + f"{current}`.", + f"`VERSION` is the canonical release version. The current version is `{target}`.", + "docs/VERSIONING.md current version declaration", + ) + + +def _line_ending(text: str) -> str: + return "\r\n" if "\r\n" in text else "\n" + + +def _line_values(text: str) -> list[str]: + return text.splitlines(keepends=True) + + +def _line_body(line: str) -> str: + return line.rstrip("\r\n") + + +def _section_headings(lines: list[str]) -> list[tuple[int, str, str]]: + headings: list[tuple[int, str, str]] = [] + for index, line in enumerate(lines): + body = _line_body(line) + if body.startswith("## "): + match = re.fullmatch(r"## \[([^\]]+)\](?: - (\d{4}-\d{2}-\d{2}))?", body) + if match is None: + raise ReleasePreparationError( + f"ambiguous changelog section heading on line {index + 1}: {body!r}" + ) + headings.append((index, match.group(1), match.group(2) or "")) + return headings + + +def _definition_indices(lines: list[str], label: str) -> list[int]: + return [ + index + for index, line in enumerate(lines) + if _line_body(line).startswith(f"[{label}]:") + ] + + +def _changelog_plan(text: str, current: str, target: str, release_date: str) -> str: + lines = _line_values(text) + newline = _line_ending(text) + headings = _section_headings(lines) + unreleased_headings = [item for item in headings if item[1] == "Unreleased"] + current_headings = [item for item in headings if item[1] == current] + target_headings = [item for item in headings if item[1] == target] + if len(unreleased_headings) != 1 or len(current_headings) != 1: + raise ReleasePreparationError( + "CHANGELOG.md must contain exactly one [Unreleased] and one current-version section" + ) + if target_headings: + raise ReleasePreparationError(f"CHANGELOG.md already contains release section [{target}]") + + unreleased_index = unreleased_headings[0][0] + current_index, _current_label, current_date = current_headings[0] + if current_index <= unreleased_index or not re.fullmatch(r"\d{4}-\d{2}-\d{2}", current_date): + raise ReleasePreparationError("CHANGELOG.md current release section is malformed") + if current_index != next( + (index for index, label, _date in headings if index > unreleased_index), -1 + ): + raise ReleasePreparationError("CHANGELOG.md has ambiguous content between release sections") + + unreleased_links = _definition_indices(lines, "Unreleased") + current_links = _definition_indices(lines, current) + target_links = _definition_indices(lines, target) + if len(unreleased_links) != 1 or len(current_links) != 1 or target_links: + raise ReleasePreparationError("CHANGELOG.md comparison links are missing, duplicated, or stale") + + expected_unreleased = f"[Unreleased]: {CHANGELOG_BASE}v{current}...HEAD" + unreleased_link = _line_body(lines[unreleased_links[0]]) + if unreleased_link != expected_unreleased: + raise ReleasePreparationError("CHANGELOG.md [Unreleased] comparison link does not match VERSION") + + current_link = _line_body(lines[current_links[0]]) + prefix = f"[{current}]: {CHANGELOG_BASE}v" + suffix = f"...v{current}" + if not current_link.startswith(prefix) or not current_link.endswith(suffix): + raise ReleasePreparationError("CHANGELOG.md current release comparison link is malformed") + previous = current_link[len(prefix) : -len(suffix)] + if not VERSION_RE.fullmatch(previous): + raise ReleasePreparationError("CHANGELOG.md current release comparison has an invalid prior tag") + + section_body = lines[unreleased_index + 1 : current_index] + while section_body and not _line_body(section_body[0]).strip(): + section_body.pop(0) + while section_body and not _line_body(section_body[-1]).strip(): + section_body.pop() + prepared_section = [ + f"## [Unreleased]{newline}", + newline, + f"## [{target}] - {release_date}{newline}", + newline, + *section_body, + newline, + ] + updated_lines = lines[:unreleased_index] + prepared_section + lines[current_index:] + + updated_unreleased_idx = next( + index for index, line in enumerate(updated_lines) if _line_body(line).startswith("[Unreleased]:") + ) + updated_lines[updated_unreleased_idx] = ( + f"[Unreleased]: {CHANGELOG_BASE}v{target}...HEAD{newline}" + ) + updated_current_idx = next( + index for index, line in enumerate(updated_lines) if _line_body(line).startswith(f"[{current}]:") + ) + updated_lines.insert( + updated_current_idx, + f"[{target}]: {CHANGELOG_BASE}v{current}...v{target}{newline}", + ) + return "".join(updated_lines) + + +def _is_prepared_changelog(text: str, target: str) -> bool: + lines = _line_values(text) + headings = _section_headings(lines) + unreleased = [item for item in headings if item[1] == "Unreleased"] + target_sections = [item for item in headings if item[1] == target] + if len(unreleased) != 1 or len(target_sections) != 1 or not target_sections[0][2]: + return False + next_heading_index = next( + (index for index, _label, _date in headings if index > unreleased[0][0]), -1 + ) + if next_heading_index != target_sections[0][0]: + return False + if any(_line_body(line).strip() for line in lines[unreleased[0][0] + 1 : next_heading_index]): + return False + unreleased_links = _definition_indices(lines, "Unreleased") + target_links = _definition_indices(lines, target) + if len(unreleased_links) != 1 or len(target_links) != 1: + return False + if _line_body(lines[unreleased_links[0]]) != f"[Unreleased]: {CHANGELOG_BASE}v{target}...HEAD": + return False + target_link = _line_body(lines[target_links[0]]) + prefix = f"[{target}]: {CHANGELOG_BASE}v" + suffix = f"...v{target}" + if not target_link.startswith(prefix) or not target_link.endswith(suffix): + return False + previous = target_link[len(prefix) : -len(suffix)] + return bool(VERSION_RE.fullmatch(previous)) + + +def _source_paths(root: Path) -> dict[str, str]: + return { + relative: _read(root, relative) + for relative in ( + "VERSION", + ".release-please-manifest.json", + "pyproject.toml", + "bbackup/__init__.py", + "uv.lock", + "README.md", + "docs/VERSIONING.md", + "CHANGELOG.md", + ) + } + + +def _planned_files(root: Path, release_date: str) -> dict[str, str]: + files = _source_paths(root) + target = _version_from_file(files["VERSION"]) + manifest_target = _manifest_version(files[".release-please-manifest.json"]) + if manifest_target != target: + raise ReleasePreparationError( + f"VERSION ({target}) and .release-please-manifest.json ({manifest_target}) disagree" + ) + current = _project_version(files["pyproject.toml"]) + if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", release_date): + raise ReleasePreparationError(f"release date is invalid: {release_date!r}") + try: + dt.date.fromisoformat(release_date) + except ValueError as exc: + raise ReleasePreparationError(f"release date is invalid: {release_date!r}") from exc + + if current == target: + if not _is_prepared_changelog(files["CHANGELOG.md"], target): + raise ReleasePreparationError( + f"VERSION is already {target}, but its changelog does not prove a prepared release" + ) + files["pyproject.toml"] = _replace_project_version( + files["pyproject.toml"], current, target + ) + files["bbackup/__init__.py"] = _replace_line_value( + files["bbackup/__init__.py"], + r'__version__ = "', + f'{current}"', + f'__version__ = "{target}"', + "bbackup/__init__.py __version__", + ) + files["uv.lock"] = _replace_lock_package_version(files["uv.lock"], current, target) + files["README.md"] = _replace_readme_badge(files["README.md"], current, target) + files["docs/VERSIONING.md"] = _validate_versioning( + files["docs/VERSIONING.md"], current, target + ) + return files + + files["pyproject.toml"] = _replace_project_version( + files["pyproject.toml"], current, target + ) + files["bbackup/__init__.py"] = _replace_line_value( + files["bbackup/__init__.py"], + r'__version__ = "', + f'{current}"', + f'__version__ = "{target}"', + "bbackup/__init__.py __version__", + ) + files["uv.lock"] = _replace_lock_package_version(files["uv.lock"], current, target) + files["README.md"] = _replace_readme_badge(files["README.md"], current, target) + files["docs/VERSIONING.md"] = _validate_versioning( + files["docs/VERSIONING.md"], current, target + ) + files["CHANGELOG.md"] = _changelog_plan( + files["CHANGELOG.md"], current, target, release_date + ) + return files + + +def _generator_outputs() -> tuple[str, ...]: + return ( + "docs/cli-skills.md", + "docs/cli-skills-index.json", + "bbackup/data/cli-skills.md", + "bbackup/data/cli-skills-index.json", + "docs/production-cli.md", + "bbackup/data/production-schemas.json", + ) + + +def _invoke_generator(root: Path, check: bool) -> None: + script = root / "scripts" / "generate_cli_skills.py" + if not script.is_file(): + raise ReleasePreparationError(f"missing generator: {script}") + command = [sys.executable, str(script)] + if check: + command.append("--check") + subprocess.run(command, cwd=root, check=True) + + +def prepare_release( + *, + release_date: str | None = None, + repo_root: Path = REPO_ROOT, + check: bool = False, +) -> None: + """Prepare a bot-proposed release using VERSION and the root manifest as its target.""" + root = Path(repo_root).resolve() + planned = _planned_files(root, release_date or dt.date.today().isoformat()) + paths = {relative: root / relative for relative in planned} + if check: + mismatches = [ + relative + for relative, content in planned.items() + if paths[relative].read_bytes().decode("utf-8") != content + ] + if mismatches: + raise ReleasePreparationError( + "release preparation is not complete; would update: " + ", ".join(mismatches) + ) + _invoke_generator(root, check=True) + return + + # Keep a small rollback set so a failed generator cannot leave a half-prepared draft. + owned_by_release_please = {"VERSION", ".release-please-manifest.json"} + affected = { + **{relative: path for relative, path in paths.items() if relative not in owned_by_release_please}, + **{relative: root / relative for relative in _generator_outputs()}, + } + original: dict[Path, bytes | None] = {} + for path in affected.values(): + original[path] = path.read_bytes() if path.is_file() else None + try: + for relative, content in planned.items(): + if relative in owned_by_release_please: + continue + paths[relative].write_bytes(content.encode("utf-8")) + _invoke_generator(root, check=False) + except Exception: + for path, content in original.items(): + if content is None: + if path.exists(): + path.unlink() + else: + path.write_bytes(content) + raise + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="Prepare version and changelog files for a local Release Please draft." + ) + parser.add_argument( + "--date", + default=dt.date.today().isoformat(), + help="Release section date (YYYY-MM-DD; defaults to today)", + ) + parser.add_argument( + "--repo-root", + type=Path, + default=REPO_ROOT, + help=argparse.SUPPRESS, + ) + parser.add_argument( + "--check", + action="store_true", + help="Verify that source and generated release files are already prepared without writing", + ) + args = parser.parse_args(argv) + try: + prepare_release( + release_date=args.date, + repo_root=args.repo_root, + check=args.check, + ) + except (ReleasePreparationError, OSError, subprocess.CalledProcessError) as exc: + print(f"prepare-release: {exc}", file=sys.stderr) + return 1 + target = _version_from_file(_read(args.repo_root, "VERSION")) + print(f"prepare-release: {'checked' if args.check else 'prepared'} v{target}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_prepare_release.py b/tests/test_prepare_release.py new file mode 100644 index 0000000..517383e --- /dev/null +++ b/tests/test_prepare_release.py @@ -0,0 +1,252 @@ +from __future__ import annotations + +from pathlib import Path +import shutil + +import pytest + +from scripts import prepare_release as release_script +from scripts.prepare_release import ReleasePreparationError, prepare_release + + +OLD_VERSION = "2.3.4" +TARGET_VERSION = "2.3.5" +RELEASE_DATE = "2026-10-23" +GENERATED = ( + "docs/cli-skills.md", + "docs/cli-skills-index.json", + "bbackup/data/cli-skills.md", + "bbackup/data/cli-skills-index.json", + "docs/production-cli.md", + "bbackup/data/production-schemas.json", +) + + +def _write(root: Path, relative: str, content: str) -> None: + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + +def _make_repo(root: Path, *, real_generator: bool = False) -> None: + _write(root, "VERSION", f"{TARGET_VERSION}\n") + _write(root, ".release-please-manifest.json", f'{{\n ".": "{TARGET_VERSION}"\n}}\n') + _write( + root, + "pyproject.toml", + '[project]\nname = "bbackup"\nversion = "2.3.4"\nrequires-python = ">=3.12"\n\n' + '[tool.example]\nversion = "leave-this-alone"\n', + ) + _write( + root, + "bbackup/__init__.py", + '"""Package source."""\n\n__version__ = "2.3.4"\n__author__ = "Example"\n', + ) + _write( + root, + "uv.lock", + 'version = 1\nrevision = 5\nrequires-python = ">=3.12"\n\n' + '[[package]]\nname = "dependency"\nversion = "8.8.8"\nsource = { registry = "https://example.invalid" }\n\n' + '[[package]]\nname = "bbackup"\nversion = "2.3.4"\nsource = { editable = "." }\ndependencies = [\n { name = "dependency" },\n]\n', + ) + _write( + root, + "README.md", + "# bbackup\n\n" + "[![Version 2.3.4](https://img.shields.io/badge/version-2.3.4-22c55e?style=flat-square)]" + "(https://github.com/CruxExperts/BestBackup/releases/latest)\n\n" + "## v2.3.4 tested scope\n", + ) + _write( + root, + "docs/VERSIONING.md", + "# Versioning\n\n`VERSION` is the canonical release version. The current version is `2.3.4`.\n" + "An earlier support note mentions v2.3.4 and stays historical.\n", + ) + _write( + root, + "CHANGELOG.md", + "# Changelog\n\nCurated release notes stay with their release.\n\n" + "## [Unreleased]\n\n" + "### Added\n\n- Keep this complete curated note.\n\n" + "### Fixed\n\n- Keep this other category too.\n\n" + "## [2.3.4] - 2026-10-20\n\n### Changed\n\n- Existing historical entry.\n\n" + "[Unreleased]: https://github.com/CruxExperts/BestBackup/compare/v2.3.4...HEAD\n" + "[2.3.4]: https://github.com/CruxExperts/BestBackup/compare/v2.3.3...v2.3.4\n", + ) + fake_generator = ( + "from __future__ import annotations\n" + "import os, re, sys\n" + "from pathlib import Path\n" + "root = Path(__file__).resolve().parent.parent\n" + "version = re.search(r'__version__ = \\\"([^\\\"]+)', (root / 'bbackup/__init__.py').read_text()).group(1)\n" + f"outputs = {GENERATED!r}\n" + "content = f'Generated version: {version}\\n'\n" + "if '--check' in sys.argv:\n" + " raise SystemExit(0 if all((root / name).is_file() and (root / name).read_text() == content for name in outputs) else 1)\n" + "(root / outputs[0]).parent.mkdir(parents=True, exist_ok=True)\n" + "(root / outputs[0]).write_text(content)\n" + "if os.environ.get('FAIL_RELEASE_GENERATOR'):\n" + " raise SystemExit(2)\n" + "for name in outputs[1:]:\n" + " path = root / name\n" + " path.parent.mkdir(parents=True, exist_ok=True)\n" + " path.write_text(content)\n" + "(root / 'generator-python.txt').write_text(sys.executable)\n" + ) + if real_generator: + source_root = Path(__file__).resolve().parents[1] + for relative in ( + "scripts/generate_cli_skills.py", + "bbackup/cli_metadata.py", + ): + destination = root / relative + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source_root / relative, destination) + else: + _write(root, "scripts/generate_cli_skills.py", fake_generator) + + +def _snapshot(root: Path) -> dict[str, bytes | None]: + paths = ( + "VERSION", + ".release-please-manifest.json", + "pyproject.toml", + "bbackup/__init__.py", + "uv.lock", + "README.md", + "docs/VERSIONING.md", + "CHANGELOG.md", + "generator-python.txt", + *GENERATED, + ) + return { + relative: (root / relative).read_bytes() if (root / relative).is_file() else None + for relative in paths + } + + +def test_no_version_argument_uses_bot_target_and_preserves_notes( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, +) -> None: + _make_repo(tmp_path) + initial_lock = (tmp_path / "uv.lock").read_bytes() + initial_version = (tmp_path / "VERSION").read_bytes() + initial_manifest = (tmp_path / ".release-please-manifest.json").read_bytes() + monkeypatch.setattr(release_script, "REPO_ROOT", tmp_path) + + assert release_script.main([]) == 0 + assert f"prepared v{TARGET_VERSION}" in capsys.readouterr().out + + assert (tmp_path / "VERSION").read_text() == f"{TARGET_VERSION}\n" + assert (tmp_path / "VERSION").read_bytes() == initial_version + assert (tmp_path / ".release-please-manifest.json").read_bytes() == initial_manifest + assert 'version = "2.3.5"' in (tmp_path / "pyproject.toml").read_text() + assert '__version__ = "2.3.5"' in (tmp_path / "bbackup/__init__.py").read_text() + lock = (tmp_path / "uv.lock").read_bytes() + assert b'name = "dependency"\nversion = "8.8.8"' in lock + assert lock.replace(b'version = "2.3.5"', b'version = "2.3.4"') == initial_lock + assert "Version 2.3.5" in (tmp_path / "README.md").read_text() + assert "## v2.3.4 tested scope" in (tmp_path / "README.md").read_text() + assert "current version is `2.3.5`" in (tmp_path / "docs/VERSIONING.md").read_text() + assert "An earlier support note mentions v2.3.4" in (tmp_path / "docs/VERSIONING.md").read_text() + + changelog = (tmp_path / "CHANGELOG.md").read_text() + assert ( + f"## [Unreleased]\n\n## [{TARGET_VERSION}] - " + f"{release_script.dt.date.today().isoformat()}" + ) in changelog + assert changelog.count("### Added") == 1 + assert changelog.count("### Fixed") == 1 + assert "- Keep this complete curated note." in changelog + assert "[Unreleased]: https://github.com/CruxExperts/BestBackup/compare/v2.3.5...HEAD" in changelog + assert "[2.3.5]: https://github.com/CruxExperts/BestBackup/compare/v2.3.4...v2.3.5" in changelog + + for relative in GENERATED: + assert (tmp_path / relative).read_text() == "Generated version: 2.3.5\n" + assert (tmp_path / "generator-python.txt").read_text() == __import__("sys").executable + + +def test_is_idempotent_and_check_mode_is_read_only(tmp_path: Path) -> None: + _make_repo(tmp_path) + prepare_release(release_date=RELEASE_DATE, repo_root=tmp_path) + prepared = _snapshot(tmp_path) + + prepare_release(release_date="2026-10-24", repo_root=tmp_path) + assert _snapshot(tmp_path) == prepared + + before_check = _snapshot(tmp_path) + prepare_release(release_date=RELEASE_DATE, repo_root=tmp_path, check=True) + assert _snapshot(tmp_path) == before_check + + +def test_runs_repository_generator_for_source_and_package_copies(tmp_path: Path) -> None: + _make_repo(tmp_path, real_generator=True) + + prepare_release(release_date=RELEASE_DATE, repo_root=tmp_path) + + source_catalog = (tmp_path / "docs/cli-skills.md").read_text() + package_catalog = (tmp_path / "bbackup/data/cli-skills.md").read_text() + assert "Version: 2.3.5." in source_catalog + assert package_catalog == source_catalog + assert "package version is v2.3.5." in (tmp_path / "docs/production-cli.md").read_text() + assert (tmp_path / "docs/cli-skills-index.json").is_file() + assert (tmp_path / "bbackup/data/cli-skills-index.json").is_file() + assert (tmp_path / "bbackup/data/production-schemas.json").is_file() + + +@pytest.mark.parametrize("damage", ["target-heading", "current-link"]) +def test_rejects_ambiguous_changelog_without_partial_writes( + tmp_path: Path, damage: str +) -> None: + _make_repo(tmp_path) + changelog = tmp_path / "CHANGELOG.md" + text = changelog.read_text() + if damage == "target-heading": + text = text.replace("## [2.3.4] - 2026-10-20", "## [2.3.5] - 2026-10-20", 1) + else: + text = text.replace("[2.3.4]: https://", "[2.3.4]: https://stale/", 1) + changelog.write_text(text) + before = _snapshot(tmp_path) + + with pytest.raises(ReleasePreparationError): + prepare_release(release_date=RELEASE_DATE, repo_root=tmp_path) + + assert _snapshot(tmp_path) == before + + +def test_check_mode_rejects_unprepared_target_without_writing(tmp_path: Path) -> None: + _make_repo(tmp_path) + before = _snapshot(tmp_path) + + with pytest.raises(ReleasePreparationError, match="not complete"): + prepare_release(release_date=RELEASE_DATE, repo_root=tmp_path, check=True) + + assert _snapshot(tmp_path) == before + + +def test_manifest_must_match_bot_proposed_version_before_writes(tmp_path: Path) -> None: + _make_repo(tmp_path) + manifest = tmp_path / ".release-please-manifest.json" + manifest.write_text('{\n ".": "2.3.6"\n}\n') + before = _snapshot(tmp_path) + + with pytest.raises(ReleasePreparationError, match="VERSION .* disagree"): + prepare_release(release_date=RELEASE_DATE, repo_root=tmp_path) + + assert _snapshot(tmp_path) == before + + +def test_generator_failure_rolls_back_source_and_generated_files( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _make_repo(tmp_path) + before = _snapshot(tmp_path) + monkeypatch.setenv("FAIL_RELEASE_GENERATOR", "1") + + with pytest.raises(Exception): + prepare_release(release_date=RELEASE_DATE, repo_root=tmp_path) + + assert _snapshot(tmp_path) == before From d9e4f7a33dd2cc31cacfd5dd7d9fd5339d130329 Mon Sep 17 00:00:00 2001 From: CruxExperts Date: Fri, 23 Oct 2026 15:53:30 -0500 Subject: [PATCH 2/2] ci(release): run the audited official Release Please CLI Replace the affected prebuilt Action with a frozen 162-package graph and verified patched Node runtime. Preserve signed candidates and publication gates, and exclude installed tooling from documentation scans and source artifacts. Release-Type: none --- .github/release-please/package-lock.json | 1820 ++++++++++++++++++++ .github/release-please/package.json | 12 + .github/workflows/release-please.yml | 108 +- .gitignore | 1 + CHANGELOG.md | 7 +- INSTALL.md | 6 +- MANIFEST.in | 3 + QUICKSTART.md | 23 +- docs/README.md | 35 +- docs/VERSIONING.md | 38 +- docs/development/release-dependencies.json | 71 + docs/development/version-2.md | 26 +- scripts/check_markdown_standards.py | 9 +- tests/test_markdown_standards.py | 20 + 14 files changed, 2106 insertions(+), 73 deletions(-) create mode 100644 .github/release-please/package-lock.json create mode 100644 .github/release-please/package.json diff --git a/.github/release-please/package-lock.json b/.github/release-please/package-lock.json new file mode 100644 index 0000000..3277a2f --- /dev/null +++ b/.github/release-please/package-lock.json @@ -0,0 +1,1820 @@ +{ + "name": "bbackup-release-tooling", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bbackup-release-tooling", + "version": "1.0.0", + "dependencies": { + "release-please": "17.11.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@conventional-commits/parser": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@conventional-commits/parser/-/parser-0.4.1.tgz", + "integrity": "sha512-H2ZmUVt6q+KBccXfMBhbBF14NlANeqHTXL4qCL6QGbMzrc4HDXyzWuxPxPNbz71f/5UkR5DrycP5VO9u7crahg==", + "license": "ISC", + "dependencies": { + "unist-util-visit": "^2.0.3", + "unist-util-visit-parents": "^3.1.1" + } + }, + "node_modules/@google-automations/git-file-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@google-automations/git-file-utils/-/git-file-utils-3.2.1.tgz", + "integrity": "sha512-LYOjAnGu/xtEkYYh6W3/1RmeK9HB/fV+xMe7nHc+WiDYrnxAJWBfcyUucoJtYS8XAhVUzNWrQvYDPRbxe67DNQ==", + "license": "Apache-2.0", + "dependencies": { + "@octokit/rest": "^20.1.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": ">= 22" + } + }, + "node_modules/@iarna/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", + "license": "ISC" + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.4.0.tgz", + "integrity": "sha512-nw2Px23Eirx0PFTVoPrb3FEq9Wkmg6vxcDtt38MtBCf1G0tjLCB9cK+3seOXyiw+6+aalgYhvJabQbWwMVKlEQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.1.0.tgz", + "integrity": "sha512-2Vkbda+XRjK6VTI6Lrh0sNBuMT8mZ51EhqMvO+V2rSdmqjs7NAGssxPZfkL23EFzYfSrvxyd1ZjOdqPQMM50og==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.4.4-cjs.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz", + "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", + "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.3.2-cjs.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz", + "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.8.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz", + "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "license": "MIT" + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "license": "MIT" + }, + "node_modules/@types/npm-package-arg": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.4.tgz", + "integrity": "sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "license": "MIT", + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz", + "integrity": "sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==", + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-6.0.1.tgz", + "integrity": "sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==", + "license": "MIT", + "dependencies": { + "conventional-commits-filter": "^3.0.0", + "dateformat": "^3.0.3", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "meow": "^8.1.2", + "semver": "^7.0.0", + "split": "^1.0.1" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/conventional-commits-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", + "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", + "license": "MIT", + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/handlebars": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsep": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.6.0.tgz", + "integrity": "sha512-Y8Tr54CX7B5YGAwo7/n8eumidEX3Opx98RyyQVnlHqUaZcPA8uUzL2v1rshr9ypW9Q6hNjfJtg3sL+8kaNE7TA==", + "license": "MIT", + "engines": { + "node": ">= 20.16.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "license": "MIT", + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "license": "MIT", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/node-html-parser": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-please": { + "version": "17.11.2", + "resolved": "https://registry.npmjs.org/release-please/-/release-please-17.11.2.tgz", + "integrity": "sha512-we0+NgyHjvczxrilD6aujnRNuASd65Jem0dY5evevxSZvz8BnKnB/QRN3UzGA229fcV09lKCYbqeE9HgkXzGmA==", + "license": "Apache-2.0", + "dependencies": { + "@conventional-commits/parser": "^0.4.1", + "@google-automations/git-file-utils": "^3.2.0", + "@iarna/toml": "^3.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/rest": "^20.1.1", + "@types/npm-package-arg": "^6.1.0", + "@xmldom/xmldom": "^0.8.4", + "async-retry": "^1.3.3", + "chalk": "^4.0.0", + "conventional-changelog-conventionalcommits": "^6.0.0", + "conventional-changelog-writer": "^6.0.0", + "conventional-commits-filter": "^3.0.0", + "detect-indent": "^6.1.0", + "diff": "^8.0.3", + "figures": "^3.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.3.1", + "jsonpath-plus": "^10.0.0", + "node-html-parser": "^6.0.0", + "parse-github-repo-url": "^1.4.1", + "semver": "^7.5.3", + "type-fest": "^3.0.0", + "typescript": "^4.6.4", + "unist-util-visit": "^2.0.3", + "unist-util-visit-parents": "^3.1.1", + "xpath": "^0.0.34", + "yaml": "^2.2.2", + "yargs": "^17.0.0" + }, + "bin": { + "release-please": "build/src/bin/release-please.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.13", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.13.tgz", + "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.17.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", + "license": "CC0-1.0" + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xpath": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", + "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==", + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/.github/release-please/package.json b/.github/release-please/package.json new file mode 100644 index 0000000..395a0c4 --- /dev/null +++ b/.github/release-please/package.json @@ -0,0 +1,12 @@ +{ + "name": "bbackup-release-tooling", + "version": "1.0.0", + "private": true, + "description": "Locked official Release Please CLI for release PR preparation", + "engines": { + "node": ">=22" + }, + "dependencies": { + "release-please": "17.11.2" + } +} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 50c8efa..f8f03a5 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -14,20 +14,106 @@ concurrency: jobs: release-pr: - # Activation blocked: v5.0.0 bundles affected Handlebars (see VERSIONING.md). - # Re-audit a patched upstream lock AND dist before removing the false guard. - if: false - runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 timeout-minutes: 10 permissions: contents: write pull-requests: write steps: - - name: Prepare a draft release pull request - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + - name: Check out the current repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: - token: ${{ secrets.GITHUB_TOKEN }} - target-branch: main - config-file: release-please-config.json - manifest-file: .release-please-manifest.json - skip-github-release: true + persist-credentials: false + + - name: Install pinned Node.js + env: + NODE_VERSION: 22.23.4 + NODE_SHA256: 1805bcd35e6815559bd2eb97db438d3fdb0eb3a5e6a977b7c1badca76337e9ce + run: | + set -euo pipefail + archive="$RUNNER_TEMP/node-v${NODE_VERSION}-linux-x64.tar.xz" + install_dir="$RUNNER_TEMP/node-v${NODE_VERSION}-linux-x64" + curl --fail --location --silent --show-error \ + "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \ + --output "$archive" + printf '%s %s\n' "$NODE_SHA256" "$archive" | sha256sum --check --strict + mkdir -p "$install_dir" + tar -xJf "$archive" --strip-components=1 --directory "$install_dir" + "$install_dir/bin/node" --version | grep --fixed-strings --line-regexp "v${NODE_VERSION}" + printf '%s\n' "$install_dir/bin" >> "$GITHUB_PATH" + + - name: Install the frozen Release Please CLI + run: npm ci --ignore-scripts --no-audit --no-fund --prefix .github/release-please + + - name: Wait for the proposed version to be published + id: published + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python3 - <<'PY' + import json + import os + import re + import sys + import urllib.error + import urllib.request + from pathlib import Path + + version = Path("VERSION").read_text(encoding="utf-8").strip() + repository = os.environ["GITHUB_REPOSITORY"] + if not re.fullmatch(r"\d+\.\d+\.\d+(?:-(?:alpha|beta|rc)\.\d+)?", version): + raise RuntimeError(f"Invalid VERSION value: {version!r}") + if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository): + raise RuntimeError("Invalid GITHUB_REPOSITORY value") + + url = f"https://api.github.com/repos/{repository}/releases/tags/v{version}" + request = urllib.request.Request( + url, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {os.environ['GH_TOKEN']}", + "X-GitHub-Api-Version": "2022-11-28", + }, + ) + try: + with urllib.request.urlopen(request, timeout=20) as response: + release = json.load(response) + except urllib.error.HTTPError as error: + if error.code != 404: + raise + release = None + + if release is None: + print(f"::notice title=Release draft deferred::Published release v{version} is not available yet.") + ready = "false" + else: + if not isinstance(release, dict) or release.get("tag_name") != f"v{version}": + raise RuntimeError("GitHub returned a release record for an unexpected tag") + draft = release.get("draft") + published_at = release.get("published_at") + if not isinstance(draft, bool): + raise RuntimeError("GitHub release record is missing its draft status") + if draft or not isinstance(published_at, str) or not published_at: + print(f"::notice title=Release draft deferred::Release v{version} is not published yet.") + ready = "false" + else: + ready = "true" + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write(f"ready={ready}\n") + PY + + - name: Create or update the draft release pull request + if: steps.published.outputs.ready == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + printf '%s' "$GH_TOKEN" | node .github/release-please/node_modules/release-please/build/src/bin/release-please.js release-pr \ + --repo-url "https://github.com/${GITHUB_REPOSITORY}" \ + --target-branch main \ + --config-file release-please-config.json \ + --manifest-file .release-please-manifest.json \ + --token /dev/stdin diff --git a/.gitignore b/.gitignore index a4e7f96..8ff5ea9 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ doc_update_brief.json .doc_update_complete MagicMock/ bbackup.egg-info/ +.github/release-please/node_modules/ # Secrets and keys (never commit these) .env diff --git a/CHANGELOG.md b/CHANGELOG.md index cee80ca..84260b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,10 @@ current uv-based install, development, and release instructions. ### Added -- A Release Please draft-PR configuration and local preparation command. - Activation is blocked pending a patched upstream Action; signed tags and - verified artifact publication retain their existing deliberate workflow. +- Release Please draft PRs using the official CLI with a reviewed, integrity- + pinned dependency graph and patched Node runtime, plus a local preparation + command. Signed tags and verified artifact publication retain their existing + deliberate workflow. - Reviewed repository lock inspection and stale-lock removal through the native production dispatcher. Unlock requires an exact review token, explicit approval, a confirmed quiet window, and only own-host, same-user locks whose diff --git a/INSTALL.md b/INSTALL.md index 2628352..90c788c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,6 +1,6 @@ # Install bbackup -This guide covers stable v2.0.5 for its tested file, database, dashboard, and AMD64 package scope. New cloud and Linux recovery workflows are experimental and unqualified; B2/AWS, native Docker, ARM64 package functionality, and broader recovery guarantees remain outside the qualified scope. The retained 1.x tag remains available as v1.8.6. +The latest published stable release is v2.0.5, with its tested file, database, dashboard, and AMD64 package scope. The current v2.0.6 source candidate includes reviewed issue #30 maintenance and compact-terminal fixes, but its release downloads are not published yet; the install commands and package examples below therefore remain pinned to v2.0.5. The candidate adds no B2/AWS, ARM64, or Linux recovery qualification. The retained 1.x tag remains available as v1.8.6. ## Version 2 stable release @@ -26,7 +26,7 @@ Version 2.0.5 supports explicit user-level installation through `service install ### Cloud repositories -Restic repositories can use local storage, Backblaze B2 through the S3 API, or Amazon S3. Cloud providers are configurable but not production-qualified in v2.0.5. Configure credentials in the supervised process environment. Do not place credentials in portable JSON policy or a repository URL. See [cloud storage](docs/cloud-storage.md) for experimental provider setup and limits. +Restic repositories can use local storage, Backblaze B2 through the S3 API, or Amazon S3. Cloud providers are configurable but not production-qualified; the v2.0.6 source candidate does not change that status. Configure credentials in the supervised process environment. Do not place credentials in portable JSON policy or a repository URL. See [cloud storage](docs/cloud-storage.md) for experimental provider setup and limits. ## Existing 1.x release @@ -52,7 +52,7 @@ See [docs/management.md](docs/management.md) for legacy setup, health, remote, a ## Development checkout -The checkout includes guided setup, schema-3 enrollment, and safety improvements. Follow its quick start and deliberately migrate existing schema-2 policy; migration preserves the original files. +The checkout contains the current v2.0.6 release-candidate source, including guided setup, schema-3 enrollment, issue #30 maintenance, and compact-terminal fixes. The candidate is not yet available as a published download. Follow its quick start and deliberately migrate existing schema-2 policy; migration preserves the original files. ```bash git clone https://github.com/CruxExperts/BestBackup.git diff --git a/MANIFEST.in b/MANIFEST.in index b141761..220cff6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,7 @@ # Portable source and maintenance inputs; runtime state never belongs in an sdist. include VERSION uv.lock .python-version config.yaml.example +include release-please-config.json .release-please-manifest.json +include .github/release-please/package.json .github/release-please/package-lock.json include bbackup.py bbman.py AGENTS.md CHANGELOG.md INSTALL.md QUICKSTART.md SECURITY.md SUPPORT.md include Dockerfile.test .dockerignore recursive-include scripts *.py *.sh *.md @@ -12,6 +14,7 @@ prune .agent prune .agents prune .codex prune .venv +prune .github/release-please/node_modules prune build prune dist global-exclude __pycache__ *.py[cod] .env *.pem *.key *.log diff --git a/QUICKSTART.md b/QUICKSTART.md index dcd9137..386b1ed 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -1,6 +1,6 @@ # Your first backup and verified restore -This guide covers v2.0.5 and its schema-3 policy. For an older v2.0.2 installation, use its [versioned quick start](https://github.com/CruxExperts/BestBackup/blob/v2.0.2/QUICKSTART.md) until you migrate. Keep an independently retrievable copy of each repository password. +This guide covers the published v2.0.5 setup and its schema-3 policy. The current v2.0.6 release-candidate source also includes the issue #30 and compact-terminal changes below, but those candidate downloads are not published yet. For an older v2.0.2 installation, use its [versioned quick start](https://github.com/CruxExperts/BestBackup/blob/v2.0.2/QUICKSTART.md) until you migrate. Keep an independently retrievable copy of each repository password. ## Start with the guided dashboard @@ -130,16 +130,17 @@ Persistent timers can catch up after an idle interruption. An interrupted mutati ## Review local retention and stale locks (unreleased) -The current source includes the following maintenance features for issue -[#30](https://github.com/CruxExperts/BestBackup/issues/30). They are not part of -the published v2.0.5 artifacts. Use an explicitly enrolled policy and bindings; -the examples below use the default configuration paths. - -The unreleased dashboard layout keeps forms and setup navigation reachable in -compact terminals. Use `Tab` and `Shift+Tab` to move between controls; the focused -control scrolls into view. Use `F1` for field help. The legacy live dashboard -summarizes resource categories and keeps failures and cancel hints visible when -there is not enough room for its full layout. +The current v2.0.6 candidate source includes the following maintenance features +for issue [#30](https://github.com/CruxExperts/BestBackup/issues/30). The +candidate is not yet a published download, so these commands require this source +checkout rather than the published v2.0.5 artifacts. Use an explicitly enrolled +policy and bindings; the examples below use the default configuration paths. + +The candidate dashboard layout keeps forms and setup navigation reachable in +compact terminals. Use `Tab` and `Shift+Tab` to move between controls; the +focused control scrolls into view. Use `F1` for field help. The legacy live +dashboard summarizes resource categories and keeps failures and cancel hints +visible when there is not enough room for its full layout. For a job's `retention` object, optional `local_last` and `local_weekly` counts default to zero (disabled). For example: diff --git a/docs/README.md b/docs/README.md index a1912c2..a271127 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,9 +2,10 @@ bbackup is for Linux administrators, homelab and self-hosted operators, and small infrastructure teams who need encrypted local backups, verified file restores, and visible scheduled jobs. Choose a guide by the work you are doing. -These guides describe published v2.0.5 and clearly marked unreleased source -changes. Check the [qualification record](development/version-2.md) for the exact -supported scope before selecting a workflow. +These guides describe published v2.0.5 and the v2.0.6 release-candidate source. +Candidate downloads are not published yet. Check the +[qualification record](development/version-2.md) for the exact supported scope +before selecting a workflow. ## Choose a starting point @@ -24,20 +25,26 @@ supported scope before selecting a workflow. ## Version and support boundaries -v2.0.5 retains the v2.0.4 tested core scope. Experimental cloud and Linux -workflows remain unqualified; issues #23–#26 were canceled and closed as not -planned. An implemented path or a closed issue does not establish support. -The [qualification record](development/version-2.md) retains the dated evidence. +Published v2.0.5 retains the v2.0.4 tested core scope. The v2.0.6 candidate +contains the maintenance updates below but adds no B2/AWS, ARM64, or Linux +recovery qualification. Experimental cloud and Linux workflows remain +unqualified; issues #23–#26 were canceled and closed as not planned. An +implemented path or a closed issue does not establish support. The +[qualification record](development/version-2.md) retains the dated evidence. -Current source includes [unreleased issue #30 maintenance](development/version-2.md#unreleased-maintenance-issue-30) +The v2.0.6 candidate source includes [issue #30 maintenance](development/version-2.md#unreleased-maintenance-issue-30) for reviewed stale locks, local retention counts, strict Restic parsing, B2 -lifecycle-read compatibility and nested cache policy. Follow the +lifecycle-read compatibility and nested cache policy. The original section +anchor is retained. Follow the [maintenance quick start](../QUICKSTART.md#review-local-retention-and-stale-locks-unreleased) -before using those commands; they are separate from published v2.0.5. - -The [current layout and dependency batch](development/version-2.md#unreleased-layout-and-dependency-maintenance) -is also unreleased. It covers compact terminal forms, documentation navigation, -and reviewed Python and GitHub Actions maintenance. +before using those commands from the candidate source; they are not in the +published v2.0.5 downloads yet. + +The [layout and dependency batch](development/version-2.md#unreleased-layout-and-dependency-maintenance) +is also included in the v2.0.6 candidate, with its existing section anchor +retained. It covers compact terminal forms, documentation navigation, and +reviewed Python and GitHub Actions maintenance; candidate downloads are pending +publication. Operators still using v2.0.2 can follow its [versioned documentation](https://github.com/CruxExperts/BestBackup/tree/v2.0.2/docs) diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index 178cf0d..331df35 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -64,26 +64,24 @@ before push. ### Prepare the release pull request -**Activation is blocked as of 2026-10-23.** The SHA-pinned -`.github/workflows/release-please.yml` has an explicit false job guard. Its -triggers are defined for `main` pushes and manual dispatch, but the Action is -not executed. The audited v5.0.0 lock includes `handlebars@4.7.8`, affected by -[critical CVE-2026-33937](https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-2w6w-674q-4c4q) -and [critical CVE-2026-106445](https://github.com/advisories/GHSA-p8wg-vrv2-v86f). -The latter requires at least 4.7.10. Other transitive advisories are also -present. Exact-lock OSV and npm registry checks confirm the findings; the -upstream `main` lock remains affected. This is a dependency finding, not a -claim that this repository was exploited. - -Before activation, review a patched immutable upstream release, its lockfile -**and rebuilt bundled Action**, rerun advisory checks, replace the SHA, and -restore the job condition to permit only `refs/heads/main`. Then allow Actions -to create PRs in repository settings and verify the first draft. No repository -permission setting was changed during this blocked setup. To roll back the -setup, revert its task commit; no tag or published artifact needs removal. - -The configuration below describes the intended behavior after activation. -Its root manifest starts at `2.0.5`; it proposes +`.github/workflows/release-please.yml` uses the official Release Please CLI +`17.11.2` with the reviewed npm lockfile in `.github/release-please/`, rather +than the older prebuilt Action bundle. All 162 resolved package versions were +checked against OSV, GitHub advisories and npm's advisory feed on 2026-10-23; +no known advisories were returned. Registry artifact integrity values were +also verified. See the [dependency evidence](development/release-dependencies.json) +for the exact lock hash, sources and review limits. This is a dated check, +not a guarantee against future vulnerabilities. + +The workflow installs dependencies with `npm ci --ignore-scripts` on Node +`22.23.4`, downloaded from Node's official distribution with a pinned SHA256. +That patched runtime was published on 2026-10-21 at 10:38:20 UTC and was older +than 48 hours when selected. The workflow runs only on `main` pushes or a manual +dispatch on `main`. It defers version proposals while the current `VERSION` +has no published release, so an unfinished release does not create a second +candidate. Repository settings must allow Actions to create PRs. + +The root manifest records the candidate version; Release Please proposes the next version in `VERSION` and `.release-please-manifest.json` through a **draft PR**. It does not create tags, GitHub releases, or merge PRs. The existing signed-tag and signed-checksum publication flow remains authoritative. diff --git a/docs/development/release-dependencies.json b/docs/development/release-dependencies.json index 44af216..d9650bb 100644 --- a/docs/development/release-dependencies.json +++ b/docs/development/release-dependencies.json @@ -467,5 +467,76 @@ ], "freshness_and_verification": "Use the timestamped exact-version advisory, registry hash, lockfile and immutable action-reference evidence in this report; no latest-version or risk-free claim is made.", "rollback": "Revert the scoped maintenance commit with an OpenPGP-signed commit and rerun uv sync --locked. Preserve published branch history." + }, + "release_automation": { + "owner": [ + ".github/release-please/package.json", + ".github/release-please/package-lock.json", + ".github/workflows/release-please.yml" + ], + "consumer": "Release Please draft PR preparation only; signed tag and checksum publication remain separate", + "cli": { + "package": "release-please", + "version": "17.11.2", + "first_published_utc": "2026-08-24T21:48:19.403Z", + "source": "https://github.com/googleapis/release-please/releases/tag/v17.11.2" + }, + "runtime": { + "version": "22.23.4", + "first_published_utc": "2026-10-21T10:38:20Z", + "url": "https://nodejs.org/dist/v22.23.4/node-v22.23.4-linux-x64.tar.xz", + "sha256": "1805bcd35e6815559bd2eb97db438d3fdb0eb3a5e6a977b7c1badca76337e9ce", + "security_source": "https://nodejs.org/en/blog/vulnerability/october-2026-security-releases" + }, + "audit": { + "checked_at_utc": "2026-10-23T20:49:28.780101+00:00", + "lockfile": ".github/release-please/package-lock.json", + "lockfile_sha256": "5133b7774d18b013bc81c70570f874ea4983557080be41b9dd9c9084de8428a6", + "resolved_packages": 162, + "osv_findings": 0, + "github_reviewed_and_malware_findings": 0, + "npm_registry_findings": 0, + "registry_integrities_verified": true, + "source_classes": [ + "A: OSV", + "B: GitHub Advisory Database", + "E: npm registry/deps.dev" + ], + "runtime_verification": "Official Node security release notice identifies 22.23.4 as the patched 22.x release; the official archive SHA256 is verified before extraction and execution. The release timestamp is from https://github.com/nodejs/node/releases/tag/v22.23.4.", + "npm_node_wrapper_cross_checks": { + "package": "node", + "version": "22.23.4", + "osv_github_deps_dev_findings": 0, + "limits": "These supplemental npm-wrapper metadata checks do not independently qualify the official Node runtime binary; use its official security notice and archive verification." + }, + "cisa_kev": { + "catalogVersion": "2026.10.23", + "aliases": [ + "CVE-2026-105481", + "CVE-2026-105482", + "CVE-2026-105483", + "CVE-2026-106069", + "CVE-2026-106070", + "CVE-2026-106071", + "CVE-2026-106072", + "CVE-2026-106073", + "CVE-2026-106074", + "CVE-2026-106445", + "CVE-2026-111497", + "CVE-2026-19481", + "CVE-2026-33937" + ], + "matches": [] + }, + "sources": [ + "https://api.osv.dev/v1/querybatch", + "https://api.github.com/advisories", + "https://registry.npmjs.org/-/npm/v1/security/advisories/bulk", + "https://api.deps.dev/v3/systems/npm/packages/node/versions/22.23.4" + ], + "limits": "Exact-version, timestamped known-advisory and registry-integrity checks; not a risk-free claim or future-version approval." + }, + "selection_policy": "Pin the official CLI and all npm artifacts to a reviewed fresh lock; bootstrap the exact patched LTS Node archive by SHA256. Re-audit before any dependency/runtime update.", + "rollback": "Revert the signed automation change and disable its workflow before reverting repository Actions settings; preserve published releases and tags." } } diff --git a/docs/development/version-2.md b/docs/development/version-2.md index fd6304d..ae54841 100644 --- a/docs/development/version-2.md +++ b/docs/development/version-2.md @@ -2,7 +2,11 @@ > Stable v2.0.5 production qualification retains the tested core scope recorded > below. Experimental additions remain unqualified. The dated records retain -> historical evidence and limits without expanding that scope. +> historical evidence and limits without expanding that scope. The v2.0.6 +> release-candidate source now includes the maintenance batches below; its +> downloads are pending publication. Until publication, v2.0.5 remains the +> latest available release, and no new provider or Linux recovery qualification +> is claimed. ## Unreleased layout and dependency maintenance @@ -27,8 +31,9 @@ The retry regression covers both 429 and 503 responses, including the fallback when that header is absent. Inspection deadlines and cancellation still bound the SDK subprocess; this offline check adds no live provider qualification. -These are unreleased source changes. They do not publish new artifacts or -expand Docker, cloud-provider, off-host recovery or complete-machine support. +These changes are included in the v2.0.6 candidate source. The candidate is not +a published artifact and does not expand Docker, cloud-provider, off-host +recovery or complete-machine support. On 2026-10-23, the final local maintenance candidate passed 1,295 tests with one skipped test in 114.24 seconds using three workers. This includes the @@ -45,7 +50,7 @@ checks. These checks validate the source candidate without publishing a release. ## Unreleased maintenance (issue #30) -The source checkout implements the reviewed portions of +The v2.0.6 candidate source implements the reviewed portions of [#30](https://github.com/CruxExperts/BestBackup/issues/30): bounded repository lock inspection and explicitly approved stale unlock, optional `local_last` and `local_weekly` counts, strict parsing of valid Restic keep-all output, @@ -81,11 +86,12 @@ during bounded startup; the production inspector still rejects changing lock sets, and uncertain unlocks are never retried. This covers slower hosted lock acquisition without weakening the maintenance safety checks. -This is unreleased source work, not acceptance of private candidate builds or -permission to modify deployed installations. The package version remains -2.0.5 until a separate release. No B2/AWS protection, off-host custody, -thirty-day recovery window or complete-machine recovery qualification is added; -the canceled scopes of #23-#26 remain canceled and unqualified. +These changes are included in the v2.0.6 release-candidate source, not +acceptance of private candidate builds or permission to modify deployed +installations. Published downloads remain at v2.0.5 until release publication. +No B2/AWS protection, off-host custody, thirty-day recovery window or +complete-machine recovery qualification is added; the canceled scopes of #23-#26 +remain canceled and unqualified. ## v2.0.5 release scope and known limits @@ -1793,7 +1799,7 @@ recovery limits rather than treating discoverability as additional qualification ## Dependency evidence The entries below record earlier candidates and released 2.0.x inputs. They -remain historical evidence. For the current unreleased dependency graph, use +remain historical evidence. For the current v2.0.6 candidate dependency graph, use the [source maintenance record](#unreleased-layout-and-dependency-maintenance) and [exact lockfile review](release-dependencies.json). diff --git a/scripts/check_markdown_standards.py b/scripts/check_markdown_standards.py index ce0b92f..3425866 100644 --- a/scripts/check_markdown_standards.py +++ b/scripts/check_markdown_standards.py @@ -11,6 +11,7 @@ import hashlib import json +import os import re import sys from pathlib import Path @@ -116,7 +117,13 @@ def public_documents() -> list[Path]: for directory in PUBLIC_DIRECTORIES: root = REPO_ROOT / directory if root.exists(): - paths.extend(sorted(root.rglob("*.md"))) + for current, subdirectories, filenames in os.walk(root): + subdirectories[:] = [name for name in subdirectories if name != "node_modules"] + paths.extend( + Path(current) / filename + for filename in filenames + if filename.endswith(".md") + ) paths.extend(REPO_ROOT / relative for relative in GENERATED_DOCUMENTS) unique = {path.resolve(): path for path in paths if path.exists()} return [unique[key] for key in sorted(unique)] diff --git a/tests/test_markdown_standards.py b/tests/test_markdown_standards.py index 8ad423a..a3a7e65 100644 --- a/tests/test_markdown_standards.py +++ b/tests/test_markdown_standards.py @@ -50,3 +50,23 @@ def test_same_release_identifier_with_different_dates_is_rejected(monkeypatch) - "heading anchor is duplicated in the same changelog version: added" in error for error in errors ), errors + + +def test_public_documents_skips_vendored_markdown_but_scans_authored_docs( + tmp_path: Path, monkeypatch +) -> None: + monkeypatch.setattr(check_markdown_standards, "REPO_ROOT", tmp_path) + authored = tmp_path / "docs" / "operator-guide.md" + authored.parent.mkdir(parents=True) + authored.write_text("## Missing an H1\n", encoding="utf-8") + vendored = tmp_path / ".github" / "release-please" / "node_modules" / "pkg" / "README.md" + vendored.parent.mkdir(parents=True) + vendored.write_text("\n", encoding="utf-8") + assert check_markdown_standards.scan_file(vendored) + + documents = check_markdown_standards.public_documents() + + assert authored in documents + assert vendored not in documents + authored_errors = check_markdown_standards.scan_file(authored) + assert any("exactly one visible H1" in error for error in authored_errors)