diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d56bb7fe9..9e2402257 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,12 +123,13 @@ jobs: with: version: ">=0.9.0" - uses: extractions/setup-just@v3 + - uses: quarto-dev/quarto-actions/setup@v2 - name: build docs run: just docs - uses: actions/upload-artifact@v5 with: name: docs - path: site/ + path: great-docs/_site/ - uses: aws-actions/configure-aws-credentials@v4 id: creds with: diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml index c85ffeb3e..9173f72c3 100644 --- a/.github/workflows/preview-docs.yml +++ b/.github/workflows/preview-docs.yml @@ -24,6 +24,8 @@ jobs: - uses: extractions/setup-just@v3 + - uses: quarto-dev/quarto-actions/setup@v2 + - name: Install and Build if: github.event.action != 'closed' # skip the build if the PR has been closed run: just docs @@ -31,4 +33,4 @@ jobs: - name: Deploy preview uses: rossjrw/pr-preview-action@v1 with: - source-dir: ./site/ + source-dir: ./great-docs/_site/ diff --git a/.gitignore b/.gitignore index 4caf6f9b3..07e9a3162 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ vetiver-testing/rsconnect_api_keys.json # license files should not be commited to this repository *.lic /site/ + +# great-docs managed build output and docs venv +/great-docs/ +.venv-docs/ diff --git a/CLAUDE.md b/CLAUDE.md index eba3f1ad6..b684a9aaf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,6 +54,14 @@ just docs just docs-serve ``` +Docs are built with [great-docs](https://github.com/posit-dev/great-docs) (Quarto-based), not +mkdocs. Requires the **Quarto CLI** to be installed. `just docs` provisions an isolated +`.venv-docs` (Python 3.12) with `uv venv`/`uv pip install`, then runs `great-docs build` inside +that venv — it does **not** use `uv run --with`, because Quarto's post-render hook must execute +under the same interpreter that has `great_docs`/`pygments` importable. Output is written to +`great-docs/_site/` (git-ignored, along with `.venv-docs/`). Narrative pages live in +`user_guide/*.qmd`; CLI reference pages are auto-generated from `rsconnect.main`. + ### Building Distribution ```bash # Build wheel distribution @@ -184,7 +192,14 @@ The app mode determines how Connect runs the content. Manifests must specify the - Version is a static field in `pyproject.toml`, managed with `uv version`. - `main` carries a `.dev` version (e.g. `1.29.1.dev0`). -- Update CHANGELOG.md before each release (even betas). +- Release notes are now authored directly in the GitHub Release for each tag — that's the source + of truth for the published changelog (great-docs renders it into the docs changelog page). + `docs/CHANGELOG.md` only retains the `Unreleased` section for in-flight work; update it before + cutting a release, then clear it back to an empty `Unreleased` section afterward. +- `scripts/backfill_release_notes.py` is the one-time migration that ported historical + `docs/CHANGELOG.md` entries into existing GitHub Releases. Run it dry-run first + (`uv run scripts/backfill_release_notes.py`) to review the plan, then `--apply` to write it. + It should not need to run again once history is migrated. - Cut a release: `uv version --bump stable`, commit, `git tag -a 1.2.3 -m 'Release 1.2.3'`, push the tag. - The `distributions` CI job asserts the tag matches the `pyproject.toml` version, then builds and publishes to PyPI. - After releasing, bump `main` back to the next `.dev` version with `uv version .dev0`. diff --git a/Justfile b/Justfile index ce2cdb453..ffbf4a9f8 100644 --- a/Justfile +++ b/Justfile @@ -42,17 +42,27 @@ install: version: uv version --short -# Build the documentation site +# Build the documentation site (great-docs / Quarto). Requires the Quarto CLI. docs: - VERSION=$(uv version --short) uv run --group docs mkdocs build + #!/usr/bin/env bash + set -euo pipefail + uv venv --python 3.12 --allow-existing .venv-docs + uv pip install --python .venv-docs --quiet great-docs pygments . + source .venv-docs/bin/activate + great-docs build # Serve the documentation with live reload docs-serve: - VERSION=$(uv version --short) uv run --group docs mkdocs serve + #!/usr/bin/env bash + set -euo pipefail + uv venv --python 3.12 --allow-existing .venv-docs + uv pip install --python .venv-docs --quiet great-docs pygments . + source .venv-docs/bin/activate + great-docs preview # Remove build/test artifacts clean: - rm -rf .coverage .pytest_cache build dist htmlcov rsconnect_python.egg-info rsconnect.egg-info site + rm -rf .coverage .pytest_cache build dist htmlcov rsconnect_python.egg-info rsconnect.egg-info great-docs # Remove local rsconnect store directories clean-stores: @@ -74,8 +84,8 @@ dev-stop: # Sync latest docs to S3 (CI) sync-latest-docs-to-s3: - aws s3 sync --acl bucket-owner-full-control --cache-control max-age=0 site/ s3://rstudio-connect-downloads/connect/rsconnect-python/latest/docs/ + aws s3 sync --acl bucket-owner-full-control --cache-control max-age=0 great-docs/_site/ s3://rstudio-connect-downloads/connect/rsconnect-python/latest/docs/ # Promote docs in S3 (CI) promote-docs-in-s3: - aws s3 sync --delete --acl bucket-owner-full-control --cache-control max-age=300 site/ s3://docs.rstudio.com/rsconnect-python/ + aws s3 sync --delete --acl bucket-owner-full-control --cache-control max-age=300 great-docs/_site/ s3://docs.rstudio.com/rsconnect-python/ diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 000000000..3543b4894 Binary files /dev/null and b/assets/favicon.ico differ diff --git a/assets/iconPositConnect.svg b/assets/iconPositConnect.svg new file mode 100644 index 000000000..6aea8f5df --- /dev/null +++ b/assets/iconPositConnect.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 1320f90e5..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -site diff --git a/docs/commands/add.md b/docs/commands/add.md deleted file mode 100644 index 0cb4ef986..000000000 --- a/docs/commands/add.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: add diff --git a/docs/commands/bootstrap.md b/docs/commands/bootstrap.md deleted file mode 100644 index 49a5ecf14..000000000 --- a/docs/commands/bootstrap.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: bootstrap diff --git a/docs/commands/content.md b/docs/commands/content.md deleted file mode 100644 index d281ca11c..000000000 --- a/docs/commands/content.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: content diff --git a/docs/commands/deploy.md b/docs/commands/deploy.md deleted file mode 100644 index 9d5b05e5c..000000000 --- a/docs/commands/deploy.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: deploy diff --git a/docs/commands/details.md b/docs/commands/details.md deleted file mode 100644 index 737d8ba2c..000000000 --- a/docs/commands/details.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: details diff --git a/docs/commands/environment.md b/docs/commands/environment.md deleted file mode 100644 index 34f78e98f..000000000 --- a/docs/commands/environment.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: environment diff --git a/docs/commands/info.md b/docs/commands/info.md deleted file mode 100644 index 12208f0c8..000000000 --- a/docs/commands/info.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: info diff --git a/docs/commands/integration.md b/docs/commands/integration.md deleted file mode 100644 index 48eb13e7b..000000000 --- a/docs/commands/integration.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: integration diff --git a/docs/commands/list.md b/docs/commands/list.md deleted file mode 100644 index fe6a4ff2e..000000000 --- a/docs/commands/list.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: list_servers diff --git a/docs/commands/login.md b/docs/commands/login.md deleted file mode 100644 index 6fe26f293..000000000 --- a/docs/commands/login.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: login diff --git a/docs/commands/logout.md b/docs/commands/logout.md deleted file mode 100644 index df0e72a6d..000000000 --- a/docs/commands/logout.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: logout diff --git a/docs/commands/quickstart.md b/docs/commands/quickstart.md deleted file mode 100644 index 5e1671b4d..000000000 --- a/docs/commands/quickstart.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: quickstart diff --git a/docs/commands/remove.md b/docs/commands/remove.md deleted file mode 100644 index 06b5a2340..000000000 --- a/docs/commands/remove.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: remove diff --git a/docs/commands/system.md b/docs/commands/system.md deleted file mode 100644 index 01630f929..000000000 --- a/docs/commands/system.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: system diff --git a/docs/commands/version.md b/docs/commands/version.md deleted file mode 100644 index f0c545ab4..000000000 --- a/docs/commands/version.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: version diff --git a/docs/commands/write-manifest.md b/docs/commands/write-manifest.md deleted file mode 100644 index 7b87cca61..000000000 --- a/docs/commands/write-manifest.md +++ /dev/null @@ -1,3 +0,0 @@ -::: mkdocs-click - :module: rsconnect.main - :command: write_manifest diff --git a/docs/css/custom.css b/docs/css/custom.css deleted file mode 100644 index 3a65c7592..000000000 --- a/docs/css/custom.css +++ /dev/null @@ -1,347 +0,0 @@ -/* Header -*/ - -.md-header[data-md-state=shadow] { - transition: background-color 0s; - box-shadow: none; -} - -.md-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.15) !important; -} - -@media only screen and (max-width: 76.1875em){ - .left-nav { - display: none; - } -} - -/* Tabs -*/ - -.md-tabs__list { - margin: 0 2em 0 .2rem; - list-style: none; - white-space: nowrap; -} - -.md-tabs__link { - margin-top: .2rem !important; -} - -.md-tabs__item { - height: unset; -} - -/* Search bar -*/ - -.md-search-result__more summary { - color: #447099 !important; -} - -.md-search-result mark { - color: #000000 !important; - font-weight: bold; -} - -/* Search bar - black palette -*/ - -[data-md-color-primary=black] .md-search-result mark { - color: #fff !important; - font-weight: bold; -} - - -/* Global -*/ - -.md-tabs__list { - margin: 0 2em 0 .2rem; - list-style: none; - white-space: nowrap; -} - -.md-tabs__link { - margin-top: .2rem !important; - padding: 0; -} - -.md-tabs__link.logo { - background-image: url(../images/positLogoBlack.svg); - background-size: 80px auto; - background-repeat: no-repeat; - background-position: bottom; - height: 1.75rem; - width: 80px; -} - -[data-md-color-primary=black] .md-tabs__link.logo { - background-image: url(../images/positLogoWhite.svg); -} - -.md-typeset dl dt { - font-style: italic; - font-weight: bold; -} - -.md-typeset dl dd { - margin-top: 0; -} - -.md-typeset dd p:first-child { - margin-top: 0; -} - -[data-md-color-primary=white] .md-typeset a { - color: #447099; -} - -[data-md-color-primary=white] .md-typeset a:hover { - text-decoration: underline; - color: #447099; -} - -[data-md-color-primary=black] .md-typeset a { - color: #447099; - font-weight: bold; -} - -[data-md-color-primary=black] .md-typeset a:hover { - text-decoration: underline; - color: #447099; -} - -.admonition-title { - font-size: .7rem; -} - -.admonition p { - font-size: .7rem; -} - -.md-typeset .superfences-tabs>label:hover { - color: #447099 !important; -} - -/* Customized footer -*/ - -/* footer - white palette -*/ -.md-footer { - background-color: #fff !important; - color: #000 !important; -} - -.md-footer__direction { - color: #000; - font-size: .65rem; -} - -.md-footer-meta { - background-color: #fdfdfd; - border-top: 1px solid rgba(0, 0, 0, 0.15); -} - -.md-footer-copyright__highlight { - color: #6F6B6B; - font-weight: 400; - font-size: 0.65rem; - clear: right; -} - -.md-footer-meta.md-typeset a { - color: #6F6B6B !important; - font-weight: 400; -} - -.md-footer-meta img { - margin-top: 4px; - height: 1em; -} - -/* footer - black palette -*/ - -[data-md-color-primary=black] .md-footer { - background-color: #000 !important; - color: #fff !important; -} - -[data-md-color-primary=black] .md-footer__direction { - color: #fff; - font-size: .65rem; -} - -[data-md-color-primary=black] .md-footer-meta { - background-color: #000; - border-top: 1px solid #fff; - font-weight: 400; -} - -[data-md-color-primary=black] .md-footer-copyright__highlight { - color: #fff; - font-weight: 400; -} - -[data-md-color-primary=black] .md-footer-meta.md-typeset a { - color: #fff !important; - font-weight: 400; -} - -.md-typeset .tabbed-set>label { - font-size: .64rem !important; -} - -.md-typeset .tabbed-set>label:hover { - color: #447099 !important; -} - -/* Sidebar -*/ - -.md-nav__link:focus, .md-nav__link:hover, .md-nav__link:active, .md-nav__link--active, .md-nav__link:active { - color: #447099 !important; -} - -.md-nav__link--active { - font-weight: bold; -} - -[data-md-color-primary=black] .md-nav { - color: #fff; -} - -[data-md-color-primary=white] .md-nav__link--active, [data-md-color-primary=white] .md-nav__link:active { - color: #447099; -} - -.md-nav__item hr { - display: block; - height: 1px; - border: 0; - border-top: 1px solid #ccc; - margin: 1em 0; - padding: 0; -} - - -h2.divider { - text-align: center; - max-width: 650px; -} - -/* Code blocks -*/ - -p.code-title { - background-color: rgba(219, 219, 219, 0.8); - margin: 1em 0 -1.2em; - padding: 0.1em 1em; - font-size: 0.9em; - font-family: "Roboto Mono","Courier New",Courier,monospace; -} - -.code-title { - background-color: rgba(219, 219, 219, 0.8); - border-bottom: 0.05rem solid var(--md-default-fg-color--lightest); - border-top-left-radius: 0.1rem; - border-top-right-radius: 0.1rem; - display: block; - font-size: 0.85em; - font-weight: 400; - font-family: 'Roboto Mono'; - margin-top: 1em; - padding: 0.1em 1em; - position: relative; -} - -[data-md-color-primary=black] .code-title { - background-color: black !important; -} - -pre .code-noselect { - color: #aaa; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - /* padding-right: 6px; */ -} - -pre code::-webkit-scrollbar-thumb:hover, .codehilite pre::-webkit-scrollbar-thumb:hover { - background-color: #447099 !important; -} - -/* Permalinks -*/ - -.md-typeset .headerlink:focus, .md-typeset:hover .headerlink, .md-typeset:target .headerlink { - color: #ccc !important; -} - -.md-typeset .headerlink:focus, .md-typeset .headerlink:hover, .md-typeset :target>.headerlink { - color: #447099; -} - - /* Back-to-top button - */ - - .md-top { - color: #fff; - background: #447099 !important; - border-radius: 100%; - outline: none; - box-shadow: - 0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1), - 0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.1); -} - // Back-to-top button in hidden state - &[data-md-state="hidden"] { - transform: translateY(px2rem(-4px)); - opacity: 0; - pointer-events: none; - } - - // Back-to-top button on focus/hover - &:focus, - &:hover { - background: var(--md-accent-fg-color); - transform: scale(1.1); - } - -/* Admonition and details */ -.md-typeset .admonition, -.md-typeset details { - border: none; - transition: box-shadow 400ms; - box-shadow: - 0 0.2rem 0.5rem rgba(0,0,0,.05), - 0 0.025rem 0.05rem rgba(0,0,0,.15) -} - -/* Admonition and details on hover */ -.md-typeset .admonition:hover, -.md-typeset details:hover { - box-shadow: - 0 0.4rem 1.0rem rgba(0,0,0,.10), - 0 0.025rem 0.05rem rgba(0,0,0,.15) -} - -/* Details in closed state */ -.md-typeset details:not([open]) { - transition-duration: 0s; - box-shadow: none; -} - -/* Details title in closed state */ -.md-typeset details:not([open]) > summary { - border-radius: 1rem; -} - -/* Admonition title */ -.md-typeset .admonition-title { - padding-right: 1rem; -} diff --git a/docs/overrides/partials/footer.html b/docs/overrides/partials/footer.html deleted file mode 100644 index cf14f38ae..000000000 --- a/docs/overrides/partials/footer.html +++ /dev/null @@ -1,85 +0,0 @@ -{#- - Make footer navigation stops at separators - Remove mkdocs links --#} -{% import "partials/language.html" as lang with context %} - - - diff --git a/docs/overrides/partials/header.html b/docs/overrides/partials/header.html deleted file mode 100644 index 90bd21de9..000000000 --- a/docs/overrides/partials/header.html +++ /dev/null @@ -1,99 +0,0 @@ -{% set site_url = config.site_url | d(nav.homepage.url, true) | url %} -{% if not config.use_directory_urls and site_url[0] == site_url[-1] == "." %} - {% set site_url = site_url ~ "/index.html" %} -{% endif %} -
- -
diff --git a/docs/overrides/partials/integrations/analytics.html b/docs/overrides/partials/integrations/analytics.html deleted file mode 100644 index 129122c5d..000000000 --- a/docs/overrides/partials/integrations/analytics.html +++ /dev/null @@ -1,41 +0,0 @@ -{% set property = config.google_analytics %} - - diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 8d5e6d933..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -Pygments -markdown -markdown-include -mkdocs -mkdocs-exclude -mkdocs-macros-plugin -mkdocs-material -pymdown-extensions -mkdocs-click diff --git a/docs/superpowers/plans/2026-07-01-great-docs-migration.md b/docs/superpowers/plans/2026-07-01-great-docs-migration.md new file mode 100644 index 000000000..0374e6353 --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-great-docs-migration.md @@ -0,0 +1,782 @@ +# great-docs Migration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the mkdocs/Material documentation toolchain with great-docs (Quarto-based) while preserving the `docs.posit.co/rsconnect-python` S3 site, CLI-reference + narrative + changelog scope, and PR previews. + +**Architecture:** great-docs reads `great-docs.yml`, auto-discovers the Click CLI from `rsconnect.main`, auto-generates a changelog from GitHub Releases, and builds a static site to `great-docs/_site/`. CI keeps syncing that directory to the existing S3 buckets. A one-time script backfills the empty GitHub Release bodies from the existing `CHANGELOG.md` so the auto-changelog has full history. Narrative pages become `.qmd`. + +**Tech Stack:** great-docs, Quarto CLI, uv, GitHub Actions, AWS S3, `gh` CLI, pytest. + +## Global Constraints + +- Hosting stays on **S3** (`s3://rstudio-connect-downloads/connect/rsconnect-python/latest/docs/` and `s3://docs.rstudio.com/rsconnect-python/`); great-docs replaces only the build step. +- Site scope: CLI reference + narrative guides + changelog. **No Python API reference.** +- **Single-version** site (one "latest"), matching current promotion-on-tag behavior. +- great-docs output directory is `great-docs/_site/` (all S3/preview paths point here, not `site/`). +- great-docs requires **Python ≥3.11 and the Quarto CLI**; the package itself still supports Python ≥3.8. Run great-docs via `uv run --python 3.12 --with great-docs ...` so its Python floor never conflicts with the project's `requires-python = ">=3.8"`. +- Repository URL for the auto-changelog comes from `pyproject.toml` `[project.urls] Repository`. +- Git tags have **no `v` prefix** (e.g. `1.29.0`). CHANGELOG version headers are `## [X.Y.Z] - YYYY-MM-DD`. + +--- + +## File Structure + +- `docs/superpowers/spikes/2026-07-01-great-docs-findings.md` — spike outcomes (Task 1). +- `scripts/backfill_release_notes.py` — CHANGELOG → GitHub Releases backfill (Tasks 2–3). +- `tests/test_backfill_release_notes.py` — parser unit tests (Task 2). +- `great-docs.yml` — great-docs config (Task 4). +- `great-docs/user_guide/*.qmd` — migrated narrative pages (Task 5). Exact directory confirmed by Task 1. +- `great-docs/_variables.yml` (generated at build) — version substitution (Tasks 5, 7). +- `pyproject.toml` — drop the `docs` dependency group (Task 4). +- `justfile` — `docs`/`docs-serve`/`clean` and S3 recipes retargeted (Task 7). +- `.github/workflows/main.yml`, `.github/workflows/preview-docs.yml` — Quarto install + path changes (Task 7). +- Removed in Task 8: `mkdocs.yml`, `docs/commands/`, `docs/overrides/`, `docs/css/`, `docs/requirements.txt`, `docs/index.md`, `docs/deploying.md`, `docs/programmatic-provisioning.md`, `docs/server-administration.md`, `docs/.gitignore`. + +--- + +## Task 1: Spike — validate great-docs feasibility + +Exploratory task. Produces a findings note that greenlights Tasks 4–8 or records required deviations. Work on a throwaway branch; nothing here needs to merge except the findings note. + +**Files:** +- Create: `docs/superpowers/spikes/2026-07-01-great-docs-findings.md` + +**Interfaces:** +- Produces: confirmed values for downstream tasks — (a) how to disable the API reference in `great-docs.yml`; (b) the source-content directory great-docs expects (assumed `great-docs/user_guide/`); (c) the working version-substitution mechanism (assumed Quarto `_variables.yml` + `{{< var rsconnect_python.version >}}`); (d) whether GTM can be injected; (e) that `great-docs build` emits to `great-docs/_site/`. + +- [ ] **Step 1: Scaffold a throwaway great-docs project in a scratch dir** + +```bash +git checkout -b spike/great-docs +uv run --python 3.12 --with great-docs great-docs config +``` +Expected: a starter `great-docs.yml` is written listing all documented options. Read it to locate the keys for CLI, changelog, reference, user-guide dir, and analytics. + +- [ ] **Step 2: Probe CLI-only build with the API reference disabled** + +Edit `great-docs.yml` to enable the CLI, enable the changelog, and disable/omit the API reference. Then: +```bash +uv run --python 3.12 --with great-docs great-docs build +ls great-docs/_site/ +``` +Record in the findings note: the exact YAML that disables the reference, whether the build succeeds with no reference, and whether the 16 `rsconnect` subcommands are auto-discovered from `rsconnect.main`. If the reference cannot be disabled, record the fallback (accept a minimal auto-reference) per the spec. + +- [ ] **Step 3: Probe version substitution** + +Create `great-docs/_variables.yml` with `rsconnect_python:\n version: "9.9.9"` and put `{{< var rsconnect_python.version >}}` in a test page under the user-guide dir. Build and grep the output HTML for `9.9.9`. Record whether Quarto var substitution works within the great-docs source layout; if not, record the fallback (build-time `sed` replacement in the `docs` recipe). + +- [ ] **Step 4: Probe GTM/analytics injection** + +Search the `great-docs config` output and great-docs docs for an analytics/head-include key. Attempt to inject `GTM-KHBDBW7`. Record whether it is supported, and if not, the fallback decision (Quarto-native `google-analytics` vs. a custom head include vs. dropping GTM). + +- [ ] **Step 5: Write the findings note and clean up** + +Write `docs/superpowers/spikes/2026-07-01-great-docs-findings.md` capturing the confirmed values (a)–(e) above with the exact YAML snippets. Then: +```bash +git checkout . # discard scratch config +git switch -c feat/great-docs-migration main +git add docs/superpowers/spikes/2026-07-01-great-docs-findings.md +git commit -m "docs: record great-docs spike findings" +``` +Expected: findings note committed on the migration branch; scratch artifacts discarded. + +--- + +## Task 2: Changelog backfill parser + +**Files:** +- Create: `scripts/backfill_release_notes.py` +- Test: `tests/test_backfill_release_notes.py` + +**Interfaces:** +- Produces: `parse_changelog(text: str) -> dict[str, str]` — maps each released version (e.g. `"1.29.0"`) to its release-notes body (markdown). Excludes the `Unreleased` section and the trailing link-reference definitions. Consumed by Task 3. + +- [ ] **Step 1: Write the failing test** + +```python +# tests/test_backfill_release_notes.py +from scripts.backfill_release_notes import parse_changelog + +SAMPLE = """# Changelog + +Some preamble. + +## Unreleased + +- unreleased thing + +## [1.29.0] - 2026-04-29 + +- Added `rsconnect deploy nodejs` command. + +### Added + +- `rsconnect content get-lockfile` command. + +## [1.28.2] - 2025-12-05 + +### Fixed + +- Corrected Changelog. + +[Unreleased]: https://github.com/posit-dev/rsconnect-python/compare/1.5.0...HEAD +[1.5.0]: https://github.com/posit-dev/rsconnect-python/releases/tag/1.5.0 +""" + + +def test_parse_changelog_extracts_versions_only(): + entries = parse_changelog(SAMPLE) + assert set(entries) == {"1.29.0", "1.28.2"} + + +def test_parse_changelog_excludes_unreleased_and_link_refs(): + entries = parse_changelog(SAMPLE) + assert "unreleased thing" not in "".join(entries.values()) + assert "compare/1.5.0" not in "".join(entries.values()) + + +def test_parse_changelog_keeps_body_and_subsections(): + entries = parse_changelog(SAMPLE) + assert "deploy nodejs" in entries["1.29.0"] + assert "### Added" in entries["1.29.0"] + assert "get-lockfile" in entries["1.29.0"] + assert entries["1.28.2"].startswith("### Fixed") + + +def test_parse_changelog_strips_surrounding_blank_lines(): + entries = parse_changelog(SAMPLE) + assert not entries["1.29.0"].startswith("\n") + assert not entries["1.29.0"].endswith("\n") +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `uv run --group test pytest tests/test_backfill_release_notes.py -v` +Expected: FAIL — `ModuleNotFoundError` / `parse_changelog` not defined. + +- [ ] **Step 3: Write minimal implementation** + +```python +# scripts/backfill_release_notes.py +"""Backfill empty GitHub Release bodies from docs/CHANGELOG.md. + +The GitHub Releases for this project have empty bodies; the real history +lives in docs/CHANGELOG.md (Keep a Changelog format). This one-time script +parses that file and populates each matching Release so great-docs can +generate its Changelog page from Releases. +""" +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +from typing import Dict + +VERSION_HEADER = re.compile( + r"^## \[(?P\d+\.\d+\.\d+(?:[a-z]+\d+)?)\](?: - \S+)?\s*$" +) +LINK_REF = re.compile(r"^\[[^\]]+\]:\s+https?://") + + +def parse_changelog(text: str) -> Dict[str, str]: + """Split a Keep-a-Changelog document into {version: body}. + + Skips the 'Unreleased' section and trailing link-reference + definitions. Body is the markdown between a version header and the + next '## ' header, with surrounding blank lines stripped. + """ + entries: Dict[str, str] = {} + current: str | None = None + buffer: list[str] = [] + + def flush() -> None: + if current is not None: + entries[current] = "\n".join(buffer).strip() + + for line in text.splitlines(): + if line.startswith("## "): + flush() + match = VERSION_HEADER.match(line) + current = match.group("version") if match else None + buffer = [] + continue + if LINK_REF.match(line): + continue + if current is not None: + buffer.append(line) + + flush() + return entries +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `uv run --group test pytest tests/test_backfill_release_notes.py -v` +Expected: PASS (all four tests). + +- [ ] **Step 5: Commit** + +```bash +git add scripts/backfill_release_notes.py tests/test_backfill_release_notes.py +git commit -m "feat: parse CHANGELOG.md into per-version release notes" +``` + +--- + +## Task 3: Changelog backfill execution + +**Files:** +- Modify: `scripts/backfill_release_notes.py` (add CLI entry point around `parse_changelog`) +- Test: `tests/test_backfill_release_notes.py` (add plan-building test) + +**Interfaces:** +- Consumes: `parse_changelog` from Task 2. +- Produces: `build_backfill_plan(entries, existing_tags, release_has_body) -> list[tuple[str, str]]` — the ordered `(tag, body)` edits to apply, limited to releases that exist and currently have an empty body. `main()` runs a dry-run by default and applies with `--apply`. + +- [ ] **Step 1: Write the failing test** + +```python +# add to tests/test_backfill_release_notes.py +from scripts.backfill_release_notes import build_backfill_plan + + +def test_build_backfill_plan_skips_missing_and_nonempty(): + entries = {"1.29.0": "body A", "1.28.2": "body B", "1.0.0": "old"} + existing_tags = {"1.29.0", "1.28.2"} # 1.0.0 has no release + release_has_body = {"1.29.0": False, "1.28.2": True} # 1.28.2 already has notes + plan = build_backfill_plan(entries, existing_tags, release_has_body) + assert plan == [("1.29.0", "body A")] +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `uv run --group test pytest tests/test_backfill_release_notes.py::test_build_backfill_plan_skips_missing_and_nonempty -v` +Expected: FAIL — `build_backfill_plan` not defined. + +- [ ] **Step 3: Write minimal implementation** + +```python +# add to scripts/backfill_release_notes.py + +def build_backfill_plan( + entries: Dict[str, str], + existing_tags: set[str], + release_has_body: Dict[str, bool], +) -> list[tuple[str, str]]: + """Return (tag, body) edits for releases that exist and are empty.""" + plan: list[tuple[str, str]] = [] + for version, body in entries.items(): + if version not in existing_tags: + continue + if release_has_body.get(version, False): + continue + plan.append((version, body)) + return plan + + +def _gh_releases() -> Dict[str, bool]: + """Map existing release tag -> whether its body is non-empty.""" + out = subprocess.run( + ["gh", "release", "list", "--limit", "500", "--json", "tagName,name"], + check=True, capture_output=True, text=True, + ).stdout + import json + result: Dict[str, bool] = {} + for rel in json.loads(out): + tag = rel["tagName"] + body = subprocess.run( + ["gh", "release", "view", tag, "--json", "body", "-q", ".body"], + check=True, capture_output=True, text=True, + ).stdout.strip() + result[tag] = bool(body) + return result + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("changelog", nargs="?", default="docs/CHANGELOG.md") + parser.add_argument("--apply", action="store_true", help="Actually edit releases") + args = parser.parse_args(argv) + + with open(args.changelog, encoding="utf-8") as handle: + entries = parse_changelog(handle.read()) + + body_by_tag = _gh_releases() + existing = set(body_by_tag) + plan = build_backfill_plan(entries, existing, body_by_tag) + + for tag, body in plan: + print(f"{'APPLY' if args.apply else 'DRY-RUN'}: {tag} ({len(body)} chars)") + if args.apply: + subprocess.run( + ["gh", "release", "edit", tag, "--notes-file", "-"], + input=body, text=True, check=True, + ) + print(f"{len(plan)} release(s) {'updated' if args.apply else 'would be updated'}.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `uv run --group test pytest tests/test_backfill_release_notes.py -v` +Expected: PASS (all tests). + +- [ ] **Step 5: Dry-run against the real repo and spot-check** + +Run: `uv run python scripts/backfill_release_notes.py` +Expected: prints `DRY-RUN: 1.29.0 (...)` etc. for the released versions present in CHANGELOG that currently have empty bodies; ends with `N release(s) would be updated.` Sanity-check N against the ~40 versioned CHANGELOG sections. + +- [ ] **Step 6: Apply the backfill and verify one release** + +Run: +```bash +uv run python scripts/backfill_release_notes.py --apply +gh release view 1.29.0 --json body -q '.body' +``` +Expected: the apply run reports the releases updated; `gh release view 1.29.0` now shows the migrated notes (matching the `## [1.29.0]` CHANGELOG section). + +- [ ] **Step 7: Commit** + +```bash +git add scripts/backfill_release_notes.py tests/test_backfill_release_notes.py +git commit -m "feat: backfill GitHub Release notes from CHANGELOG" +``` + +--- + +> **SPIKE-CORRECTED (Task 1 findings, authoritative).** These tasks were rewritten after the +> spike overturned several pre-spike assumptions. Confirmed facts used below: +> - CLI-only build works with `reference: false` (boolean; suppresses the Python API reference). +> NOTE: `reference: []` does NOT work — an empty list is falsy and falls through to full API +> auto-discovery. Use the boolean `false`. Config keys: `display_name`, `cli.enabled`/`cli.module`, +> `changelog.enabled`, top-level `include_in_header`. +> - `user_guide/` lives at the **project root** (not `great-docs/user_guide/`). +> - great-docs renders into a **managed `great-docs/` directory** (git-ignore it entirely) and +> outputs `great-docs/_site/`. `great-docs.yml` (the config file) stays at the repo root and IS tracked. +> - Build requires a dedicated venv (Python 3.12) with `great-docs` + `pygments` + the project +> installed, run **activated** (Quarto's post-render hook needs `pygments` on the active `python3`). +> The `uv run --with great-docs` ephemeral approach does NOT work. +> - GTM analytics injects via a **top-level `include_in_header`** key (inline `text:`), verified on +> all pages. It is NOT reachable under `site:`. +> - The inline `{{ rsconnect_python.version }}` line is **dropped** (great-docs auto-displays the +> package version); no `_variables.yml`, no post-build substitution. + +## Task 4: great-docs scaffolding & config + +**Files:** +- Create: `great-docs.yml` (repo root) +- Create/modify: `.gitignore` (ignore the managed `great-docs/` dir and the docs venv) +- Modify: `pyproject.toml` (remove the mkdocs `docs` dependency group) + +**Interfaces:** +- Produces: a `great-docs.yml` that builds a CLI + changelog site (no Python API reference) to + `great-docs/_site/`, consumed by Tasks 5–7. The build recipe (venv + activate) is defined in Task 7; + this task builds with the same recipe inline to validate config. + +- [ ] **Step 1: Write `great-docs.yml`** (repo root) + +```yaml +# great-docs.yml +display_name: rsconnect-python +cli: + enabled: true + module: rsconnect.main +reference: false # disables auto Python API reference (CLI reference still generated). NOT `[]` — an empty list is falsy and falls through to full auto-discovery. +changelog: + enabled: true + max_releases: 100 +``` + +- [ ] **Step 2: Remove the mkdocs `docs` dependency group** + +Delete the `docs = [...]` block from `[dependency-groups]` in `pyproject.toml`. Do NOT add great-docs +to a dependency group: it requires Python ≥3.11 while the project is `requires-python = ">=3.8"`, so a +group would break resolution. great-docs is installed ad-hoc into a docs venv by the Task 7 recipe. +Leave `[project.urls] Repository` intact — the auto-changelog reads it. + +Confirm the mkdocs block is gone: +```bash +grep -n "mkdocs" pyproject.toml +``` +Expected: no output. + +- [ ] **Step 3: Ignore the managed build dir and docs venv** + +Append to the repo-root `.gitignore` (create the entries if absent): +``` +/great-docs/ +.venv-docs/ +``` +(`great-docs/` is regenerated on every build — `_quarto.yml`, `index.qmd`, `scripts/`, `_site/`, +`_package_meta.json`, etc. `great-docs.yml` is a file at the root and is NOT covered by `/great-docs/`.) + +- [ ] **Step 4: Build to verify the config is valid** + +```bash +uv venv --python 3.12 .venv-docs +uv pip install --python .venv-docs --quiet great-docs pygments . +source .venv-docs/bin/activate +great-docs build +deactivate +``` +Expected: `[OK] Build complete`; `great-docs/_site/index.html` exists; CLI reference pages exist under +`great-docs/_site/reference/cli/`; a Changelog page renders (its history is populated only after the +Task 3 backfill is applied by the user — an empty/short changelog here is expected and NOT a failure). +Confirm no Python-module reference pages were generated: +```bash +find great-docs/_site -path '*reference*' -name '*.html' | grep -v '/cli/' || echo "no python API pages (correct)" +``` + +- [ ] **Step 5: Commit** + +```bash +git add great-docs.yml pyproject.toml .gitignore +git commit -m "build: add great-docs config, drop mkdocs deps" +``` + +--- + +## Task 5: Migrate narrative content to `.qmd` + +The four narrative pages are plain markdown; the only mkdocs-specific construct across all of them is +the single `{{ rsconnect_python.version }}` macro in `deploying.md`, which is **dropped** (great-docs +auto-displays the version). Migration is: move the pages to the root `user_guide/` dir as `.qmd`, +preserving prose verbatim, and remove that one macro line. + +**Files:** +- Create: `user_guide/index.qmd`, `user_guide/deploying.qmd`, `user_guide/programmatic-provisioning.qmd`, + `user_guide/server-administration.qmd` (from the matching `docs/*.md`) + +**Interfaces:** +- Consumes: `great-docs.yml` (Task 4). great-docs auto-discovers `user_guide/` at the repo root. +- Produces: rendered user-guide pages consumed by the build in Tasks 7–8. + +- [ ] **Step 1: Copy the four pages into the root `user_guide/` dir as `.qmd`** + +```bash +mkdir -p user_guide +for f in index deploying programmatic-provisioning server-administration; do + cp "docs/$f.md" "user_guide/$f.qmd" +done +``` +(The `docs/*.md` originals are removed later, in Task 8.) + +- [ ] **Step 2: Drop the version macro line in `deploying.qmd`** + +In `user_guide/deploying.qmd`, remove the line containing the mkdocs macro: +``` +Generated from rsconnect-python {{ rsconnect_python.version }} +``` +Delete the whole line (and an adjacent now-orphaned blank line if it leaves one). Do not replace it — +great-docs shows the package version automatically. Confirm no macro remains: +```bash +grep -rn "rsconnect_python.version\|{{" user_guide/ || echo "no macros remain (correct)" +``` + +- [ ] **Step 3: Add page titles/ordering if great-docs needs them** + +Build once (Step 4 recipe) and inspect the generated User Guide nav order. If pages are mis-ordered or +mis-titled, add YAML front matter (`---\ntitle: "Deploying Content"\n---`) to each `.qmd` matching the +current mkdocs nav labels: `Getting Started` (index), `Programmatic Provisioning`, `Deploying Content`, +`Server Administration`. If the auto order/titles are already correct, skip. + +- [ ] **Step 4: Build and verify all four pages render** + +```bash +source .venv-docs/bin/activate # venv from Task 4 +great-docs build +deactivate +for p in index deploying programmatic-provisioning server-administration; do + ls great-docs/_site/user-guide/$p.html 2>/dev/null || echo "MISSING $p" +done +grep -rn "{{ rsconnect_python.version }}" great-docs/_site/ && echo "MACRO LEAKED" || echo "no leaked macro (correct)" +``` +Expected: build succeeds; all four `user-guide/*.html` pages exist; no leaked macro token. + +- [ ] **Step 5: Commit** + +```bash +git add user_guide +git commit -m "docs: migrate narrative pages to great-docs qmd" +``` + +--- + +## Task 6: Branding & analytics + +**Files:** +- Create: `assets/` at repo root for brand files (logo, favicon, custom CSS) +- Modify: `great-docs.yml` (logo/favicon/CSS + `include_in_header` GTM + User Guide ordering + comment cleanup) + +**Interfaces:** +- Consumes: existing assets under `docs/images/` and `docs/css/custom.css`; `great-docs.yml` from Task 4; the `user_guide/*.qmd` pages from Task 5. +- Produces: a branded site with GTM injected into every page's `` and the User Guide sidebar in the intended order. + +**Carry-overs folded into this task (from Tasks 4 & 5):** +- Task 4 left the `reference: false` line with a comment referencing the ephemeral `task-4-report.md`. Make that comment self-contained (e.g. `# disable auto Python API reference; an empty list [] is falsy and would NOT disable it`). +- Task 5 left the User Guide sidebar mis-ordered and the index browser-tab title as "rsconnect". Fix both here (Steps 6-7 below). + +- [ ] **Step 1: Copy brand assets to a tracked `assets/` dir** + +```bash +mkdir -p assets +cp docs/images/iconPositConnect.svg docs/images/favicon.ico assets/ +cp docs/css/custom.css assets/custom.css +``` +(Use `assets/` at the repo root — NOT under `great-docs/`, which is git-ignored and regenerated.) + +- [ ] **Step 2: Wire logo/favicon/CSS into `great-docs.yml`** + +Add the branding keys (see the `great-docs config` starter output for exact key shapes). Baseline: +```yaml +logo: assets/iconPositConnect.svg +``` +Add favicon and custom CSS via the keys great-docs exposes for them (confirm against +`uv run --python 3.12 --with great-docs great-docs config` output; e.g. a `favicon:` key and a CSS/SCSS +include). Keep it minimal — only what maps to the current Material config (Posit logo, favicon, custom CSS). +If a key for custom CSS is not available, note it and defer; branding parity beyond the logo is +non-blocking. + +- [ ] **Step 3: Inject GTM via top-level `include_in_header`** + +Add to `great-docs.yml` (top level, NOT under `site:`), using the real Google Tag Manager snippet for +container `GTM-KHBDBW7`: +```yaml +include_in_header: + - text: | + + + +``` + +- [ ] **Step 4: Build and verify branding + analytics** + +```bash +source .venv-docs/bin/activate +great-docs build +deactivate +n=$(grep -rl "GTM-KHBDBW7" great-docs/_site/ | wc -l); echo "pages with GTM: $n" +ls great-docs/_site/**/iconPositConnect.svg great-docs/_site/*iconPositConnect* 2>/dev/null || echo "check logo path in output" +``` +Expected: build succeeds; GTM snippet present on all generated pages (n > 1); logo asset present in output. + +- [ ] **Step 5: Set the User Guide sidebar order and clean up the reference comment** + +The auto-discovered User Guide order does not match the old mkdocs nav. Add an explicit `user_guide:` +ordering to `great-docs.yml` (paths are relative to the `user_guide/` dir, no prefix) matching the +original nav sequence, and make the `reference:` comment self-contained: +```yaml +reference: false # disable auto Python API reference; an empty list [] is falsy and would NOT disable it +user_guide: + - index.qmd + - programmatic-provisioning.qmd + - deploying.qmd + - server-administration.qmd +``` +(If great-docs requires the `section`/`contents` form instead of a bare list, use that shape with a +single unnamed/"User Guide" section containing these four files in this order — confirm against +`great-docs config` output.) + +- [ ] **Step 6: Fix the index browser-tab title** + +Add `pagetitle: "Getting Started"` to the front matter of `user_guide/index.qmd` so the browser tab +title matches the nav label (Quarto otherwise derives `` from the page's first `#` heading). + +- [ ] **Step 7: Rebuild and verify ordering + tab title** + +```bash +source .venv-docs/bin/activate +great-docs build +deactivate +``` +Confirm the User Guide sidebar order in the output is: Getting Started, Programmatic Provisioning, +Deploying Content, Server Administration; and that `great-docs/_site/user-guide/index.html`'s +`<title>` contains "Getting Started". + +- [ ] **Step 8: Commit** + +```bash +git add great-docs.yml assets user_guide +git commit -m "docs: port Posit branding, GTM, and User Guide order to great-docs" +``` + +--- + +## Task 7: Build tooling & CI + +**Files:** +- Modify: `justfile` (`docs`, `docs-serve`, `clean`, `sync-latest-docs-to-s3`, `promote-docs-in-s3`) +- Modify: `.github/workflows/main.yml` (docs job) +- Modify: `.github/workflows/preview-docs.yml` + +**Interfaces:** +- Consumes: output dir `great-docs/_site/`; the venv build recipe validated in the spike. +- Produces: `just docs` builds via great-docs into `great-docs/_site/`; CI syncs that dir to the + existing S3 buckets and PR previews. + +- [ ] **Step 1: Replace the `docs` and `docs-serve` recipes** + +The build needs an activated venv, so these must be `bash` shebang recipes (multi-line shell state). +Replace the mkdocs recipes in `justfile`: +```make +# Build the documentation site (great-docs / Quarto). Requires the Quarto CLI. +docs: + #!/usr/bin/env bash + set -euo pipefail + uv venv --python 3.12 .venv-docs + uv pip install --python .venv-docs --quiet great-docs pygments . + source .venv-docs/bin/activate + great-docs build + +# Serve the documentation with live reload +docs-serve: + #!/usr/bin/env bash + set -euo pipefail + uv venv --python 3.12 .venv-docs + uv pip install --python .venv-docs --quiet great-docs pygments . + source .venv-docs/bin/activate + great-docs preview +``` +(`pygments` MUST be installed explicitly — the Quarto post-render hook needs it on the active `python3`. +`.` installs the project so great-docs can import `rsconnect.main` for CLI discovery.) + +- [ ] **Step 2: Retarget the S3 and clean recipes** + +Change `site/` → `great-docs/_site/` in both S3 recipes and update `clean`: +```make +clean: + rm -rf .coverage .pytest_cache build dist htmlcov rsconnect_python.egg-info rsconnect.egg-info great-docs + +sync-latest-docs-to-s3: + aws s3 sync --acl bucket-owner-full-control --cache-control max-age=0 great-docs/_site/ s3://rstudio-connect-downloads/connect/rsconnect-python/latest/docs/ + +promote-docs-in-s3: + aws s3 sync --delete --acl bucket-owner-full-control --cache-control max-age=300 great-docs/_site/ s3://docs.rstudio.com/rsconnect-python/ +``` + +- [ ] **Step 3: Verify the recipe end-to-end locally** + +```bash +just docs +ls great-docs/_site/index.html +``` +Expected: build succeeds; `great-docs/_site/index.html` exists. + +- [ ] **Step 4: Add Quarto to the `docs` CI job** + +In `.github/workflows/main.yml`, in the `docs` job, add the Quarto setup action after the `setup-just` +step and before `build docs`: +```yaml + - uses: quarto-dev/quarto-actions/setup@v2 +``` +The `run: just docs` step and the S3 sync/promote steps are unchanged — the recipe now builds via +great-docs and the recipes point at `great-docs/_site/`. + +- [ ] **Step 5: Update the PR preview workflow** + +In `.github/workflows/preview-docs.yml`: add the Quarto setup step before `Install and Build`, and +change `source-dir: ./site/` to `source-dir: ./great-docs/_site/`: +```yaml + - uses: quarto-dev/quarto-actions/setup@v2 +``` +```yaml + with: + source-dir: ./great-docs/_site/ +``` + +- [ ] **Step 6: Commit** + +```bash +git add justfile .github/workflows/main.yml .github/workflows/preview-docs.yml +git commit -m "build: run docs via great-docs in CI and just recipes" +``` + +--- + +## Task 8: Remove mkdocs and finalize + +**Files:** +- Delete: `mkdocs.yml`, `docs/commands/`, `docs/overrides/`, `docs/css/`, `docs/requirements.txt`, `docs/.gitignore`, and the four migrated `docs/*.md` pages +- Keep: `docs/CHANGELOG.md` (retained for the `Unreleased` section and as the notes source for future releases), `docs/images/` +- Modify: `CLAUDE.md` (docs commands + changelog/release process), `docs/superpowers/spikes/2026-07-01-great-docs-findings.md` if any notes changed + +**Interfaces:** +- Consumes: everything from Tasks 4–7. +- Produces: a repo with no mkdocs residue and an accurate `CLAUDE.md`. + +- [ ] **Step 1: Delete mkdocs files and migrated originals** + +```bash +git rm mkdocs.yml docs/requirements.txt docs/.gitignore +git rm -r docs/commands docs/overrides docs/css +git rm docs/index.md docs/deploying.md docs/programmatic-provisioning.md docs/server-administration.md +git rm assets/custom.css # inert: great-docs has no custom-CSS hook, and its ../images/*.svg paths are broken +``` +(Keep `assets/iconPositConnect.svg` and `assets/favicon.ico` — those ARE wired via `great-docs.yml`.) + +- [ ] **Step 2: Confirm no mkdocs references remain** + +Run: +```bash +grep -rniE "mkdocs|pymdownx|mkdocs-click|mkdocs-macros" . --exclude-dir=.git --exclude-dir=great-docs/_site --exclude-dir=docs/superpowers +``` +Expected: no output (or only historical mentions inside `docs/CHANGELOG.md`, which are fine). + +- [ ] **Step 3: Update `CLAUDE.md`** + +- In the Documentation commands section, replace the mkdocs `just docs`/`docs-serve` descriptions with the great-docs equivalents. Note: requires the **Quarto CLI**; `just docs` builds via great-docs into `great-docs/_site/` using an isolated `.venv-docs` (Python 3.12) — it does NOT use `uv run --with`. +- In the Releasing section, update the changelog guidance: release notes are now authored in the GitHub Release (source of truth for the published changelog); `docs/CHANGELOG.md` retains only the `Unreleased` section for in-flight work. Reference `scripts/backfill_release_notes.py` as the one-time migration (run its dry-run, then `--apply`). + +- [ ] **Step 4: Full build + parity check** + +Run: +```bash +just docs +``` +Then verify against this checklist (manually open `great-docs/_site/`): +- All four narrative pages present under `user-guide/` with correct titles. +- All 16 CLI commands documented under `reference/cli/` (add, bootstrap, content, deploy, details, environment, info, integration, list, login, logout, quickstart, remove, system, version, write-manifest). +- Changelog page renders (full history appears once the user applies the Task 3 backfill; empty/short before that is expected). +- No leaked `{{ ... }}` macro token; great-docs shows the package version automatically. +- Posit logo present; GTM (`GTM-KHBDBW7`) snippet present on pages. + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "build: remove mkdocs toolchain, update CLAUDE.md" +``` + +--- + +## Self-Review + +**Spec coverage:** +- S3 hosting preserved → Task 7 (S3 recipes retargeted, CI unchanged in intent). ✓ +- CLI + narrative + changelog scope, no API reference → Tasks 4 (`reference.enabled: false`), 5 (narrative), 3/4 (changelog). ✓ +- Single-version → no `versions:` key introduced. ✓ +- Backfill-then-auto changelog → Tasks 2, 3, then 4 (`changelog.enabled`). ✓ +- Spike-then-migrate strategy → Task 1 gates Tasks 4–8. ✓ +- All narrative pages to `.qmd` → Task 5. ✓ +- CLI auto-discovery from `rsconnect.main` → Tasks 1, 4. ✓ +- Version substitution → Tasks 1, 5, 7. ✓ +- GTM/analytics → Tasks 1, 6. ✓ +- Build tooling + PR previews → Task 7. ✓ +- Cutover/cleanup + CLAUDE.md → Task 8. ✓ +- Risks (reference-disable, GTM, content-dir layout, URL stability) → carried into Task 1 findings with fallbacks. + +**Placeholder scan:** No "TBD/TODO". The intentional spike-dependent values (exact YAML keys, analytics mechanism) are resolved in Task 1 and referenced with documented fallbacks — not silent gaps. + +**Type consistency:** `parse_changelog(text) -> dict[str, str]` and `build_backfill_plan(entries, existing_tags, release_has_body) -> list[tuple[str, str]]` are used consistently across Tasks 2–3. + +**Open item — URL stability:** great-docs' CLI page structure differs from the current `commands/<name>/` paths; inbound deep links may break. Enumerate broken paths during Task 8's parity check and decide whether S3 redirects are warranted (out of scope for this plan unless the check surfaces critical links). diff --git a/docs/superpowers/specs/2026-07-01-great-docs-migration-design.md b/docs/superpowers/specs/2026-07-01-great-docs-migration-design.md new file mode 100644 index 000000000..ff09712c2 --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-great-docs-migration-design.md @@ -0,0 +1,164 @@ +# Migrate rsconnect-python docs from mkdocs to great-docs + +**Date:** 2026-07-01 +**Status:** Approved for planning + +## Goal + +Replace the mkdocs/Material documentation toolchain with +[great-docs](https://posit-dev.github.io/great-docs/) (a Quarto-based framework) +while preserving the site's current audience-facing behavior. + +Preserve: + +- Canonical URL `docs.posit.co/rsconnect-python` via **S3 hosting** with + tag-based promotion (great-docs replaces only the build step; CI keeps syncing + the built directory to S3). +- Current site **scope**: CLI reference + narrative guides + changelog. **No + Python API reference** — the public contract is the CLI, not rsconnect's + importable internals. +- **Single-version** behavior (one "latest" site), matching today's + promotion-on-tag model. +- **PR previews**. + +Change: + +- Changelog moves to great-docs' **auto-generated-from-GitHub-Releases** model, + after a one-time backfill of Release bodies from the existing `CHANGELOG.md`. + +Strategy: **spike to de-risk, then a single cutover PR** (chosen over big-bang +and parallel dual-build). + +## Current state (baseline) + +- `mkdocs.yml` — Material theme, `docs/overrides/` header/footer partials, custom + CSS, Posit logos, Google Tag Manager analytics (`GTM-KHBDBW7`). +- Narrative content (markdown): `docs/index.md`, + `docs/programmatic-provisioning.md`, `docs/deploying.md`, + `docs/server-administration.md`. +- CLI reference: 16 stub files in `docs/commands/*.md`, each a + `::: mkdocs-click` directive pointing at command objects in `rsconnect.main`. +- `mkdocs-macros` injects `{{ rsconnect_python.version }}` (from a `VERSION` env + var) into `deploying.md`. +- `docs/CHANGELOG.md` maintained by hand (Keep-a-Changelog format, 40+ versioned + sections plus an `## Unreleased` section); treated as release source of truth. +- Builds to `site/`, deployed to S3; promoted on tag. PR previews via + `pr-preview-action` publishing `./site/`. +- Markdown extensions in use: admonitions, `pymdownx.tabbed`, snippets, mermaid, + footnotes, magiclink, keys. + +### great-docs facts established during research + +- Quarto-based, Python ≥3.11, **requires the Quarto CLI installed**. +- Config in `great-docs.yml` at project root; **auto-generates `_quarto.yml`** + (do not hand-edit — overwritten each build). +- CLI docs: `cli: enabled: true`; auto-discovers the Click group. + `rsconnect.main` is a standard discovery location. +- Changelog auto-generated from GitHub Releases (`changelog.enabled`, default on); + reads repo URL from `pyproject.toml` `[project.urls] Repository`. +- Output directory is `great-docs/_site/`. Deployment to **S3 is explicitly + supported** (sync that directory). +- Build/preview via the `great-docs` CLI (`great-docs build`, + `great-docs setup-github-pages`; preview via build + open). +- **Empty Release bodies today:** all ~89 tags (back to `1.5.0b1`, 2020) have no + notes. The real history lives entirely in `CHANGELOG.md`. + +## Phases + +### Phase 0 — Spike (throwaway branch) + +Validate load-bearing unknowns against the real package before committing to the +rewrite. Produces a short findings note that either greenlights Phase 2 or flags +required design changes. + +1. Can great-docs build **CLI-only** with the API reference disabled/absent? + (Biggest unknown — great-docs is primarily an API-doc generator.) Fallback: + accept a minimal auto-generated reference, or revisit scope. +2. Does CLI auto-discovery of `rsconnect.main`'s Click group produce acceptable + output for all 16 commands? +3. Can GTM analytics (`GTM-KHBDBW7`) be injected given great-docs owns + `_quarto.yml`? +4. What is the Quarto/great-docs equivalent for the `{{ rsconnect_python.version }}` + substitution used in `deploying.md`? +5. Confirm `great-docs/_site/` output syncs to S3 cleanly. + +### Phase 1 — Changelog backfill (independent, reversible) + +One-time script: + +- Parse `docs/CHANGELOG.md` by `## [X.Y.Z] - YYYY-MM-DD` section. +- Map each section to its matching git tag (`X.Y.Z`); handle `bN` prereleases. +- Populate each empty GitHub Release body via `gh release edit <tag> --notes-file -`. +- Verify a sample of releases renders correctly. + +Leaves the `## Unreleased` block in `CHANGELOG.md` (GitHub Releases can't +represent an untagged section). Going forward, release notes are authored in the +GitHub Release; update `CLAUDE.md`'s release section to reflect the shifted +source of truth. This phase is reversible (touches only Release notes, not +artifacts) and can run before or in parallel with the site work. + +### Phase 2 — great-docs scaffolding + +- Add `great-docs.yml`: CLI enabled, changelog enabled (auto), API reference + disabled, homepage + user-guide layout, Posit branding. +- Swap the `docs` dependency group in `pyproject.toml`: remove mkdocs packages, + add `great-docs`. +- Add Quarto CLI to the docs CI job. +- Confirm `_quarto.yml` is generated and git-ignored. +- Resolve exact source-content layout great-docs expects (`great-docs/` dir vs + root `user_guide/`) — informed by Phase 0. + +### Phase 3 — Content migration + +- **Narrative → `.qmd` (all pages, uniformly):** `index.qmd`, + `programmatic-provisioning.qmd`, `deploying.qmd`, `server-administration.qmd`. + No `.md` carve-out — uniform `.qmd` enables executable examples, version + substitution, and future version fences without a later format migration. + Convert mkdocs-isms to Quarto equivalents: admonitions → callouts, + `pymdownx.tabbed` → tabsets, snippets → includes, mermaid (native), + footnotes/magiclink. +- **CLI reference:** delete the 16 `docs/commands/*.md` mkdocs-click stubs; + great-docs generates this section. +- **Branding:** port Posit logos, favicon, custom CSS, and footer/header intent + into great-docs theming config; drop `docs/overrides/`. +- **Changelog:** rendered via auto-changelog (Phase 1 makes the history + complete). + +### Phase 4 — Build tooling & CI + +- `justfile`: `docs` / `docs-serve` → `great-docs build` / preview; S3 sync + recipes retarget `site/` → `great-docs/_site/`. +- `.github/workflows/main.yml` docs job: install Quarto, run `just docs`; + sync/promote steps unchanged in intent, pointed at the new path. +- `.github/workflows/preview-docs.yml`: point `source-dir` at + `great-docs/_site/`. +- Update or remove `docs/requirements.txt` (superseded by the dependency group). + +### Phase 5 — Cutover & cleanup + +- Remove `mkdocs.yml`, mkdocs dependencies, `docs/overrides/`, and the + mkdocs-click command stubs. +- Update `CLAUDE.md` (docs commands, changelog/release process). +- Verify built-site parity before merge (see Validation). + +## Risks / open questions carried into the plan + +- **API-reference-disable feasibility** (Phase 0 gate) — the biggest unknown, + given great-docs' purpose. +- **GTM injection** — if unsupported, decide between Quarto-native Google + Analytics or a custom head include if great-docs exposes one. +- **Content dir layout** — exactly where great-docs expects source, resolved in + Phase 0/2. +- **URL/anchor stability** — great-docs' CLI page structure differs from the + current per-command nav; existing deep links (e.g. `commands/deploy/`) may + change. Enumerate any inbound links that break and decide whether redirects + are needed. + +## Validation / testing + +- Phase 0 spike findings note. +- Local `great-docs build` producing a complete `great-docs/_site/`. +- Manual parity checklist: all narrative pages present; all 16 CLI commands + documented; changelog history complete; `{{ version }}` substitution resolves; + Posit branding present; GTM/analytics firing. +- PR-preview visual check before the S3 cutover. diff --git a/docs/superpowers/spikes/2026-07-01-great-docs-findings.md b/docs/superpowers/spikes/2026-07-01-great-docs-findings.md new file mode 100644 index 000000000..9bc2f3ea9 --- /dev/null +++ b/docs/superpowers/spikes/2026-07-01-great-docs-findings.md @@ -0,0 +1,102 @@ +# great-docs spike findings — 2026-07-01 + +Validated great-docs against the real rsconnect-python package (via a scratch project + the +installed package). Results gate/adjust plan Tasks 4–8. + +## ✅ CLI-only build works; Python API reference can be suppressed + +Config that builds CLI + user-guide + changelog with **no Python API reference**: + +```yaml +display_name: rsconnect-python +cli: + enabled: true + module: rsconnect.main +reference: false # boolean false disables auto Python API reference +changelog: + enabled: true +``` + +**CORRECTION (found during Task 4 against the real package):** use `reference: false`, NOT +`reference: []`. In great-docs 0.14.1, an empty list is falsy and falls through to full API +auto-discovery (config.py:834, core.py:8452) — it enumerates every class in every module. The +spike's original `reference: []` result (zero API pages) was a **false positive**: the spike probe +used a throwaway package with no importable module, so griffe found nothing to document regardless +of the key. Against the real, importable `rsconnect` package, only `reference: false` suppresses it. +With `reference: false`: 84 CLI reference pages under `reference/cli/`, zero non-CLI reference pages. + +## ✅ CLI auto-discovery from `rsconnect.main` is rich + +great-docs generated `reference/cli/<command>.qmd` for every command **plus per-content-type +subcommands** (e.g. `reference/cli/deploy/{quarto,notebook,...}.qmd`, +`reference/cli/write_manifest/{flask,dash,voila,api,nodejs,gradio,fastapi,pyproject,notebook,panel}.qmd`). +Cosmetic: Click warns "parameter --verbose/-v used more than once" — originates in rsconnect's +CLI, not great-docs; harmless. + +## Content location (corrects the plan) + +- `user_guide/` lives at the **project root** by default (NOT `great-docs/user_guide/`). Override + with the `user_guide:` key if needed. +- great-docs renders into a **managed `great-docs/` working directory** and writes output to + **`great-docs/_site/`**. It also generates `great-docs/_quarto.yml`, `great-docs/index.qmd`, + `great-docs/scripts/post-render.py`, `great-docs/_package_meta.json`, `skill.md`, etc. + All generated/managed paths must be git-ignored. + +## Build recipe (corrects the plan's `uv run --with` approach) + +The `uv run --python 3.12 --with great-docs` ephemeral approach **fails**: Quarto's post-render +hook (`great-docs/scripts/post-render.py`) runs under an interpreter that must have `pygments` +AND `great_docs` importable, and the ephemeral env is not the interpreter Quarto uses. + +**Working recipe:** a dedicated venv (Python 3.12) with `great-docs`, `pygments`, and the project +installed, run **activated** so `python3` on PATH is that venv: + +```bash +uv venv --python 3.12 .venv-docs +uv pip install --python .venv-docs great-docs pygments . # "." installs rsconnect +source .venv-docs/bin/activate +great-docs build # -> great-docs/_site/ +``` + +- `pygments` must be installed explicitly (declared great-docs dep, but the post-render subprocess + needs it on the active interpreter). +- A non-`.venv` name (`.venv-docs`) works when **activated**, so it won't clobber the dev/test + `.venv`. (great-docs only auto-detects `.venv`/`venv` in the project root for `QUARTO_PYTHON`; + otherwise it uses its own `sys.executable`, which the activated venv satisfies.) +- Requires-python note: installing into a 3.12 venv via `uv pip install` avoids the dependency-group + resolution conflict between the project (`>=3.8`) and great-docs (`>=3.11`). + +## ❌ Version substitution has no clean mechanism + +`_variables.yml` at the project root is NOT picked up (Quarto renders in the managed `great-docs/` +dir; the root variables file never reaches it). The literal `{{< var ... >}}` token survives in +output. **great-docs auto-detects the package version (1.29.0) and injects version badges**, so the +version is already displayed. Options for the inline "Generated from rsconnect-python X" line: +(a) drop it (redundant with the auto version badge), or (b) post-build `sed` over +`great-docs/_site/**/*.html` replacing a sentinel token. + +## ✅ GTM/analytics via `include_in_header` (Quarto includes) + +great-docs has no dedicated analytics key, and `site:` keys are whitelisted (theme, toc, toc-depth, +toc-title, show_dates, date_format, show_author, show_security) so nesting includes under `site:` +does NOT work. BUT great-docs supports a **top-level `include_in_header`** key that it merges into +Quarto's `format.html.include-in-header` (core.py:11247–11250; config.py:1205). Accepts an inline +`text:` block or a `file:` entry. Verified: an inline GTM `<script>` snippet was injected into the +`<head>` of **all 88 pages**: + +```yaml +include_in_header: + - text: | + <!-- Google Tag Manager --> + <script>...GTM-KHBDBW7...</script> +``` + +This is the clean, config-based path — no post-build HTML mutation needed for analytics. (Note: +`include_in_header` is head-only; the GTM `<noscript>` body iframe has no config hook, but the head +script is the functional part for JS-enabled clients.) + +## Net gate result + +Feasible. The scope-defining assumptions hold (CLI-only, S3 output dir, single version). Two +cosmetic/outward-facing items (version line, GTM) lack config hooks and need a post-build step or a +drop decision — escalated to the user before Tasks 5–7. diff --git a/great-docs.yml b/great-docs.yml new file mode 100644 index 000000000..2efc3fa9d --- /dev/null +++ b/great-docs.yml @@ -0,0 +1,27 @@ +# great-docs.yml +display_name: rsconnect-python +cli: + enabled: true + module: rsconnect.main +logo: assets/iconPositConnect.svg +favicon: assets/favicon.ico +reference: false # disable auto Python API reference; an empty list [] is falsy and would NOT disable it +user_guide: + - section: "User Guide" + contents: + - index.qmd + - programmatic-provisioning.qmd + - deploying.qmd + - server-administration.qmd +changelog: + enabled: true + max_releases: 100 +include_in_header: + - text: | + <!-- Google Tag Manager --> + <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer','GTM-KHBDBW7');</script> + <!-- End Google Tag Manager --> diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index a92bcba56..000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,95 +0,0 @@ -site_name: 'rsconnect-python' -copyright: Posit Software, PBC. All Rights Reserved - -markdown_extensions: - - attr_list - - mkdocs-click - - admonition - - footnotes - - pymdownx.details - - pymdownx.inlinehilite - - pymdownx.magiclink - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.snippets: - base_path: "docs/" - - pymdownx.highlight - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format - - meta - - toc: - permalink: "#" - - pymdownx.tabbed: - alternate_style: true - - pymdownx.emoji - - pymdownx.keys - - md_in_html - -plugins: - - macros - - search - -nav: - - Getting Started: index.md - - Programmatic Provisioning: programmatic-provisioning.md - - Deploying Content: deploying.md - - Server Administration: server-administration.md - - CLI reference: - - rsconnect: - - add: commands/add.md - - bootstrap: commands/bootstrap.md - - content: commands/content.md - - deploy: commands/deploy.md - - details: commands/details.md - - environment: commands/environment.md - - info: commands/info.md - - integration: commands/integration.md - - list: commands/list.md - - login: commands/login.md - - logout: commands/logout.md - - quickstart: commands/quickstart.md - - remove: commands/remove.md - - system: commands/system.md - - version: commands/version.md - - write-manifest: commands/write-manifest.md - - -theme: - features: - - navigation.expand - name: material - custom_dir: docs/overrides - font: - text: Open Sans - logo: 'images/iconPositConnect.svg' - favicon: 'images/favicon.ico' - palette: - - media: "(prefers-color-scheme)" - toggle: - icon: material/brightness-auto - name: Switch to light mode - primary: white - accent: blue - - scheme: default - primary: white - toggle: - icon: material/toggle-switch-off-outline - name: Switch to dark mode - - scheme: slate - primary: black - toggle: - icon: material/toggle-switch - name: Switch to light mode - -extra_css: - - docs/css/custom.css - -extra: - rsconnect_python: - version: !!python/object/apply:os.getenv ["VERSION"] - analytics: - provider: google - property: 'GTM-KHBDBW7' diff --git a/pyproject.toml b/pyproject.toml index 2890f4802..16eb62a96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,12 +39,6 @@ test = [ "ruff", "pyright", ] -docs = [ - "mkdocs-material", - "mkdocs-click", - "pymdown-extensions", - "mkdocs-macros-plugin", -] [project.urls] Repository = "http://github.com/posit-dev/rsconnect-python" diff --git a/scripts/backfill_release_notes.py b/scripts/backfill_release_notes.py new file mode 100644 index 000000000..3eb1359de --- /dev/null +++ b/scripts/backfill_release_notes.py @@ -0,0 +1,122 @@ +"""Backfill empty GitHub Release bodies from docs/CHANGELOG.md. + +The GitHub Releases for this project have empty bodies; the real history +lives in docs/CHANGELOG.md (Keep a Changelog format). This one-time script +parses that file and populates each matching Release so great-docs can +generate its Changelog page from Releases. +""" + +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from typing import Dict + +VERSION_HEADER = re.compile(r"^## \[(?P<version>\d+\.\d+\.\d+(?:[a-z]+\d+)?)\](?: - \S+)?\s*$") +LINK_REF = re.compile(r"^\[[^\]]+\]:\s+https?://") + + +def parse_changelog(text: str) -> Dict[str, str]: + """Split a Keep-a-Changelog document into {version: body}. + + Skips the 'Unreleased' section and trailing link-reference + definitions. Body is the markdown between a version header and the + next '## ' header, with surrounding blank lines stripped. + """ + entries: Dict[str, str] = {} + current: str | None = None + buffer: list[str] = [] + + def flush() -> None: + if current is not None: + entries[current] = "\n".join(buffer).strip() + + for line in text.splitlines(): + if line.startswith("## "): + flush() + match = VERSION_HEADER.match(line) + current = match.group("version") if match else None + buffer = [] + continue + if LINK_REF.match(line): + continue + if current is not None: + buffer.append(line) + + flush() + return entries + + +def build_backfill_plan( + entries: Dict[str, str], + existing_tags: set[str], + release_has_body: Dict[str, bool], +) -> list[tuple[str, str]]: + """Return (tag, body) edits for releases that exist and are empty.""" + plan: list[tuple[str, str]] = [] + for version, body in entries.items(): + if version not in existing_tags: + continue + if release_has_body.get(version, False): + continue + plan.append((version, body)) + return plan + + +def _gh_releases() -> Dict[str, bool]: + """Map existing release tag -> whether its body is non-empty.""" + out = subprocess.run( + ["gh", "release", "list", "--limit", "500", "--json", "tagName,name"], + check=True, + capture_output=True, + text=True, + ).stdout + result: Dict[str, bool] = {} + for rel in json.loads(out): + tag = rel["tagName"] + try: + body = subprocess.run( + ["gh", "release", "view", tag, "--json", "body", "-q", ".body"], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + except subprocess.CalledProcessError as err: + print(f"warning: could not read release {tag}: {err}", file=sys.stderr) + result[tag] = True + continue + result[tag] = bool(body) + return result + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("changelog", nargs="?", default="docs/CHANGELOG.md") + parser.add_argument("--apply", action="store_true", help="Actually edit releases") + args = parser.parse_args(argv) + + with open(args.changelog, encoding="utf-8") as handle: + entries = parse_changelog(handle.read()) + + body_by_tag = _gh_releases() + existing = set(body_by_tag) + plan = build_backfill_plan(entries, existing, body_by_tag) + + for tag, body in plan: + print(f"{'APPLY' if args.apply else 'DRY-RUN'}: {tag} ({len(body)} chars)") + if args.apply: + subprocess.run( + ["gh", "release", "edit", tag, "--notes-file", "-"], + input=body, + text=True, + check=True, + ) + print(f"{len(plan)} release(s) {'updated' if args.apply else 'would be updated'}.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_backfill_release_notes.py b/tests/test_backfill_release_notes.py new file mode 100644 index 000000000..b6b34ed3c --- /dev/null +++ b/tests/test_backfill_release_notes.py @@ -0,0 +1,60 @@ +from scripts.backfill_release_notes import build_backfill_plan, parse_changelog + +SAMPLE = """# Changelog + +Some preamble. + +## Unreleased + +- unreleased thing + +## [1.29.0] - 2026-04-29 + +- Added `rsconnect deploy nodejs` command. + +### Added + +- `rsconnect content get-lockfile` command. + +## [1.28.2] - 2025-12-05 + +### Fixed + +- Corrected Changelog. + +[Unreleased]: https://github.com/posit-dev/rsconnect-python/compare/1.5.0...HEAD +[1.5.0]: https://github.com/posit-dev/rsconnect-python/releases/tag/1.5.0 +""" + + +def test_parse_changelog_extracts_versions_only(): + entries = parse_changelog(SAMPLE) + assert set(entries) == {"1.29.0", "1.28.2"} + + +def test_parse_changelog_excludes_unreleased_and_link_refs(): + entries = parse_changelog(SAMPLE) + assert "unreleased thing" not in "".join(entries.values()) + assert "compare/1.5.0" not in "".join(entries.values()) + + +def test_parse_changelog_keeps_body_and_subsections(): + entries = parse_changelog(SAMPLE) + assert "deploy nodejs" in entries["1.29.0"] + assert "### Added" in entries["1.29.0"] + assert "get-lockfile" in entries["1.29.0"] + assert entries["1.28.2"].startswith("### Fixed") + + +def test_parse_changelog_strips_surrounding_blank_lines(): + entries = parse_changelog(SAMPLE) + assert not entries["1.29.0"].startswith("\n") + assert not entries["1.29.0"].endswith("\n") + + +def test_build_backfill_plan_skips_missing_and_nonempty(): + entries = {"1.29.0": "body A", "1.28.2": "body B", "1.0.0": "old"} + existing_tags = {"1.29.0", "1.28.2"} # 1.0.0 has no release + release_has_body = {"1.29.0": False, "1.28.2": True} # 1.28.2 already has notes + plan = build_backfill_plan(entries, existing_tags, release_has_body) + assert plan == [("1.29.0", "body A")] diff --git a/docs/deploying.md b/user_guide/deploying.qmd similarity index 99% rename from docs/deploying.md rename to user_guide/deploying.qmd index 60642075c..883f056e2 100644 --- a/docs/deploying.md +++ b/user_guide/deploying.qmd @@ -1,3 +1,7 @@ +--- +title: "Deploying Content" +--- + ### Notebook Deployment Options There are a variety of options available to you when deploying a Jupyter notebook to @@ -546,10 +550,6 @@ in an `rsconnect-python` subdirectory, if possible. If that location is not writ during deployment, then the deployment data will be stored in the global configuration directory specified above. -<div style="display:none"> -Generated from <code>rsconnect-python {{ rsconnect_python.version }}</code> -</div> - ### Hide Jupyter Notebook Input Code Cells You can render a Jupyter notebook without its corresponding input code cells by passing the '--hide-all-input' flag through the cli: diff --git a/docs/index.md b/user_guide/index.qmd similarity index 98% rename from docs/index.md rename to user_guide/index.qmd index a0024d5b5..fa528c1a0 100644 --- a/docs/index.md +++ b/user_guide/index.qmd @@ -1,3 +1,8 @@ +--- +title: "Getting Started" +pagetitle: "Getting Started" +--- + # `rsconnect` This package provides a (command-line interface) CLI for interacting diff --git a/docs/programmatic-provisioning.md b/user_guide/programmatic-provisioning.qmd similarity index 93% rename from docs/programmatic-provisioning.md rename to user_guide/programmatic-provisioning.qmd index fec6d7478..2b564772e 100644 --- a/docs/programmatic-provisioning.md +++ b/user_guide/programmatic-provisioning.qmd @@ -1,3 +1,7 @@ +--- +title: "Programmatic Provisioning" +--- + # Programmatic Provisioning Posit Connect supports the programmatic bootstrapping of an administrator API key diff --git a/docs/server-administration.md b/user_guide/server-administration.qmd similarity index 99% rename from docs/server-administration.md rename to user_guide/server-administration.qmd index 21168dbc9..16e28339e 100644 --- a/docs/server-administration.md +++ b/user_guide/server-administration.qmd @@ -1,3 +1,7 @@ +--- +title: "Server Administration" +--- + # Server Administration Starting with the 2023.05 edition of Posit Connect, `rsconnect-python` can be diff --git a/uv.lock b/uv.lock index fca5283e9..e55320028 100644 --- a/uv.lock +++ b/uv.lock @@ -106,18 +106,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] -[[package]] -name = "babel" -version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytz", marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, -] - [[package]] name = "backcall" version = "0.2.0" @@ -136,60 +124,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, ] -[[package]] -name = "backrefs" -version = "5.7.post1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -sdist = { url = "https://files.pythonhosted.org/packages/df/30/903f35159c87ff1d92aa3fcf8cb52de97632a21e0ae43ed940f5d033e01a/backrefs-5.7.post1.tar.gz", hash = "sha256:8b0f83b770332ee2f1c8244f4e03c77d127a0fa529328e6a0e77fa25bee99678", size = 6582270, upload-time = "2024-06-16T18:38:20.166Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/bb/47fc255d1060dcfd55b460236380edd8ebfc5b2a42a0799ca90c9fc983e3/backrefs-5.7.post1-py310-none-any.whl", hash = "sha256:c5e3fd8fd185607a7cb1fefe878cfb09c34c0be3c18328f12c574245f1c0287e", size = 380429, upload-time = "2024-06-16T18:38:10.131Z" }, - { url = "https://files.pythonhosted.org/packages/89/72/39ef491caef3abae945f5a5fd72830d3b596bfac0630508629283585e213/backrefs-5.7.post1-py311-none-any.whl", hash = "sha256:712ea7e494c5bf3291156e28954dd96d04dc44681d0e5c030adf2623d5606d51", size = 392234, upload-time = "2024-06-16T18:38:12.283Z" }, - { url = "https://files.pythonhosted.org/packages/6a/00/33403f581b732ca70fdebab558e8bbb426a29c34e0c3ed674a479b74beea/backrefs-5.7.post1-py312-none-any.whl", hash = "sha256:a6142201c8293e75bce7577ac29e1a9438c12e730d73a59efdd1b75528d1a6c5", size = 398110, upload-time = "2024-06-16T18:38:14.257Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ea/df0ac74a26838f6588aa012d5d801831448b87d0a7d0aefbbfabbe894870/backrefs-5.7.post1-py38-none-any.whl", hash = "sha256:ec61b1ee0a4bfa24267f6b67d0f8c5ffdc8e0d7dc2f18a2685fd1d8d9187054a", size = 369477, upload-time = "2024-06-16T18:38:16.196Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e8/e43f535c0a17a695e5768670fc855a0e5d52dc0d4135b3915bfa355f65ac/backrefs-5.7.post1-py39-none-any.whl", hash = "sha256:05c04af2bf752bb9a6c9dcebb2aff2fab372d3d9d311f2a138540e307756bd3a", size = 380429, upload-time = "2024-06-16T18:38:18.079Z" }, -] - -[[package]] -name = "backrefs" -version = "6.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/39/3765df263e08a4df37f4f43cb5aa3c6c17a4bdd42ecfe841e04c26037171/backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8", size = 381075, upload-time = "2026-02-16T19:10:04.322Z" }, - { url = "https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be", size = 392874, upload-time = "2026-02-16T19:10:06.314Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90", size = 398787, upload-time = "2026-02-16T19:10:08.274Z" }, - { url = "https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b", size = 400747, upload-time = "2026-02-16T19:10:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7", size = 412602, upload-time = "2026-02-16T19:10:12.317Z" }, - { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" }, -] - -[[package]] -name = "backrefs" -version = "7.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, - { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, - { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" }, -] - [[package]] name = "beautifulsoup4" version = "4.15.0" @@ -1223,18 +1157,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, ] -[[package]] -name = "ghp-import" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, -] - [[package]] name = "gitdb" version = "4.0.12" @@ -1283,15 +1205,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, ] -[[package]] -name = "hjson" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/e5/0b56d723a76ca67abadbf7fb71609fb0ea7e6926e94fcca6c65a85b36a0e/hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75", size = 40541, upload-time = "2022-08-13T02:53:01.919Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89", size = 54018, upload-time = "2022-08-13T02:52:59.899Z" }, -] - [[package]] name = "httpretty" version = "1.1.4" @@ -2015,53 +1928,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] -[[package]] -name = "markdown" -version = "3.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -dependencies = [ - { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086, upload-time = "2024-08-16T15:55:17.812Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349, upload-time = "2024-08-16T15:55:16.176Z" }, -] - -[[package]] -name = "markdown" -version = "3.9" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, -] - -[[package]] -name = "markdown" -version = "3.10.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", -] -sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -2309,15 +2175,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[package]] -name = "mergedeep" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, -] - [[package]] name = "mistune" version = "3.3.2" @@ -2331,191 +2188,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/43/894c2cbbcbdf53b57d1257a249811abe2ee9ab7ef76af301b40f1c054533/mistune-3.3.2-py3-none-any.whl", hash = "sha256:a678a56387d487db7368ede4647cb2ba1deff22ce61f92343e4ebe0ddfce4f2d", size = 61554, upload-time = "2026-06-23T00:29:27.088Z" }, ] -[[package]] -name = "mkdocs" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "ghp-import" }, - { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "jinja2", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "jinja2", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "mergedeep" }, - { name = "mkdocs-get-deps", version = "0.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "mkdocs-get-deps", version = "0.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "packaging" }, - { name = "pathspec", version = "0.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pathspec", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pyyaml-env-tag", version = "0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pyyaml-env-tag", version = "1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "watchdog", version = "4.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "watchdog", version = "6.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, -] - -[[package]] -name = "mkdocs-click" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -dependencies = [ - { name = "click" }, - { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/61/d6b68573b4c399cd201502e4ea4cbfc12e274333d9ee622668cfbc9940ac/mkdocs_click-0.8.1.tar.gz", hash = "sha256:0a88cce04870c5d70ff63138e2418219c3c4119cc928a59c66b76eb5214edba6", size = 17874, upload-time = "2023-09-18T18:36:09.887Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/ce/12158add31617ea579f7975f502812555371d7b8a4410c993a27d7e20727/mkdocs_click-0.8.1-py3-none-any.whl", hash = "sha256:a100ff938be63911f86465a1c21d29a669a7c51932b700fdb3daa90d13b61ee4", size = 14862, upload-time = "2023-09-18T18:36:08.172Z" }, -] - -[[package]] -name = "mkdocs-click" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "click" }, - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/c7/8c25f3a3b379def41e6d0bb5c4beeab7aa8a394b17e749f498504102cfa5/mkdocs_click-0.9.0.tar.gz", hash = "sha256:6050917628d4740517541422b607404d044117bc31b770c4f9e9e1939a50c908", size = 18720, upload-time = "2025-04-07T16:59:36.387Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/fc/9124ab36e2341e78d8d9c669511bd70f52ea0de8105760c31fabec1f9396/mkdocs_click-0.9.0-py3-none-any.whl", hash = "sha256:5208e828f4f68f63c847c1ef7be48edee9964090390afc8f5b3d4cbe5ea9bbed", size = 15104, upload-time = "2025-04-07T16:59:34.807Z" }, -] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -dependencies = [ - { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" } }, - { name = "mergedeep" }, - { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" } }, - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, -] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "mergedeep" }, - { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, -] - -[[package]] -name = "mkdocs-macros-plugin" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hjson" }, - { name = "jinja2", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "jinja2", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mkdocs" }, - { name = "packaging" }, - { name = "pathspec", version = "0.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pathspec", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "python-dateutil" }, - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "requests", version = "2.32.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "super-collections" }, - { name = "termcolor", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "termcolor", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "termcolor", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/15/e6a44839841ebc9c5872fa0e6fad1c3757424e4fe026093b68e9f386d136/mkdocs_macros_plugin-1.5.0.tar.gz", hash = "sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f", size = 37730, upload-time = "2025-11-13T08:08:55.545Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl", hash = "sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f", size = 44626, upload-time = "2025-11-13T08:08:53.878Z" }, -] - -[[package]] -name = "mkdocs-material" -version = "9.7.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "backrefs", version = "5.7.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "backrefs", version = "6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "backrefs", version = "7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "colorama" }, - { name = "jinja2", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "jinja2", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mkdocs" }, - { name = "mkdocs-material-extensions" }, - { name = "paginate" }, - { name = "pygments", version = "2.19.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pygments", version = "2.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "pymdown-extensions", version = "10.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pymdown-extensions", version = "10.21.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "pymdown-extensions", version = "11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "requests", version = "2.32.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/45/29/6d2bcf41ae40802c4beda2432396fff97b8456fb496371d1bc7aad6512ec/mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69", size = 4097959, upload-time = "2026-03-19T15:41:58.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba", size = 9305470, upload-time = "2026-03-19T15:41:55.217Z" }, -] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, -] - [[package]] name = "more-itertools" version = "10.5.0" @@ -2773,15 +2445,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] -[[package]] -name = "paginate" -version = "0.5.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, -] - [[package]] name = "pandocfilters" version = "1.5.1" @@ -2800,36 +2463,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - -[[package]] -name = "pathspec" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, -] - [[package]] name = "pexpect" version = "4.9.0" @@ -3404,59 +3037,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] -[[package]] -name = "pymdown-extensions" -version = "10.15" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -dependencies = [ - { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" } }, - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/92/a7296491dbf5585b3a987f3f3fc87af0e632121ff3e490c14b5f2d2b4eb5/pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7", size = 852320, upload-time = "2025-04-27T23:48:29.183Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/d1/c54e608505776ce4e7966d03358ae635cfd51dff1da6ee421c090dbc797b/pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f", size = 265845, upload-time = "2025-04-27T23:48:27.359Z" }, -] - -[[package]] -name = "pymdown-extensions" -version = "10.21.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" } }, - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" }, -] - -[[package]] -name = "pymdown-extensions" -version = "11.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", -] -dependencies = [ - { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" } }, - { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/47/67/f1e79672a5f91985577c7984c9709ca110e4fd37fe7fd167b60422e6ccc2/pymdown_extensions-11.0.tar.gz", hash = "sha256:8269cef0247f9e2d0a62fcea10860aba05c1cbab5470fd4b63230b96434dc589", size = 857049, upload-time = "2026-06-23T02:27:45.146Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/b6/1ae53367e28b9cffa3be7574e13fbe4589694272fd47710fbdbafd3d63c6/pymdown_extensions-11.0-py3-none-any.whl", hash = "sha256:fbc4acb641814fa9d17521bbd21a5240ef739a662f11c06330c4b78c93e954d6", size = 269415, upload-time = "2026-06-23T02:27:43.826Z" }, -] - [[package]] name = "pyopenssl" version = "24.3.0" @@ -3828,43 +3408,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, ] -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -dependencies = [ - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631, upload-time = "2020-11-12T02:38:26.239Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911, upload-time = "2020-11-12T02:38:24.638Z" }, -] - -[[package]] -name = "pyyaml-env-tag" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "pyyaml", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, -] - [[package]] name = "pyzmq" version = "27.1.0" @@ -4785,15 +4328,6 @@ snowflake = [ ] [package.dev-dependencies] -docs = [ - { name = "mkdocs-click", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "mkdocs-click", version = "0.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "mkdocs-macros-plugin" }, - { name = "mkdocs-material" }, - { name = "pymdown-extensions", version = "10.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pymdown-extensions", version = "10.21.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "pymdown-extensions", version = "11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] test = [ { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, @@ -4832,12 +4366,6 @@ requires-dist = [ provides-extras = ["keyring", "snowflake"] [package.metadata.requires-dev] -docs = [ - { name = "mkdocs-click" }, - { name = "mkdocs-macros-plugin" }, - { name = "mkdocs-material" }, - { name = "pymdown-extensions" }, -] test = [ { name = "coverage" }, { name = "httpretty" }, @@ -5347,59 +4875,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] -[[package]] -name = "super-collections" -version = "0.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hjson" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/de/a0c3d1244912c260638f0f925e190e493ccea37ecaea9bbad7c14413b803/super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a", size = 31315, upload-time = "2025-09-30T00:37:08.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/43/47c7cf84b3bd74a8631b02d47db356656bb8dff6f2e61a4c749963814d0d/super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56", size = 16173, upload-time = "2025-09-30T00:37:07.104Z" }, -] - -[[package]] -name = "termcolor" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -sdist = { url = "https://files.pythonhosted.org/packages/10/56/d7d66a84f96d804155f6ff2873d065368b25a07222a6fd51c4f24ef6d764/termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a", size = 12664, upload-time = "2023-12-01T11:04:51.66Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5f/8c716e47b3a50cbd7c146f45881e11d9414def768b7cd9c5e6650ec2a80a/termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63", size = 7719, upload-time = "2023-12-01T11:04:50.019Z" }, -] - -[[package]] -name = "termcolor" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, -] - -[[package]] -name = "termcolor" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", -] -sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, -] - [[package]] name = "tinycss2" version = "1.2.1" @@ -5919,97 +5394,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/cb/d7bb121261db0b829f38b1e86b80d8103afd050b8a5c5c9fe4a988d2f1fc/uv-0.11.25-py3-none-win_arm64.whl", hash = "sha256:79f166cd1b84f855e9d2768221d59b403869648289fd884d58ad4299edfb4d9e", size = 25153588, upload-time = "2026-06-27T00:48:58.495Z" }, ] -[[package]] -name = "watchdog" -version = "4.0.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.8.1' and python_full_version < '3.9'", - "python_full_version < '3.8.1'", -] -sdist = { url = "https://files.pythonhosted.org/packages/4f/38/764baaa25eb5e35c9a043d4c4588f9836edfe52a708950f4b6d5f714fd42/watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270", size = 126587, upload-time = "2024-08-11T07:38:01.623Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/b0/219893d41c16d74d0793363bf86df07d50357b81f64bba4cb94fe76e7af4/watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22", size = 100257, upload-time = "2024-08-11T07:37:04.209Z" }, - { url = "https://files.pythonhosted.org/packages/6d/c6/8e90c65693e87d98310b2e1e5fd7e313266990853b489e85ce8396cc26e3/watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1", size = 92249, upload-time = "2024-08-11T07:37:06.364Z" }, - { url = "https://files.pythonhosted.org/packages/6f/cd/2e306756364a934532ff8388d90eb2dc8bb21fe575cd2b33d791ce05a02f/watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503", size = 92888, upload-time = "2024-08-11T07:37:08.275Z" }, - { url = "https://files.pythonhosted.org/packages/de/78/027ad372d62f97642349a16015394a7680530460b1c70c368c506cb60c09/watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930", size = 100256, upload-time = "2024-08-11T07:37:11.017Z" }, - { url = "https://files.pythonhosted.org/packages/59/a9/412b808568c1814d693b4ff1cec0055dc791780b9dc947807978fab86bc1/watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b", size = 92252, upload-time = "2024-08-11T07:37:13.098Z" }, - { url = "https://files.pythonhosted.org/packages/04/57/179d76076cff264982bc335dd4c7da6d636bd3e9860bbc896a665c3447b6/watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef", size = 92888, upload-time = "2024-08-11T07:37:15.077Z" }, - { url = "https://files.pythonhosted.org/packages/92/f5/ea22b095340545faea37ad9a42353b265ca751f543da3fb43f5d00cdcd21/watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a", size = 100342, upload-time = "2024-08-11T07:37:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d2/8ce97dff5e465db1222951434e3115189ae54a9863aef99c6987890cc9ef/watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29", size = 92306, upload-time = "2024-08-11T07:37:17.997Z" }, - { url = "https://files.pythonhosted.org/packages/49/c4/1aeba2c31b25f79b03b15918155bc8c0b08101054fc727900f1a577d0d54/watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a", size = 92915, upload-time = "2024-08-11T07:37:19.967Z" }, - { url = "https://files.pythonhosted.org/packages/79/63/eb8994a182672c042d85a33507475c50c2ee930577524dd97aea05251527/watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b", size = 100343, upload-time = "2024-08-11T07:37:21.935Z" }, - { url = "https://files.pythonhosted.org/packages/ce/82/027c0c65c2245769580605bcd20a1dc7dfd6c6683c8c4e2ef43920e38d27/watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d", size = 92313, upload-time = "2024-08-11T07:37:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/2a/89/ad4715cbbd3440cb0d336b78970aba243a33a24b1a79d66f8d16b4590d6a/watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7", size = 92919, upload-time = "2024-08-11T07:37:24.715Z" }, - { url = "https://files.pythonhosted.org/packages/55/08/1a9086a3380e8828f65b0c835b86baf29ebb85e5e94a2811a2eb4f889cfd/watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040", size = 100255, upload-time = "2024-08-11T07:37:26.862Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3e/064974628cf305831f3f78264800bd03b3358ec181e3e9380a36ff156b93/watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7", size = 92257, upload-time = "2024-08-11T07:37:28.253Z" }, - { url = "https://files.pythonhosted.org/packages/23/69/1d2ad9c12d93bc1e445baa40db46bc74757f3ffc3a3be592ba8dbc51b6e5/watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4", size = 92886, upload-time = "2024-08-11T07:37:29.52Z" }, - { url = "https://files.pythonhosted.org/packages/68/eb/34d3173eceab490d4d1815ba9a821e10abe1da7a7264a224e30689b1450c/watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9", size = 100254, upload-time = "2024-08-11T07:37:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/18/a1/4bbafe7ace414904c2cc9bd93e472133e8ec11eab0b4625017f0e34caad8/watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578", size = 92249, upload-time = "2024-08-11T07:37:32.193Z" }, - { url = "https://files.pythonhosted.org/packages/f3/11/ec5684e0ca692950826af0de862e5db167523c30c9cbf9b3f4ce7ec9cc05/watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b", size = 92891, upload-time = "2024-08-11T07:37:34.212Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9a/6f30f023324de7bad8a3eb02b0afb06bd0726003a3550e9964321315df5a/watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa", size = 91775, upload-time = "2024-08-11T07:37:35.567Z" }, - { url = "https://files.pythonhosted.org/packages/87/62/8be55e605d378a154037b9ba484e00a5478e627b69c53d0f63e3ef413ba6/watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3", size = 92255, upload-time = "2024-08-11T07:37:37.596Z" }, - { url = "https://files.pythonhosted.org/packages/6b/59/12e03e675d28f450bade6da6bc79ad6616080b317c472b9ae688d2495a03/watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508", size = 91682, upload-time = "2024-08-11T07:37:38.901Z" }, - { url = "https://files.pythonhosted.org/packages/ef/69/241998de9b8e024f5c2fbdf4324ea628b4231925305011ca8b7e1c3329f6/watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee", size = 92249, upload-time = "2024-08-11T07:37:40.143Z" }, - { url = "https://files.pythonhosted.org/packages/70/3f/2173b4d9581bc9b5df4d7f2041b6c58b5e5448407856f68d4be9981000d0/watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1", size = 91773, upload-time = "2024-08-11T07:37:42.095Z" }, - { url = "https://files.pythonhosted.org/packages/f0/de/6fff29161d5789048f06ef24d94d3ddcc25795f347202b7ea503c3356acb/watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e", size = 92250, upload-time = "2024-08-11T07:37:44.052Z" }, - { url = "https://files.pythonhosted.org/packages/8a/b1/25acf6767af6f7e44e0086309825bd8c098e301eed5868dc5350642124b9/watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83", size = 82947, upload-time = "2024-08-11T07:37:45.388Z" }, - { url = "https://files.pythonhosted.org/packages/e8/90/aebac95d6f954bd4901f5d46dcd83d68e682bfd21798fd125a95ae1c9dbf/watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c", size = 82942, upload-time = "2024-08-11T07:37:46.722Z" }, - { url = "https://files.pythonhosted.org/packages/15/3a/a4bd8f3b9381824995787488b9282aff1ed4667e1110f31a87b871ea851c/watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a", size = 82947, upload-time = "2024-08-11T07:37:48.941Z" }, - { url = "https://files.pythonhosted.org/packages/09/cc/238998fc08e292a4a18a852ed8274159019ee7a66be14441325bcd811dfd/watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73", size = 82946, upload-time = "2024-08-11T07:37:50.279Z" }, - { url = "https://files.pythonhosted.org/packages/80/f1/d4b915160c9d677174aa5fae4537ae1f5acb23b3745ab0873071ef671f0a/watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc", size = 82947, upload-time = "2024-08-11T07:37:51.55Z" }, - { url = "https://files.pythonhosted.org/packages/db/02/56ebe2cf33b352fe3309588eb03f020d4d1c061563d9858a9216ba004259/watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757", size = 82944, upload-time = "2024-08-11T07:37:52.855Z" }, - { url = "https://files.pythonhosted.org/packages/01/d2/c8931ff840a7e5bd5dcb93f2bb2a1fd18faf8312e9f7f53ff1cf76ecc8ed/watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8", size = 82947, upload-time = "2024-08-11T07:37:55.172Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d8/cdb0c21a4a988669d7c210c75c6a2c9a0e16a3b08d9f7e633df0d9a16ad8/watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19", size = 82935, upload-time = "2024-08-11T07:37:56.668Z" }, - { url = "https://files.pythonhosted.org/packages/99/2e/b69dfaae7a83ea64ce36538cc103a3065e12c447963797793d5c0a1d5130/watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b", size = 82934, upload-time = "2024-08-11T07:37:57.991Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0b/43b96a9ecdd65ff5545b1b13b687ca486da5c6249475b1a45f24d63a1858/watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c", size = 82933, upload-time = "2024-08-11T07:37:59.573Z" }, -] - -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.10.*' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, - { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" }, - { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" }, - { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" }, - { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, - { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, -] - [[package]] name = "wcwidth" version = "0.8.2"