treestamp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- treestamp-0.1.0/.gitignore +145 -0
- treestamp-0.1.0/.pre-commit-config.yaml +51 -0
- treestamp-0.1.0/.pre-commit-hooks.yaml +14 -0
- treestamp-0.1.0/.python-version +1 -0
- treestamp-0.1.0/CHANGELOG.md +17 -0
- treestamp-0.1.0/LICENSE +21 -0
- treestamp-0.1.0/MAINTENANCE.md +146 -0
- treestamp-0.1.0/Makefile +116 -0
- treestamp-0.1.0/PKG-INFO +205 -0
- treestamp-0.1.0/PRIVACY.md +3 -0
- treestamp-0.1.0/README.md +173 -0
- treestamp-0.1.0/pyproject.toml +133 -0
- treestamp-0.1.0/scripts/changed_tests.py +76 -0
- treestamp-0.1.0/scripts/check_docs.py +143 -0
- treestamp-0.1.0/scripts/distribution_check.py +173 -0
- treestamp-0.1.0/scripts/hook_check.py +69 -0
- treestamp-0.1.0/scripts/installed_editor_check.py +57 -0
- treestamp-0.1.0/scripts/release_check.py +151 -0
- treestamp-0.1.0/src/treestamp/__init__.py +0 -0
- treestamp-0.1.0/src/treestamp/__main__.py +10 -0
- treestamp-0.1.0/src/treestamp/cli.py +197 -0
- treestamp-0.1.0/src/treestamp/codec.py +515 -0
- treestamp-0.1.0/src/treestamp/document.py +476 -0
- treestamp-0.1.0/src/treestamp/editor.py +202 -0
- treestamp-0.1.0/src/treestamp/errors.py +41 -0
- treestamp-0.1.0/src/treestamp/gitio.py +337 -0
- treestamp-0.1.0/src/treestamp/maintenance.py +115 -0
- treestamp-0.1.0/src/treestamp/model.py +266 -0
- treestamp-0.1.0/src/treestamp/py.typed +0 -0
- treestamp-0.1.0/src/treestamp/session.py +327 -0
- treestamp-0.1.0/src/treestamp/sources.py +103 -0
- treestamp-0.1.0/src/treestamp/tui.py +688 -0
- treestamp-0.1.0/src/treestamp/tui_dialogs.py +187 -0
- treestamp-0.1.0/tests/__init__.py +0 -0
- treestamp-0.1.0/tests/conftest.py +107 -0
- treestamp-0.1.0/tests/test_changed_tests.py +45 -0
- treestamp-0.1.0/tests/test_cli.py +358 -0
- treestamp-0.1.0/tests/test_codec.py +576 -0
- treestamp-0.1.0/tests/test_document.py +486 -0
- treestamp-0.1.0/tests/test_editor.py +323 -0
- treestamp-0.1.0/tests/test_gitio.py +250 -0
- treestamp-0.1.0/tests/test_maintenance.py +200 -0
- treestamp-0.1.0/tests/test_output_bytes.py +205 -0
- treestamp-0.1.0/tests/test_paths.py +191 -0
- treestamp-0.1.0/tests/test_release_check.py +65 -0
- treestamp-0.1.0/tests/test_session.py +300 -0
- treestamp-0.1.0/tests/test_sources.py +187 -0
- treestamp-0.1.0/tests/test_tui.py +681 -0
- treestamp-0.1.0/tests/test_tui_dialogs.py +78 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
npm-debug.log*
|
|
4
|
+
yarn-debug.log*
|
|
5
|
+
yarn-error.log*
|
|
6
|
+
pnpm-debug.log*
|
|
7
|
+
.pnpm-store/
|
|
8
|
+
|
|
9
|
+
# Python
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.py[cod]
|
|
12
|
+
*$py.class
|
|
13
|
+
*.so
|
|
14
|
+
.Python
|
|
15
|
+
build/
|
|
16
|
+
develop-eggs/
|
|
17
|
+
dist/
|
|
18
|
+
downloads/
|
|
19
|
+
eggs/
|
|
20
|
+
.eggs/
|
|
21
|
+
lib/
|
|
22
|
+
lib64/
|
|
23
|
+
parts/
|
|
24
|
+
sdist/
|
|
25
|
+
var/
|
|
26
|
+
wheels/
|
|
27
|
+
*.egg-info/
|
|
28
|
+
.installed.cfg
|
|
29
|
+
*.egg
|
|
30
|
+
MANIFEST
|
|
31
|
+
.venv/
|
|
32
|
+
venv/
|
|
33
|
+
ENV/
|
|
34
|
+
env/
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# TypeScript
|
|
39
|
+
*.tsbuildinfo
|
|
40
|
+
.tsc-cache/
|
|
41
|
+
|
|
42
|
+
# Testing
|
|
43
|
+
coverage/
|
|
44
|
+
.nyc_output/
|
|
45
|
+
.pytest_cache/
|
|
46
|
+
htmlcov/
|
|
47
|
+
.tox/
|
|
48
|
+
.coverage
|
|
49
|
+
.coverage.*
|
|
50
|
+
.cache
|
|
51
|
+
nosetests.xml
|
|
52
|
+
coverage.xml
|
|
53
|
+
*.cover
|
|
54
|
+
.hypothesis/
|
|
55
|
+
|
|
56
|
+
# IDEs and editors
|
|
57
|
+
.vscode/
|
|
58
|
+
.idea/
|
|
59
|
+
*.swp
|
|
60
|
+
*.swo
|
|
61
|
+
*~
|
|
62
|
+
.project
|
|
63
|
+
.classpath
|
|
64
|
+
.c9/
|
|
65
|
+
*.launch
|
|
66
|
+
.settings/
|
|
67
|
+
*.sublime-workspace
|
|
68
|
+
*.sublime-project
|
|
69
|
+
|
|
70
|
+
# OS files
|
|
71
|
+
.DS_Store
|
|
72
|
+
Thumbs.db
|
|
73
|
+
ehthumbs.db
|
|
74
|
+
Desktop.ini
|
|
75
|
+
$RECYCLE.BIN/
|
|
76
|
+
*.cab
|
|
77
|
+
*.msi
|
|
78
|
+
*.msm
|
|
79
|
+
*.msp
|
|
80
|
+
|
|
81
|
+
# Logs
|
|
82
|
+
logs/
|
|
83
|
+
*.log
|
|
84
|
+
npm-debug.log*
|
|
85
|
+
yarn-debug.log*
|
|
86
|
+
yarn-error.log*
|
|
87
|
+
lerna-debug.log*
|
|
88
|
+
|
|
89
|
+
# Runtime data
|
|
90
|
+
pids/
|
|
91
|
+
*.pid
|
|
92
|
+
*.seed
|
|
93
|
+
*.pid.lock
|
|
94
|
+
|
|
95
|
+
# Environment variables
|
|
96
|
+
.env
|
|
97
|
+
.env.local
|
|
98
|
+
.env.development.local
|
|
99
|
+
.env.test.local
|
|
100
|
+
.env.production.local
|
|
101
|
+
|
|
102
|
+
# Build outputs
|
|
103
|
+
dist/
|
|
104
|
+
build/
|
|
105
|
+
out/
|
|
106
|
+
.next/
|
|
107
|
+
.nuxt/
|
|
108
|
+
.cache/
|
|
109
|
+
.parcel-cache/
|
|
110
|
+
|
|
111
|
+
# Temporary files
|
|
112
|
+
.tmp/
|
|
113
|
+
.temp/
|
|
114
|
+
tmp/
|
|
115
|
+
temp/
|
|
116
|
+
|
|
117
|
+
# Package files
|
|
118
|
+
*.tgz
|
|
119
|
+
*.tar.gz
|
|
120
|
+
*.zip
|
|
121
|
+
|
|
122
|
+
# Database
|
|
123
|
+
*.sqlite
|
|
124
|
+
*.sqlite3
|
|
125
|
+
*.db
|
|
126
|
+
|
|
127
|
+
# Miscellaneous
|
|
128
|
+
.eslintcache
|
|
129
|
+
.stylelintcache
|
|
130
|
+
*.bak
|
|
131
|
+
*.orig
|
|
132
|
+
|
|
133
|
+
# PERSONAL
|
|
134
|
+
|
|
135
|
+
INTERNAL
|
|
136
|
+
|
|
137
|
+
sync-from-*
|
|
138
|
+
|
|
139
|
+
# Treestamp tooling and local working material
|
|
140
|
+
.stamps/
|
|
141
|
+
.mypy_cache/
|
|
142
|
+
.ruff_cache/
|
|
143
|
+
.playwright-cli/
|
|
144
|
+
output/
|
|
145
|
+
__INTERNAL__/
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
exclude: ^assets/TREESTAMP_BANNER\.png$
|
|
10
|
+
- id: check-merge-conflict
|
|
11
|
+
|
|
12
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
13
|
+
rev: v0.9.7
|
|
14
|
+
hooks:
|
|
15
|
+
- id: ruff
|
|
16
|
+
args: [--fix]
|
|
17
|
+
- id: ruff-format
|
|
18
|
+
|
|
19
|
+
- repo: local
|
|
20
|
+
hooks:
|
|
21
|
+
- id: mypy
|
|
22
|
+
name: mypy (project environment)
|
|
23
|
+
entry: make typecheck
|
|
24
|
+
language: system
|
|
25
|
+
types: [python]
|
|
26
|
+
pass_filenames: false
|
|
27
|
+
- id: web-typecheck
|
|
28
|
+
name: mypy (hosted preview)
|
|
29
|
+
entry: make web-typecheck
|
|
30
|
+
language: system
|
|
31
|
+
files: ^web/
|
|
32
|
+
types: [python]
|
|
33
|
+
pass_filenames: false
|
|
34
|
+
- id: pytest
|
|
35
|
+
name: pytest (package, changed files)
|
|
36
|
+
entry: venv/bin/python scripts/changed_tests.py package
|
|
37
|
+
language: system
|
|
38
|
+
files: ^(src/|tests/|pyproject\.toml|Makefile)
|
|
39
|
+
pass_filenames: true
|
|
40
|
+
- id: web-test
|
|
41
|
+
name: pytest (hosted preview, changed files)
|
|
42
|
+
entry: venv/bin/python scripts/changed_tests.py web
|
|
43
|
+
language: system
|
|
44
|
+
files: ^(web/src/|web/tests/|web/pyproject\.toml|src/)
|
|
45
|
+
pass_filenames: true
|
|
46
|
+
- id: treestamp-check
|
|
47
|
+
name: treestamp check (this repository's README)
|
|
48
|
+
entry: make integration-check
|
|
49
|
+
language: system
|
|
50
|
+
pass_filenames: false
|
|
51
|
+
always_run: true
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
- id: treestamp
|
|
2
|
+
name: treestamp update
|
|
3
|
+
description: Maintain the README tree from staged Git paths
|
|
4
|
+
entry: treestamp update
|
|
5
|
+
language: python
|
|
6
|
+
pass_filenames: false
|
|
7
|
+
always_run: true
|
|
8
|
+
- id: treestamp-check
|
|
9
|
+
name: treestamp check
|
|
10
|
+
description: Fail if the README tree needs maintenance
|
|
11
|
+
entry: treestamp check
|
|
12
|
+
language: python
|
|
13
|
+
pass_filenames: false
|
|
14
|
+
always_run: true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* import treestamp public source tree ([764f4e9](https://github.com/reponomics/treestamp/commit/764f4e9bd9e9630d628c86e90f4d813902a85e6e))
|
|
9
|
+
* prepare release and production deployment automation ([#1](https://github.com/reponomics/treestamp/issues/1)) ([25722b8](https://github.com/reponomics/treestamp/commit/25722b884e29dd8a4ff65e22c782f4e11ea98922))
|
|
10
|
+
|
|
11
|
+
## 0.1.0 (Unreleased)
|
|
12
|
+
|
|
13
|
+
- Create and curate README directory trees in the terminal or web editor.
|
|
14
|
+
- Preserve expanded directories, hidden paths, and single-line annotations when updating a tree.
|
|
15
|
+
- Check and update documents from the Git index or a locally available commit, with pre-commit hooks for automation.
|
|
16
|
+
- Export Unicode, ASCII, or Markdown list trees with optional child counts.
|
|
17
|
+
- Edit public GitHub repository snapshots in the hosted editor and copy the result into your project.
|
treestamp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reponomics
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Maintenance
|
|
2
|
+
|
|
3
|
+
These procedures require a full Git checkout, including the hosted application in `web/`. Use the environment and contributor setup in [CONTRIBUTING](https://github.com/reponomics/treestamp/blob/main/.github/CONTRIBUTING.md). Run `make help` for available commands. Stage source changes before updating the README tree because Treestamp reads the Git index.
|
|
4
|
+
|
|
5
|
+
## Validate and build
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
make ci web-ci docs-check
|
|
9
|
+
make web-browser-test
|
|
10
|
+
make build metadata-check distribution-check
|
|
11
|
+
make integration-check
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Browser checks require the separate browser dependencies described in [the web README](https://github.com/reponomics/treestamp/blob/main/web/README.md). `metadata-check` validates both distributions against the approved file set and reports their hashes. `distribution-check` installs the wheel and source archive into fresh environments and exercises headless commands and the terminal editor. After committing a candidate, `make hook-check` checks the public pre-commit hooks from that exact HEAD.
|
|
15
|
+
|
|
16
|
+
Build release artifacts from a clean, reviewed revision. Inspect the archive contents before publication; internal notes and unrelated products do not belong in either distribution. Remove or archive older files from `dist/` before a release build so checks and uploads cannot select stale artifacts.
|
|
17
|
+
|
|
18
|
+
## Dependency and installer maintenance
|
|
19
|
+
|
|
20
|
+
The core and hosted projects share `uv.lock`. `make install` installs locked dependencies and build tools into `venv`, then builds the local editable packages without isolated dependency resolution. `make build` synchronizes that locked toolchain before building. Docker selects only the hosted runtime dependencies and operational TLS tools from the same lock, builds both local wheels with the locked build group, and installs those wheels offline. Third-party dependencies must have compatible wheels; the controlled install fails rather than resolving unpinned build dependencies for a source archive.
|
|
21
|
+
|
|
22
|
+
The uv version is pinned in `.tool-versions`, checked by the Makefile, and read by CI. Docker uses the matching version and image digest. Update those together, including when Dependabot proposes an update to the Docker image; Dependabot does not maintain the `.tool-versions` pin. Keep pip at least `26.2.2`, certifi at least `2026.10.19`, and uv at least `0.13.4` for the eMudhra certificate incident. Continue updating the pinned Python base image and checking its system certificate store; the Python dependency lock does not manage operating-system certificates.
|
|
23
|
+
|
|
24
|
+
Dependabot proposes uv dependency updates. Review lock changes with their declarations, run the quality gates and container checks, and commit both together. Release Please also updates the two local package versions in the lock. The clean-distribution checks intentionally use pip to resolve the published dependency ranges outside the workspace; they test consumer compatibility separately from the locked development environment. Revert a dependency change by reverting its declarations and lock together, then synchronizing the environment again.
|
|
25
|
+
|
|
26
|
+
## Repository and release setup
|
|
27
|
+
|
|
28
|
+
Use `main` as the default branch and require pull requests and signed commits. After the checks have run in the repository, select these required PR check names:
|
|
29
|
+
|
|
30
|
+
- `Regression suite (Linux, Python 3.11)`
|
|
31
|
+
- `Regression suite (Linux, Python 3.12)`
|
|
32
|
+
- `Regression suite (Windows, Python 3.11)`
|
|
33
|
+
- `Regression suite (Windows, Python 3.12)`
|
|
34
|
+
- `Hosted editor (Linux, Python 3.11)`
|
|
35
|
+
- `README tree freshness`
|
|
36
|
+
- `Validate PR title`
|
|
37
|
+
|
|
38
|
+
Production deployment, Release Please, and PyPI publication do not run on ordinary PRs and should not be required PR checks. Enable immutable GitHub Releases before the first release. CodeQL uses GitHub's default setup with the extended query suite for Python, JavaScript/TypeScript, and GitHub Actions, managed in the repository's code scanning settings. Do not enable an advanced CodeQL workflow alongside default setup: GitHub rejects its analysis uploads. Scorecard runs only when the repository is public; OSV scans resolved Python dependencies while private, retaining its report in Actions without uploading it to code scanning.
|
|
39
|
+
|
|
40
|
+
Release Please manages the core and hosted versions together, updates `CHANGELOG.md`, and creates version tags and GitHub Releases after its release PR is merged. The published-release event runs the PyPI workflow, which validates the tag, versions, and commit on `main`, builds and tests fresh distributions, and publishes them with attestations through Trusted Publishing. Website deployment has its own trigger and does not wait for a package release.
|
|
41
|
+
|
|
42
|
+
Configure these GitHub settings before enabling publication or production deployment:
|
|
43
|
+
|
|
44
|
+
| Setting | Location | Purpose |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `RELEASE_APP_CLIENT_ID` | Repository variable | GitHub App used by Release Please |
|
|
47
|
+
| `RELEASE_APP_PRIVATE_KEY` | Repository secret | Private key for that App |
|
|
48
|
+
| `PYPI_PUBLISH_ENABLED=true` | Repository variable | Enables PyPI publication once the repository is public |
|
|
49
|
+
| `pypi` | GitHub environment | Publication approvals and PyPI's trusted identity |
|
|
50
|
+
| `FLY_DEPLOY_ENABLED=true` | Repository variable | Enables independent production deployment on pushes to `main` |
|
|
51
|
+
| `FLY_API_TOKEN` | Secret in the `production` environment | Deploy token scoped to the production Fly app |
|
|
52
|
+
|
|
53
|
+
Install the release App on the selected `reponomics/treestamp` repository with write access to contents, issues, and pull requests. Its token lets release PRs trigger CI and GitHub Releases trigger publication. The App does not need organization-wide repository access. The `pypi` environment exists; review its protections and those of `production` against the maintainers' approval policy. Publication runs from release tags, while deployment runs from `main`.
|
|
54
|
+
|
|
55
|
+
In PyPI, configure a [pending Trusted Publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) for project `treestamp`, GitHub owner `reponomics`, repository `treestamp`, workflow filename `publish.yml`, and environment `pypi`. No long-lived PyPI upload token is needed. A pending publisher does not reserve the package name.
|
|
56
|
+
|
|
57
|
+
## First release and subsequent releases
|
|
58
|
+
|
|
59
|
+
The first release is configured as `0.1.0`. Complete these steps in order:
|
|
60
|
+
|
|
61
|
+
1. Merge the automation setup to `main`, configure the release App, enable immutable releases, and configure the `pypi` environment and Trusted Publisher.
|
|
62
|
+
2. Finish the public-content review, make the repository public, enable private vulnerability reporting, and set `PYPI_PUBLISH_ENABLED=true`.
|
|
63
|
+
3. Review Release Please's first release PR. Confirm that both project versions and its manifest say `0.1.0`. Consolidate the existing `0.1.0 (Unreleased)` notes into the generated dated release section so the changelog describes the release once.
|
|
64
|
+
4. After its required checks pass, merge that release PR. Release Please creates the tag and GitHub Release; the publication workflow validates and uploads the package. Verify the PyPI files, installation, and attestations.
|
|
65
|
+
|
|
66
|
+
Do not merge the first release PR before publication is ready: a release created while the repository is private or publication is disabled will not publish automatically when those settings change later. Subsequent releases follow the same release-PR process. Explain changes requiring user action in the changelog, including format changes before `1.0`.
|
|
67
|
+
|
|
68
|
+
Published versions are immutable. Correct a released package through a later release; do not move its tag, delete and recreate its release, or upload changed files under the same version. If a publication run fails, inspect which artifacts reached PyPI before retrying it.
|
|
69
|
+
|
|
70
|
+
## Production deployment
|
|
71
|
+
|
|
72
|
+
[`web/fly.toml`](https://github.com/reponomics/treestamp/blob/main/web/fly.toml) promotes the existing `treestamp-preview` Fly app to production at `https://treestamp.reponomics.org`. Future previews use the separate `reponomics-treestamp-preview` app. Leave `FLY_DEPLOY_ENABLED` unset until production deployment, session routing, and custom-domain checks succeed.
|
|
73
|
+
|
|
74
|
+
Keep the production app's GitHub API credentials in Fly secrets: either `GITHUB_TOKEN` or `GITHUB_APP_CLIENT_ID`, `GITHUB_APP_INSTALLATION_ID`, and `GITHUB_APP_PRIVATE_KEY`. To install or rotate the App credentials, `make fly-secrets` accepts `FLY_CONFIG=web/fly.toml` and reads the key from `GITHUB_APP_PRIVATE_KEY_FILE`. These service credentials are separate from the App used by Release Please.
|
|
75
|
+
|
|
76
|
+
Deploy a clean, reviewed commit on `main` manually for the initial setup:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
make fly-production-deploy FLY_DEPLOY_FLAGS=--remote-only
|
|
80
|
+
make fly-status FLY_CONFIG=web/fly.toml
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Production keeps two Machines running; the production target enables Fly's high-availability provisioning. Rolling deployment replaces at most one Machine at a time and checks `/healthz` during rollout. A build failure stops before rollout. A failed health check can halt rollout, but it is not a guarantee against application bugs or interrupted editing sessions. With `FLY_SESSION_ROUTING=true`, a browser-session cookie uses Fly's `FLY_MACHINE_ID` to route an active editing session to its cached snapshot. Sessions are temporary. A Machine failure, restart, or deployment can require a fresh URL load; the existing browser draft stays copyable and is replaced only after a successful load. Snapshots and quotas remain process-local, so changing the Machine count changes aggregate capacity and limits.
|
|
84
|
+
|
|
85
|
+
The custom domain is attached to `treestamp-preview` in Fly. These records are saved in Cloudflare for the `reponomics.org` zone, with proxying disabled. Fly reports the certificate as Ready, with active RSA and ECDSA certificates and successful validations:
|
|
86
|
+
|
|
87
|
+
| Type | Name | Value | Cloudflare proxy |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| CNAME | `treestamp` | `8w9r381.treestamp-preview.fly.dev` | DNS only; enable after the reviewed production deployment |
|
|
90
|
+
| TXT | `_fly-ownership.treestamp` | `app-8w9r381` | Not applicable |
|
|
91
|
+
| CNAME | `_acme-challenge.treestamp` | `treestamp.reponomics.org.8w9r381.flydns.net` | DNS only |
|
|
92
|
+
|
|
93
|
+
Recheck the records with `fly certs setup treestamp.reponomics.org --app treestamp-preview` if the app changes, and check certificate status with `fly certs check treestamp.reponomics.org --app treestamp-preview`. Keep proxying disabled until a reviewed `main` deployment includes Cloudflare peer validation; then enable it with Full (strict) TLS and complete the checks below. See [Fly's custom-domain instructions](https://docs.fly.io/networking/custom-domain) and [Cloudflare guidance](https://docs.fly.io/networking/understanding-cloudflare).
|
|
94
|
+
|
|
95
|
+
Keep HTML, API, and `/healthz` responses out of Cloudflare's edge cache; do not apply a Cache Everything rule to this hostname. The application accepts `CF-Connecting-IP` only when Fly's peer address belongs to the configured Cloudflare ranges. Worker subrequests and invalid visitor headers fall back conservatively to the peer for rate limiting. Review `CLOUDFLARE_PROXY_CIDRS` against [Cloudflare's published ranges](https://www.cloudflare.com/ips/) when maintaining this configuration, and keep the service behind Fly ingress.
|
|
96
|
+
|
|
97
|
+
Check `/healthz` through the custom domain and confirm that `build_commit` matches the deployed commit. Exercise loading, editing, copying, and draft preservation after failed loads in a browser, then run a public-repository smoke test with a matching local checkout:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
make fly-smoke SMOKE_URL=https://treestamp.reponomics.org \
|
|
101
|
+
SMOKE_REPO=OWNER/REPOSITORY SMOKE_CHECKOUT=/path/to/checkout
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Store an app-scoped Fly deploy token as `FLY_API_TOKEN` in the GitHub `production` environment, then enable `FLY_DEPLOY_ENABLED`. The separate [`deploy.yml`](https://github.com/reponomics/treestamp/blob/main/.github/workflows/deploy.yml) workflow deploys pushes to `main`, including merged PRs. Required PR checks are the merge gate; deployment does not wait for post-merge CI, and a failure in the separate CI workflow does not cancel it. Deployment never runs on PR events. No repository connection or auto-deploy setting in Fly is needed; leave Fly-native auto-deployment disabled to avoid duplicate rollouts. Cloudflare supplies DNS and proxying, while GitHub Actions invokes Fly to build and deploy the application.
|
|
105
|
+
|
|
106
|
+
Deployments are serialized without cancelling an active rollout. A queued run skips an obsolete commit if a newer commit is already on `main`. The build receives the pushed commit identifier, and the final health probe verifies that the public domain serves that commit. A failed final probe reports deployment failure; it does not automatically roll back. No separate release command or blue-green deployment is configured.
|
|
107
|
+
|
|
108
|
+
For rollback, prefer a reviewed revert on `main` through the same checks. An urgent manual rollback should use a previously verified image with current certificate stores; verify its build identifier and smoke test afterward, and reconcile `main` before the next automatic deployment.
|
|
109
|
+
|
|
110
|
+
## Preview deployment
|
|
111
|
+
|
|
112
|
+
The separate `web/fly.preview.toml` targets `reponomics-treestamp-preview`. Here, preview means a test deployment of the web editor. This app has been created but has not been deployed. Install its GitHub API credentials separately before its first deployment, using `make fly-secrets FLY_CONFIG=web/fly.preview.toml` if using a GitHub App. Preview deployment is manual and defaults to one Machine that may stop when idle:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
make web-image
|
|
116
|
+
make fly-preview-deploy
|
|
117
|
+
make fly-status FLY_CONFIG=web/fly.preview.toml
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Deploy a clean, committed candidate so that identifier describes the deployed files. The Docker build checks certificate stores; `make tls-check` performs the corresponding check in the local Python environment. Credentials belong in the host's secret store, not in Git.
|
|
121
|
+
|
|
122
|
+
After deploying, check `https://reponomics-treestamp-preview.fly.dev/healthz`, then run the public-repository smoke test with a matching local checkout:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
make fly-smoke SMOKE_URL=https://reponomics-treestamp-preview.fly.dev \
|
|
126
|
+
SMOKE_REPO=OWNER/REPOSITORY SMOKE_CHECKOUT=/path/to/checkout
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The repository must be public, and the local checkout must contain the commit used by the hosted session. Verify loading, editing, copying, and draft preservation after failed loads in a browser. For rollback, redeploy a previously verified image with up-to-date certificate stores and recheck the build identifier and smoke test.
|
|
130
|
+
|
|
131
|
+
## Product page
|
|
132
|
+
|
|
133
|
+
The product information page and selected demos belong in the separate `reponomics-web` repository, using its static assets and website deployment. They are not deployed by this repository. Add the product-page link after that page is published.
|
|
134
|
+
|
|
135
|
+
## External setup checklist
|
|
136
|
+
|
|
137
|
+
- [ ] Configure `main` protections, required checks, signed commits, and immutable releases.
|
|
138
|
+
- [ ] Install the release App on this repository and configure its variable and secret.
|
|
139
|
+
- [ ] Finish the pending Trusted Publisher and review `pypi` environment protections; complete public review and enable publication before merging the first release PR.
|
|
140
|
+
- [ ] Enable private vulnerability reporting when the repository becomes public.
|
|
141
|
+
- [ ] Deploy the production configuration to `treestamp-preview` and verify two-Machine session routing and draft recovery.
|
|
142
|
+
- [x] Save the three Cloudflare DNS records without proxying and validate the Fly certificate.
|
|
143
|
+
- [ ] After the reviewed production deployment, enable Cloudflare proxying with Full (strict) TLS, verify cache behavior, and complete domain and smoke checks.
|
|
144
|
+
- [ ] Configure the `production` environment and app-scoped deploy token, then enable deployment from `main`.
|
|
145
|
+
- [ ] Configure credentials and deploy the new `reponomics-treestamp-preview` app before using it for previews.
|
|
146
|
+
- [ ] Publish the product page and demos from `reponomics-web` when ready.
|
treestamp-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
.DEFAULT_GOAL := help
|
|
2
|
+
VENV ?= venv
|
|
3
|
+
PYTHON ?= python3
|
|
4
|
+
UV ?= uv
|
|
5
|
+
UV_VERSION := $(shell awk '$$1 == "uv" {print $$2}' .tool-versions)
|
|
6
|
+
NODE ?= node
|
|
7
|
+
FLY ?= fly
|
|
8
|
+
ACTIONLINT ?= actionlint
|
|
9
|
+
PY := $(VENV)/bin/python
|
|
10
|
+
PIP := $(VENV)/bin/pip
|
|
11
|
+
CLI := $(VENV)/bin/treestamp
|
|
12
|
+
|
|
13
|
+
.PHONY: help venv install format format-check lint typecheck test cov ci build distribution-check integration-check hook-check pre-commit-check tui clean web-install web-lint web-typecheck web-test web-cov web-ci web-fake web-serve web-image web-js-test web-browser-test fly-secrets fly-deploy fly-status fly-logs fly-smoke docs-check metadata-check
|
|
14
|
+
GITHUB_API_URL ?= http://127.0.0.1:8100
|
|
15
|
+
FAKE_PORT ?= 8100
|
|
16
|
+
FAKE_FLAGS ?=
|
|
17
|
+
FLY_CONFIG ?= web/fly.toml
|
|
18
|
+
FLY_PRODUCTION_CONFIG ?= web/fly.toml
|
|
19
|
+
FLY_PREVIEW_CONFIG ?= web/fly.preview.toml
|
|
20
|
+
FLY_DEPLOY_FLAGS ?=
|
|
21
|
+
FLY_HA ?= true
|
|
22
|
+
BUILD_COMMIT ?= $(shell git rev-parse HEAD)
|
|
23
|
+
SMOKE_REPO ?= reponomics/treestamp
|
|
24
|
+
SMOKE_URL ?= https://treestamp.reponomics.org
|
|
25
|
+
SMOKE_CHECKOUT ?= .
|
|
26
|
+
WEB_VENV := $(abspath $(VENV))
|
|
27
|
+
.PHONY: tls-check fly-production-deploy fly-preview-deploy workflow-check lock-check lock-update web-browser-install uv-check
|
|
28
|
+
export UV_PROJECT_ENVIRONMENT := $(abspath $(VENV))
|
|
29
|
+
help: ## Show available targets
|
|
30
|
+
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "%-22s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
|
31
|
+
uv-check: ## Check that uv matches the pinned development tool version
|
|
32
|
+
@test "$$($(UV) --version | awk '{print $$2}')" = "$(UV_VERSION)" || \
|
|
33
|
+
{ echo "Install uv $(UV_VERSION), as pinned in .tool-versions."; exit 1; }
|
|
34
|
+
venv: uv-check ## Create the virtual environment
|
|
35
|
+
@test -d "$(VENV)" || $(UV) venv --python $(PYTHON) $(VENV)
|
|
36
|
+
lock-check: uv-check ## Reject dependency declarations that differ from the lock
|
|
37
|
+
$(UV) lock --check
|
|
38
|
+
lock-update: uv-check ## Resolve intentional dependency upgrades into uv.lock
|
|
39
|
+
$(UV) lock --upgrade
|
|
40
|
+
install: uv-check ## Sync locked core, hosted, build, and test dependencies into venv
|
|
41
|
+
$(UV) sync --locked --all-packages --all-extras --no-install-workspace --no-build
|
|
42
|
+
$(UV) sync --locked --all-packages --all-extras --no-build-isolation
|
|
43
|
+
format: ## Format Python code
|
|
44
|
+
$(VENV)/bin/ruff format .
|
|
45
|
+
format-check: ## Check Python formatting
|
|
46
|
+
$(VENV)/bin/ruff format --check .
|
|
47
|
+
lint: ## Lint Python code
|
|
48
|
+
$(VENV)/bin/ruff check .
|
|
49
|
+
typecheck: ## Check strict Python types
|
|
50
|
+
$(VENV)/bin/mypy
|
|
51
|
+
test: ## Run the regression suite, including headless TUI journeys
|
|
52
|
+
$(PY) -m pytest
|
|
53
|
+
cov: ## Run tests with branch coverage
|
|
54
|
+
$(PY) -m pytest --cov --cov-report=term-missing --cov-fail-under=90
|
|
55
|
+
ci: format-check lint typecheck cov ## Run all local quality gates
|
|
56
|
+
docs-check: ## Check retained local documentation links and packaged README URLs
|
|
57
|
+
$(PY) scripts/check_docs.py
|
|
58
|
+
metadata-check: ## Check built metadata, README rendering, package contents, and hashes
|
|
59
|
+
$(PY) -m twine check --strict dist/*.whl dist/*.tar.gz
|
|
60
|
+
$(PY) scripts/release_check.py
|
|
61
|
+
build: install ## Sync locked build tools and build wheel and sdist without isolation
|
|
62
|
+
$(PY) -m build --no-isolation
|
|
63
|
+
distribution-check: ## Verify clean wheel/sdist headless commands and editor workflows
|
|
64
|
+
$(PY) scripts/distribution_check.py
|
|
65
|
+
integration-check: ## Verify the repository's treestamped README
|
|
66
|
+
$(CLI) check README.md
|
|
67
|
+
workflow-check: ## Validate GitHub Actions workflows (requires actionlint)
|
|
68
|
+
$(ACTIONLINT)
|
|
69
|
+
hook-check: ## Exercise installed repository hooks at HEAD
|
|
70
|
+
$(PY) scripts/hook_check.py
|
|
71
|
+
pre-commit-check: ## Run the repository development hooks
|
|
72
|
+
$(VENV)/bin/pre-commit run --all-files
|
|
73
|
+
tui: ## Open the editor
|
|
74
|
+
$(CLI)
|
|
75
|
+
clean: ## Remove generated build and coverage artifacts
|
|
76
|
+
rm -rf dist/ build/ .coverage htmlcov/
|
|
77
|
+
web-install: install ## Sync the locked workspace, including hosted development dependencies
|
|
78
|
+
web-browser-install: install ## Sync the locked browser test dependencies (install Chromium separately)
|
|
79
|
+
web-lint: ## Lint the web editor project
|
|
80
|
+
$(VENV)/bin/ruff check web && $(VENV)/bin/ruff format --check web
|
|
81
|
+
web-typecheck: ## Check strict types for the web editor project
|
|
82
|
+
cd web && $(WEB_VENV)/bin/mypy
|
|
83
|
+
web-test: ## Run the web editor tests
|
|
84
|
+
cd web && $(WEB_VENV)/bin/python -m pytest
|
|
85
|
+
web-cov: ## Run the web editor tests with branch coverage
|
|
86
|
+
cd web && $(WEB_VENV)/bin/python -m pytest --cov --cov-report=term-missing --cov-fail-under=90
|
|
87
|
+
web-ci: web-lint web-typecheck web-cov web-js-test ## Run all web editor quality gates
|
|
88
|
+
web-js-test: ## Run the page's pure-logic tests with Node's test runner
|
|
89
|
+
$(NODE) --test web/tests/js/*.test.mjs
|
|
90
|
+
web-browser-test: ## Drive the page in a browser (needs make install and a browser)
|
|
91
|
+
cd web && $(WEB_VENV)/bin/python -m pytest -q -o addopts="" -m browser tests/browser $(BROWSER_FLAGS)
|
|
92
|
+
web-fake: ## Serve this checkout as a fake GitHub API on FAKE_PORT (FAKE_FLAGS adds failure modes)
|
|
93
|
+
$(PY) web/scripts/fake_github.py --repo . --port $(FAKE_PORT) $(FAKE_FLAGS)
|
|
94
|
+
web-serve: ## Run the web editor locally against GITHUB_API_URL
|
|
95
|
+
GITHUB_API_URL=$(GITHUB_API_URL) GITHUB_RATE_RESERVE=0 $(VENV)/bin/uvicorn --factory treestamp_web.app:create_app --host 127.0.0.1 --port 8000 --reload
|
|
96
|
+
tls-check: ## Check Python TLS package versions and certificate trust stores
|
|
97
|
+
$(PY) web/scripts/check_tls.py
|
|
98
|
+
web-image: ## Build the web editor container image
|
|
99
|
+
docker build -f web/Dockerfile --build-arg BUILD_COMMIT="$(BUILD_COMMIT)" -t treestamp-web:local .
|
|
100
|
+
fly-secrets: ## Set the GitHub App secrets on Fly (GITHUB_APP_CLIENT_ID, GITHUB_APP_INSTALLATION_ID, GITHUB_APP_PRIVATE_KEY_FILE)
|
|
101
|
+
@test -n "$(GITHUB_APP_CLIENT_ID)" -a -n "$(GITHUB_APP_INSTALLATION_ID)" -a -n "$(GITHUB_APP_PRIVATE_KEY_FILE)" || \
|
|
102
|
+
{ echo "usage: make fly-secrets GITHUB_APP_CLIENT_ID=... GITHUB_APP_INSTALLATION_ID=... GITHUB_APP_PRIVATE_KEY_FILE=path.pem"; exit 2; }
|
|
103
|
+
$(FLY) secrets set --config $(FLY_CONFIG) GITHUB_APP_CLIENT_ID="$(GITHUB_APP_CLIENT_ID)" \
|
|
104
|
+
GITHUB_APP_INSTALLATION_ID="$(GITHUB_APP_INSTALLATION_ID)" GITHUB_APP_PRIVATE_KEY="$$(cat $(GITHUB_APP_PRIVATE_KEY_FILE))"
|
|
105
|
+
fly-deploy: ## Build and deploy the app selected by FLY_CONFIG (defaults to production)
|
|
106
|
+
$(FLY) deploy --config $(FLY_CONFIG) --dockerfile web/Dockerfile --build-arg BUILD_COMMIT="$(BUILD_COMMIT)" --ha=$(FLY_HA) $(FLY_DEPLOY_FLAGS)
|
|
107
|
+
fly-production-deploy: ## Build and deploy the production editor
|
|
108
|
+
$(MAKE) fly-deploy FLY_CONFIG=$(FLY_PRODUCTION_CONFIG) FLY_HA=true
|
|
109
|
+
fly-preview-deploy: ## Build and deploy the separate test editor
|
|
110
|
+
$(MAKE) fly-deploy FLY_CONFIG=$(FLY_PREVIEW_CONFIG) FLY_HA=false
|
|
111
|
+
fly-status: ## Show the app's machines and health
|
|
112
|
+
$(FLY) status --config $(FLY_CONFIG)
|
|
113
|
+
fly-logs: ## Stream the service's log lines from Fly
|
|
114
|
+
$(FLY) logs --config $(FLY_CONFIG)
|
|
115
|
+
fly-smoke: ## Run the smoke script against the deployed app (SMOKE_REPO, SMOKE_CHECKOUT)
|
|
116
|
+
$(PY) web/scripts/smoke.py $(SMOKE_URL) --repo $(SMOKE_REPO) --checkout $(SMOKE_CHECKOUT)
|