mdcompose 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.
- mdcompose-0.1.0/.gitattributes +11 -0
- mdcompose-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +51 -0
- mdcompose-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- mdcompose-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +28 -0
- mdcompose-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +11 -0
- mdcompose-0.1.0/.github/workflows/bump.yml +97 -0
- mdcompose-0.1.0/.github/workflows/ci.yml +318 -0
- mdcompose-0.1.0/.github/workflows/codeql.yml +39 -0
- mdcompose-0.1.0/.github/workflows/dependency-review.yml +45 -0
- mdcompose-0.1.0/.github/workflows/mutation.yml +55 -0
- mdcompose-0.1.0/.github/workflows/python-eol.yml +74 -0
- mdcompose-0.1.0/.github/workflows/release.yml +181 -0
- mdcompose-0.1.0/.github/workflows/scorecard.yml +47 -0
- mdcompose-0.1.0/.github/workflows/supply-chain.yml +151 -0
- mdcompose-0.1.0/.github/zizmor.yml +11 -0
- mdcompose-0.1.0/.gitignore +58 -0
- mdcompose-0.1.0/.pre-commit-config.yaml +78 -0
- mdcompose-0.1.0/CHANGELOG.md +20 -0
- mdcompose-0.1.0/CODE_OF_CONDUCT.md +85 -0
- mdcompose-0.1.0/CONTRIBUTING.md +107 -0
- mdcompose-0.1.0/LICENSE +21 -0
- mdcompose-0.1.0/PKG-INFO +234 -0
- mdcompose-0.1.0/README.md +211 -0
- mdcompose-0.1.0/SECURITY.md +27 -0
- mdcompose-0.1.0/TODO.md +536 -0
- mdcompose-0.1.0/docs/benchmarks.md +46 -0
- mdcompose-0.1.0/docs/commit-and-release-tooling.md +196 -0
- mdcompose-0.1.0/docs/concepts.md +154 -0
- mdcompose-0.1.0/docs/core-contract.md +914 -0
- mdcompose-0.1.0/docs/hardening-review.md +53 -0
- mdcompose-0.1.0/docs/optional-tooling.md +79 -0
- mdcompose-0.1.0/docs/publishing.md +136 -0
- mdcompose-0.1.0/docs/scorecard.md +113 -0
- mdcompose-0.1.0/docs/versioning-explained.md +173 -0
- mdcompose-0.1.0/mdcompose/__init__.py +0 -0
- mdcompose-0.1.0/mdcompose/cli.py +219 -0
- mdcompose-0.1.0/mdcompose/commands/__init__.py +0 -0
- mdcompose-0.1.0/mdcompose/commands/config_cmd.py +172 -0
- mdcompose-0.1.0/mdcompose/commands/convert_cmd.py +299 -0
- mdcompose-0.1.0/mdcompose/commands/eject_cmd.py +180 -0
- mdcompose-0.1.0/mdcompose/commands/import_cmd.py +351 -0
- mdcompose-0.1.0/mdcompose/commands/init_cmd.py +668 -0
- mdcompose-0.1.0/mdcompose/commands/snippet_cmd.py +312 -0
- mdcompose-0.1.0/mdcompose/commands/target_cmd.py +134 -0
- mdcompose-0.1.0/mdcompose/core/__init__.py +0 -0
- mdcompose-0.1.0/mdcompose/core/composition.py +96 -0
- mdcompose-0.1.0/mdcompose/core/config.py +454 -0
- mdcompose-0.1.0/mdcompose/core/convert_ops.py +193 -0
- mdcompose-0.1.0/mdcompose/core/eject.py +168 -0
- mdcompose-0.1.0/mdcompose/core/exit_codes.py +28 -0
- mdcompose-0.1.0/mdcompose/core/files.py +194 -0
- mdcompose-0.1.0/mdcompose/core/import_ops.py +175 -0
- mdcompose-0.1.0/mdcompose/core/init_ops.py +436 -0
- mdcompose-0.1.0/mdcompose/core/library_ops.py +199 -0
- mdcompose-0.1.0/mdcompose/core/managed_block.py +380 -0
- mdcompose-0.1.0/mdcompose/core/manifest.py +404 -0
- mdcompose-0.1.0/mdcompose/core/output.py +208 -0
- mdcompose-0.1.0/mdcompose/core/platform.py +368 -0
- mdcompose-0.1.0/mdcompose/core/report.py +278 -0
- mdcompose-0.1.0/mdcompose/core/sections.py +215 -0
- mdcompose-0.1.0/mdcompose/core/snippets.py +359 -0
- mdcompose-0.1.0/mdcompose/core/targets.py +200 -0
- mdcompose-0.1.0/mdcompose/prompts.py +102 -0
- mdcompose-0.1.0/mdcompose/version.py +31 -0
- mdcompose-0.1.0/pyproject.toml +192 -0
- mdcompose-0.1.0/renovate.json +39 -0
- mdcompose-0.1.0/scripts/check_ascii.py +89 -0
- mdcompose-0.1.0/scripts/check_licenses.py +156 -0
- mdcompose-0.1.0/tests/benchmarks/conftest.py +100 -0
- mdcompose-0.1.0/tests/benchmarks/test_core_ops.py +87 -0
- mdcompose-0.1.0/tests/conftest.py +91 -0
- mdcompose-0.1.0/tests/fixtures/plain.bom.md +4 -0
- mdcompose-0.1.0/tests/fixtures/plain.crlf.md +4 -0
- mdcompose-0.1.0/tests/fixtures/plain.lf.md +4 -0
- mdcompose-0.1.0/tests/fixtures/prose.cp1252.md +3 -0
- mdcompose-0.1.0/tests/test_cli.py +377 -0
- mdcompose-0.1.0/tests/test_config.py +366 -0
- mdcompose-0.1.0/tests/test_config_cmd.py +382 -0
- mdcompose-0.1.0/tests/test_convert.py +484 -0
- mdcompose-0.1.0/tests/test_doctor_drift.py +224 -0
- mdcompose-0.1.0/tests/test_e2e_import_convert.py +181 -0
- mdcompose-0.1.0/tests/test_eject.py +435 -0
- mdcompose-0.1.0/tests/test_files.py +236 -0
- mdcompose-0.1.0/tests/test_fuzz.py +82 -0
- mdcompose-0.1.0/tests/test_import_cmd.py +610 -0
- mdcompose-0.1.0/tests/test_import_ops.py +265 -0
- mdcompose-0.1.0/tests/test_init.py +657 -0
- mdcompose-0.1.0/tests/test_init_ops.py +299 -0
- mdcompose-0.1.0/tests/test_layering.py +82 -0
- mdcompose-0.1.0/tests/test_managed_block.py +306 -0
- mdcompose-0.1.0/tests/test_manifest.py +356 -0
- mdcompose-0.1.0/tests/test_onedrive.py +137 -0
- mdcompose-0.1.0/tests/test_output.py +192 -0
- mdcompose-0.1.0/tests/test_platform.py +220 -0
- mdcompose-0.1.0/tests/test_prompts.py +93 -0
- mdcompose-0.1.0/tests/test_report.py +213 -0
- mdcompose-0.1.0/tests/test_sections.py +230 -0
- mdcompose-0.1.0/tests/test_snippet_commands.py +493 -0
- mdcompose-0.1.0/tests/test_snippets.py +349 -0
- mdcompose-0.1.0/tests/test_targets.py +562 -0
- mdcompose-0.1.0/uv.lock +1773 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Normalize every text file to LF in the repository and in working copies.
|
|
2
|
+
# This project's whole design turns on line-ending correctness, so it must not
|
|
3
|
+
# depend on a contributor's core.autocrlf setting.
|
|
4
|
+
* text=auto eol=lf
|
|
5
|
+
|
|
6
|
+
# Test fixtures that deliberately carry a specific encoding or line ending must
|
|
7
|
+
# survive checkout byte for byte, otherwise the tests that prove normalization
|
|
8
|
+
# works would be testing normalized inputs.
|
|
9
|
+
tests/fixtures/**/*.crlf.md -text
|
|
10
|
+
tests/fixtures/**/*.bom.md -text
|
|
11
|
+
tests/fixtures/**/*.cp1252.md -text
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something mdcompose does wrong
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
A failing test case is the fastest possible bug report. If you can write
|
|
9
|
+
one, open a pull request instead of an issue.
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: what
|
|
12
|
+
attributes:
|
|
13
|
+
label: What happened
|
|
14
|
+
description: The command you ran and what mdcompose did.
|
|
15
|
+
render: text
|
|
16
|
+
validations:
|
|
17
|
+
required: true
|
|
18
|
+
- type: textarea
|
|
19
|
+
id: expected
|
|
20
|
+
attributes:
|
|
21
|
+
label: What you expected instead
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: repro
|
|
26
|
+
attributes:
|
|
27
|
+
label: Smallest reproduction
|
|
28
|
+
description: The fewest steps, files, or snippets that still show the problem.
|
|
29
|
+
validations:
|
|
30
|
+
required: true
|
|
31
|
+
- type: textarea
|
|
32
|
+
id: doctor
|
|
33
|
+
attributes:
|
|
34
|
+
label: Output of mdcompose doctor
|
|
35
|
+
render: text
|
|
36
|
+
validations:
|
|
37
|
+
required: true
|
|
38
|
+
- type: input
|
|
39
|
+
id: version
|
|
40
|
+
attributes:
|
|
41
|
+
label: mdcompose version and how you installed it
|
|
42
|
+
placeholder: 0.1.0, pipx
|
|
43
|
+
validations:
|
|
44
|
+
required: true
|
|
45
|
+
- type: input
|
|
46
|
+
id: platform
|
|
47
|
+
attributes:
|
|
48
|
+
label: Operating system and Python version
|
|
49
|
+
placeholder: Windows 11, Python 3.12
|
|
50
|
+
validations:
|
|
51
|
+
required: true
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest a capability or a change
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Read the "Deliberate non-goals" section of AGENTS.md first: a few things
|
|
9
|
+
are excluded on purpose, with the reasoning.
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: problem
|
|
12
|
+
attributes:
|
|
13
|
+
label: The problem
|
|
14
|
+
description: What are you trying to do that mdcompose makes hard or impossible?
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: textarea
|
|
18
|
+
id: idea
|
|
19
|
+
attributes:
|
|
20
|
+
label: The change you have in mind
|
|
21
|
+
validations:
|
|
22
|
+
required: true
|
|
23
|
+
- type: textarea
|
|
24
|
+
id: alternatives
|
|
25
|
+
attributes:
|
|
26
|
+
label: Alternatives you considered
|
|
27
|
+
validations:
|
|
28
|
+
required: false
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- What does this change, and why.
|
|
2
|
+
Link the issue if there is one. -->
|
|
3
|
+
|
|
4
|
+
## Checklist
|
|
5
|
+
|
|
6
|
+
- [ ] `uv run pytest` passes, and so does `uv sync --locked --python 3.11 && uv run --no-sync pytest`
|
|
7
|
+
- [ ] `uv run ruff check .` is clean
|
|
8
|
+
- [ ] `uv run pre-commit run --all-files` is clean
|
|
9
|
+
- [ ] Every commit is a Conventional Commit (`uv run cz check --rev-range origin/main..HEAD`)
|
|
10
|
+
- [ ] Plain ASCII in everything the change adds
|
|
11
|
+
- [ ] Behavior change: [docs/core-contract.md](../docs/core-contract.md) updated in the same commit
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
name: bump
|
|
2
|
+
|
|
3
|
+
# The release button. Run this from the Actions tab and pick a channel:
|
|
4
|
+
#
|
|
5
|
+
# stable -> a stable release (0.3.1 -> 0.3.2 / 0.4.0 / 1.0.0, from the commits)
|
|
6
|
+
# alpha -> 0.4.0a1, then a2, ... feature-incomplete, breakage expected
|
|
7
|
+
# beta -> 0.4.0b1, ... feature-complete, bug hunting
|
|
8
|
+
# rc -> 0.4.0rc1, ... believed shippable
|
|
9
|
+
#
|
|
10
|
+
# Run it again with "stable" to promote the current pre-release to the final
|
|
11
|
+
# version. cz bump derives the number from the Conventional Commits since
|
|
12
|
+
# the last tag, rewrites [project].version and CHANGELOG.md, commits, and pushes
|
|
13
|
+
# the tag that release.yml publishes on. You never type a version.
|
|
14
|
+
#
|
|
15
|
+
# Requires a repository secret RELEASE_TOKEN: a fine-grained PAT (or GitHub App
|
|
16
|
+
# token) with contents:write. The built-in GITHUB_TOKEN cannot be used here,
|
|
17
|
+
# because a tag it pushes does not trigger release.yml and its push is blocked by
|
|
18
|
+
# branch protection. See TODO.md, "The bump.yml PAT wrinkle".
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
inputs:
|
|
23
|
+
prerelease:
|
|
24
|
+
description: Release channel
|
|
25
|
+
type: choice
|
|
26
|
+
default: stable
|
|
27
|
+
options:
|
|
28
|
+
- stable
|
|
29
|
+
- alpha
|
|
30
|
+
- beta
|
|
31
|
+
- rc
|
|
32
|
+
|
|
33
|
+
# Never let two bumps run at once.
|
|
34
|
+
concurrency:
|
|
35
|
+
group: bump
|
|
36
|
+
cancel-in-progress: false
|
|
37
|
+
|
|
38
|
+
permissions:
|
|
39
|
+
contents: read
|
|
40
|
+
|
|
41
|
+
jobs:
|
|
42
|
+
bump:
|
|
43
|
+
name: bump version, changelog and tag
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
timeout-minutes: 10
|
|
46
|
+
# A release comes only off the mainline.
|
|
47
|
+
if: github.ref == 'refs/heads/main'
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
50
|
+
with:
|
|
51
|
+
# Full history so cz bump can read every commit since the last tag,
|
|
52
|
+
# and the PAT so the pushed commit and tag are not blocked by branch
|
|
53
|
+
# protection and the tag triggers release.yml.
|
|
54
|
+
fetch-depth: 0
|
|
55
|
+
token: ${{ secrets.RELEASE_TOKEN }}
|
|
56
|
+
persist-credentials: true
|
|
57
|
+
|
|
58
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
59
|
+
with:
|
|
60
|
+
enable-cache: true
|
|
61
|
+
cache-dependency-glob: uv.lock
|
|
62
|
+
|
|
63
|
+
- name: Sync the locked environment
|
|
64
|
+
run: uv sync --locked
|
|
65
|
+
|
|
66
|
+
- name: Identify the committer
|
|
67
|
+
run: |
|
|
68
|
+
git config user.name "github-actions[bot]"
|
|
69
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
70
|
+
|
|
71
|
+
- name: Bump
|
|
72
|
+
env:
|
|
73
|
+
PRERELEASE: ${{ inputs.prerelease }}
|
|
74
|
+
run: |
|
|
75
|
+
if [ "$PRERELEASE" = "stable" ]; then
|
|
76
|
+
uv run --no-sync cz bump --yes --changelog
|
|
77
|
+
else
|
|
78
|
+
uv run --no-sync cz bump --yes --changelog --prerelease "$PRERELEASE"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
- name: Fold the refreshed lockfile into the bump commit
|
|
82
|
+
# cz bump rewrites [project].version but not uv.lock, which pins the
|
|
83
|
+
# project's own version. Left stale, every `uv sync --locked` downstream
|
|
84
|
+
# (CI, the release build) fails with "the lockfile needs to be updated".
|
|
85
|
+
# The bump commit and tag are still local here, so amend rather than add
|
|
86
|
+
# a follow-up commit, and re-point the annotated tag at the new commit.
|
|
87
|
+
run: |
|
|
88
|
+
uv lock
|
|
89
|
+
if ! git diff --quiet -- uv.lock; then
|
|
90
|
+
tag="$(git describe --tags --exact-match HEAD)"
|
|
91
|
+
git add uv.lock
|
|
92
|
+
git commit --amend --no-edit
|
|
93
|
+
git tag -f -a "$tag" -m "$tag"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
- name: Push the bump commit and the tag
|
|
97
|
+
run: git push --follow-tags origin HEAD:main
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
# The default token is read-only. No job here writes to the repository.
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
env:
|
|
18
|
+
# Colour is decided by TTY detection, and a runner is not a TTY. Setting this
|
|
19
|
+
# anyway keeps log output identical to what a piped run produces locally.
|
|
20
|
+
NO_COLOR: "1"
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
test:
|
|
24
|
+
# The full matrix runs on all three operating systems rather than only on
|
|
25
|
+
# Linux. Cross-platform path and encoding handling is what this project
|
|
26
|
+
# claims to do better than its neighbours, so it is the last thing that
|
|
27
|
+
# should be tested on one platform and assumed elsewhere.
|
|
28
|
+
name: test ${{ matrix.python }} on ${{ matrix.os }}
|
|
29
|
+
runs-on: ${{ matrix.os }}
|
|
30
|
+
timeout-minutes: 20
|
|
31
|
+
strategy:
|
|
32
|
+
fail-fast: false
|
|
33
|
+
matrix:
|
|
34
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
35
|
+
python: ["3.11", "3.12", "3.13", "3.14"]
|
|
36
|
+
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
39
|
+
with:
|
|
40
|
+
persist-credentials: false
|
|
41
|
+
|
|
42
|
+
- name: Install uv
|
|
43
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
44
|
+
with:
|
|
45
|
+
enable-cache: true
|
|
46
|
+
cache-dependency-glob: uv.lock
|
|
47
|
+
|
|
48
|
+
- name: Sync the locked environment
|
|
49
|
+
# --locked fails if uv.lock is out of date with pyproject.toml, so a
|
|
50
|
+
# dependency change that was not locked cannot pass CI unnoticed.
|
|
51
|
+
run: uv sync --locked --python ${{ matrix.python }}
|
|
52
|
+
|
|
53
|
+
- name: Run the test suite
|
|
54
|
+
run: uv run --no-sync pytest
|
|
55
|
+
|
|
56
|
+
- name: Smoke test the doctor command
|
|
57
|
+
# The suite covers doctor thoroughly, but only through the in-process
|
|
58
|
+
# entry point. This runs the installed console script the way a user
|
|
59
|
+
# does, which is what would have caught the Path.exists divergence
|
|
60
|
+
# between Python versions.
|
|
61
|
+
shell: bash
|
|
62
|
+
run: |
|
|
63
|
+
uv run --no-sync mdcompose --version
|
|
64
|
+
uv run --no-sync mdcompose doctor
|
|
65
|
+
uv run --no-sync mdcompose doctor --json > report.json
|
|
66
|
+
uv run --no-sync python -c "import json,pathlib; d=json.loads(pathlib.Path('report.json').read_text()); assert d['os'] in {'windows','macos','linux'}, d; assert d['paths'], d; print('doctor json ok on', d['os'])"
|
|
67
|
+
|
|
68
|
+
- name: Verify quiet mode reduces doctor to its exit code
|
|
69
|
+
shell: bash
|
|
70
|
+
run: |
|
|
71
|
+
output=$(uv run --no-sync mdcompose doctor --quiet)
|
|
72
|
+
if [ -n "$output" ]; then
|
|
73
|
+
echo "expected no stdout in quiet mode, got: $output"
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
lint:
|
|
78
|
+
name: lint
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
timeout-minutes: 20
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
83
|
+
with:
|
|
84
|
+
persist-credentials: false
|
|
85
|
+
|
|
86
|
+
- name: Install uv
|
|
87
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
88
|
+
with:
|
|
89
|
+
enable-cache: true
|
|
90
|
+
cache-dependency-glob: uv.lock
|
|
91
|
+
|
|
92
|
+
- name: Sync the locked environment
|
|
93
|
+
run: uv sync --locked
|
|
94
|
+
|
|
95
|
+
- name: Run ruff
|
|
96
|
+
run: uv run --no-sync ruff check .
|
|
97
|
+
|
|
98
|
+
- name: Run mypy
|
|
99
|
+
# Strict type checking over mdcompose/. Config in [tool.mypy].
|
|
100
|
+
run: uv run --no-sync mypy
|
|
101
|
+
|
|
102
|
+
- name: Check dependency hygiene
|
|
103
|
+
# Unused, missing, or misplaced dependencies, inferred from the imports.
|
|
104
|
+
run: uv run --no-sync deptry .
|
|
105
|
+
|
|
106
|
+
coverage:
|
|
107
|
+
# A single-platform coverage floor. The full matrix in `test` proves the
|
|
108
|
+
# suite passes everywhere; this one run enforces `fail_under` from
|
|
109
|
+
# [tool.coverage.report] so coverage cannot slide unnoticed between
|
|
110
|
+
# ratchets. It is not the matrix's job because branch coverage differs by
|
|
111
|
+
# platform (winreg, /mnt/) and the floor is set against one of them.
|
|
112
|
+
name: coverage floor
|
|
113
|
+
runs-on: ubuntu-latest
|
|
114
|
+
timeout-minutes: 20
|
|
115
|
+
steps:
|
|
116
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
117
|
+
with:
|
|
118
|
+
persist-credentials: false
|
|
119
|
+
|
|
120
|
+
- name: Install uv
|
|
121
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
122
|
+
with:
|
|
123
|
+
enable-cache: true
|
|
124
|
+
cache-dependency-glob: uv.lock
|
|
125
|
+
|
|
126
|
+
- name: Sync the locked environment
|
|
127
|
+
run: uv sync --locked
|
|
128
|
+
|
|
129
|
+
- name: Run the suite under coverage
|
|
130
|
+
run: uv run --no-sync pytest --cov=mdcompose --cov-report=term-missing
|
|
131
|
+
|
|
132
|
+
ascii:
|
|
133
|
+
# This project requires every character it generates, and every character in
|
|
134
|
+
# its own documents, to be ASCII. The reason is not style: a Windows console
|
|
135
|
+
# on a cp1252 or cp437 code page cannot encode an emoji or an em dash, so
|
|
136
|
+
# emitting one raises an encoding error on a primary target platform. A rule
|
|
137
|
+
# that is only enforced by review stops being enforced. The same script runs
|
|
138
|
+
# as a pre-commit hook, so a violation fails locally before it is pushed.
|
|
139
|
+
name: ascii convention
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
timeout-minutes: 20
|
|
142
|
+
steps:
|
|
143
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
144
|
+
with:
|
|
145
|
+
persist-credentials: false
|
|
146
|
+
|
|
147
|
+
- name: Check that sources and documents are plain ASCII
|
|
148
|
+
run: python3 scripts/check_ascii.py
|
|
149
|
+
|
|
150
|
+
build:
|
|
151
|
+
name: build and install
|
|
152
|
+
runs-on: ubuntu-latest
|
|
153
|
+
timeout-minutes: 20
|
|
154
|
+
steps:
|
|
155
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
156
|
+
with:
|
|
157
|
+
persist-credentials: false
|
|
158
|
+
|
|
159
|
+
- name: Install uv
|
|
160
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
161
|
+
with:
|
|
162
|
+
enable-cache: true
|
|
163
|
+
cache-dependency-glob: uv.lock
|
|
164
|
+
|
|
165
|
+
- name: Build the wheel and sdist
|
|
166
|
+
run: uv build
|
|
167
|
+
|
|
168
|
+
- name: Install the built wheel into a clean environment and run it
|
|
169
|
+
# Proves the package works as published, not only as an editable
|
|
170
|
+
# checkout. A missing file in the wheel would pass every other job.
|
|
171
|
+
shell: bash
|
|
172
|
+
run: |
|
|
173
|
+
uv venv --python 3.11 /tmp/fresh
|
|
174
|
+
uv pip install --python /tmp/fresh/bin/python dist/*.whl
|
|
175
|
+
/tmp/fresh/bin/mdcompose --version
|
|
176
|
+
/tmp/fresh/bin/mdcompose doctor
|
|
177
|
+
|
|
178
|
+
workflows:
|
|
179
|
+
# The two workflow-file linters that pre-commit.ci cannot run in its sandbox
|
|
180
|
+
# (actionlint fetches a binary, zizmor ships as a compiled tool), run here
|
|
181
|
+
# from pinned releases instead. Everything else in .pre-commit-config.yaml
|
|
182
|
+
# is left to pre-commit.ci; those two are listed in its `ci.skip`.
|
|
183
|
+
name: workflow lint
|
|
184
|
+
runs-on: ubuntu-latest
|
|
185
|
+
timeout-minutes: 10
|
|
186
|
+
steps:
|
|
187
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
188
|
+
with:
|
|
189
|
+
persist-credentials: false
|
|
190
|
+
|
|
191
|
+
- name: Install uv
|
|
192
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
193
|
+
with:
|
|
194
|
+
enable-cache: true
|
|
195
|
+
cache-dependency-glob: uv.lock
|
|
196
|
+
|
|
197
|
+
- name: Install actionlint
|
|
198
|
+
env:
|
|
199
|
+
# renovate: datasource=github-releases depName=rhysd/actionlint
|
|
200
|
+
ACTIONLINT_VERSION: 1.7.12
|
|
201
|
+
run: |
|
|
202
|
+
curl -sSfL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
|
|
203
|
+
| sudo tar -xz -C /usr/local/bin actionlint
|
|
204
|
+
|
|
205
|
+
- name: actionlint
|
|
206
|
+
run: actionlint -color
|
|
207
|
+
|
|
208
|
+
- name: zizmor
|
|
209
|
+
env:
|
|
210
|
+
# renovate: datasource=pypi depName=zizmor
|
|
211
|
+
ZIZMOR_VERSION: 1.30.1
|
|
212
|
+
run: uv tool run "zizmor==${ZIZMOR_VERSION}" .
|
|
213
|
+
|
|
214
|
+
- name: check-github-workflows
|
|
215
|
+
run: >-
|
|
216
|
+
uv tool run --from check-jsonschema
|
|
217
|
+
check-jsonschema --builtin-schema github-workflows .github/workflows/*.yml
|
|
218
|
+
|
|
219
|
+
commits:
|
|
220
|
+
# Every commit on a pull request branch must be a Conventional Commit, so
|
|
221
|
+
# the release-time version bump derived from those commits is trustworthy.
|
|
222
|
+
name: conventional commits
|
|
223
|
+
if: github.event_name == 'pull_request'
|
|
224
|
+
runs-on: ubuntu-latest
|
|
225
|
+
timeout-minutes: 20
|
|
226
|
+
steps:
|
|
227
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
228
|
+
with:
|
|
229
|
+
fetch-depth: 0
|
|
230
|
+
persist-credentials: false
|
|
231
|
+
|
|
232
|
+
- name: Install uv
|
|
233
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
234
|
+
with:
|
|
235
|
+
enable-cache: true
|
|
236
|
+
cache-dependency-glob: uv.lock
|
|
237
|
+
|
|
238
|
+
- name: Sync the locked environment
|
|
239
|
+
run: uv sync --locked
|
|
240
|
+
|
|
241
|
+
- name: Check every commit in the pull request
|
|
242
|
+
env:
|
|
243
|
+
RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
|
|
244
|
+
run: uv run --no-sync cz check --rev-range "$RANGE"
|
|
245
|
+
|
|
246
|
+
gitleaks:
|
|
247
|
+
# The pre-commit gitleaks hook only sees a working tree. This scans the full
|
|
248
|
+
# history, so a secret committed and then removed in a later commit is still
|
|
249
|
+
# caught. The gitleaks binary is run directly rather than through
|
|
250
|
+
# gitleaks-action: the action diffs only the pushed range, which has no
|
|
251
|
+
# valid base on the first push of a rewritten history.
|
|
252
|
+
name: secret scan
|
|
253
|
+
runs-on: ubuntu-latest
|
|
254
|
+
timeout-minutes: 20
|
|
255
|
+
steps:
|
|
256
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
257
|
+
with:
|
|
258
|
+
fetch-depth: 0
|
|
259
|
+
persist-credentials: false
|
|
260
|
+
|
|
261
|
+
- name: Install gitleaks
|
|
262
|
+
env:
|
|
263
|
+
# renovate: datasource=github-releases depName=gitleaks/gitleaks
|
|
264
|
+
GITLEAKS_VERSION: 8.24.3
|
|
265
|
+
run: |
|
|
266
|
+
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
|
267
|
+
| sudo tar -xz -C /usr/local/bin gitleaks
|
|
268
|
+
|
|
269
|
+
- name: Scan the full history
|
|
270
|
+
run: gitleaks git --redact --exit-code 1 --verbose .
|
|
271
|
+
|
|
272
|
+
all-green:
|
|
273
|
+
# The single check branch protection requires: it passes only if every job
|
|
274
|
+
# it needs passed or was legitimately skipped (commits is skipped outside a
|
|
275
|
+
# pull request). Adding a job above without adding it here is the one way to
|
|
276
|
+
# weaken the gate, so the list is deliberately explicit.
|
|
277
|
+
name: all green
|
|
278
|
+
if: always()
|
|
279
|
+
needs: [test, lint, coverage, ascii, build, workflows, commits, gitleaks]
|
|
280
|
+
runs-on: ubuntu-latest
|
|
281
|
+
timeout-minutes: 20
|
|
282
|
+
steps:
|
|
283
|
+
- name: Require every needed job to have passed
|
|
284
|
+
env:
|
|
285
|
+
RESULTS: ${{ join(needs.*.result, ' ') }}
|
|
286
|
+
run: |
|
|
287
|
+
echo "job results: $RESULTS"
|
|
288
|
+
for result in $RESULTS; do
|
|
289
|
+
case "$result" in
|
|
290
|
+
success|skipped) ;;
|
|
291
|
+
*) echo "a required job did not pass"; exit 1 ;;
|
|
292
|
+
esac
|
|
293
|
+
done
|
|
294
|
+
|
|
295
|
+
# DORMANT, repo-gated. The suite (tests/benchmarks) and the `benchmark`
|
|
296
|
+
# dependency group both exist; what is missing is a CodSpeed project, which
|
|
297
|
+
# needs the GitHub repo. CodSpeed runs the benchmarks instrumented rather than
|
|
298
|
+
# wall-clock, so the numbers are stable across runners, and it posts the delta
|
|
299
|
+
# as a pull request check rather than failing hard. Free for open source. To
|
|
300
|
+
# enable: connect the repo at codspeed.io, uncomment this job, and leave it
|
|
301
|
+
# out of the all-green needs list (benchmarks never block a merge).
|
|
302
|
+
#
|
|
303
|
+
# benchmarks:
|
|
304
|
+
# name: benchmarks
|
|
305
|
+
# runs-on: ubuntu-latest
|
|
306
|
+
# timeout-minutes: 20
|
|
307
|
+
# steps:
|
|
308
|
+
# - uses: actions/checkout@v4
|
|
309
|
+
# with:
|
|
310
|
+
# persist-credentials: false
|
|
311
|
+
# - uses: astral-sh/setup-uv@v5
|
|
312
|
+
# with:
|
|
313
|
+
# enable-cache: true
|
|
314
|
+
# cache-dependency-glob: uv.lock
|
|
315
|
+
# - run: uv sync --locked --group benchmark
|
|
316
|
+
# - uses: CodSpeedHQ/action@v3
|
|
317
|
+
# with:
|
|
318
|
+
# run: uv run --no-sync pytest tests/benchmarks --codspeed
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: codeql
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: "41 5 * * 2"
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
analyze:
|
|
20
|
+
name: analyze python
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 30
|
|
23
|
+
permissions:
|
|
24
|
+
security-events: write
|
|
25
|
+
actions: read
|
|
26
|
+
contents: read
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
29
|
+
with:
|
|
30
|
+
persist-credentials: false
|
|
31
|
+
|
|
32
|
+
- name: Initialize CodeQL
|
|
33
|
+
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
|
|
34
|
+
with:
|
|
35
|
+
languages: python
|
|
36
|
+
queries: security-and-quality
|
|
37
|
+
|
|
38
|
+
- name: Analyze
|
|
39
|
+
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: dependency review
|
|
2
|
+
|
|
3
|
+
# On a pull request that changes dependencies, block the merge if it introduces
|
|
4
|
+
# a known-vulnerable package or a copyleft licence. Complements
|
|
5
|
+
# scripts/check_licenses.py (which runs on the resolved tree) by catching the
|
|
6
|
+
# change at review time. Does nothing until the repository is on GitHub and a
|
|
7
|
+
# pull request exists.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
pull_request:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
review:
|
|
21
|
+
name: dependency review
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 10
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
|
|
29
|
+
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
|
|
30
|
+
with:
|
|
31
|
+
fail-on-severity: moderate
|
|
32
|
+
# The DENY tier from scripts/check_licenses.py: the strong and linking
|
|
33
|
+
# copyleft families (GPL, LGPL, AGPL, EUPL, SSPL), every SPDX spelling
|
|
34
|
+
# of each. `deny-licenses` matches exact ids, so each -only and
|
|
35
|
+
# -or-later variant and each deprecated id is listed. That script is
|
|
36
|
+
# the real gate (deny by default over the resolved tree); this one
|
|
37
|
+
# catches the same families at review time.
|
|
38
|
+
deny-licenses: >-
|
|
39
|
+
GPL-1.0-only, GPL-1.0-or-later, GPL-2.0, GPL-2.0-only, GPL-2.0-or-later,
|
|
40
|
+
GPL-3.0, GPL-3.0-only, GPL-3.0-or-later,
|
|
41
|
+
LGPL-2.0, LGPL-2.0-only, LGPL-2.0-or-later,
|
|
42
|
+
LGPL-2.1, LGPL-2.1-only, LGPL-2.1-or-later,
|
|
43
|
+
LGPL-3.0, LGPL-3.0-only, LGPL-3.0-or-later,
|
|
44
|
+
AGPL-1.0-only, AGPL-1.0-or-later, AGPL-3.0, AGPL-3.0-only, AGPL-3.0-or-later,
|
|
45
|
+
EUPL-1.1, EUPL-1.2, SSPL-1.0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: mutation testing
|
|
2
|
+
|
|
3
|
+
# Mutation testing: mutmut mutates mdcompose/core (flips a comparison, deletes a
|
|
4
|
+
# line, ...) and runs the suite against each mutant. A mutant that survives is
|
|
5
|
+
# behaviour the tests do not actually check. It runs the whole suite once per
|
|
6
|
+
# mutant, so this is a weekly cron and a manual button, never a required check
|
|
7
|
+
# and never on a pull request. Read the job summary to see where freshly written
|
|
8
|
+
# code needs more tests; the survivors feed the TDD coverage review in the
|
|
9
|
+
# roadmap quality pass.
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
schedule:
|
|
13
|
+
- cron: "0 4 * * 1"
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: mutation
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
mutmut:
|
|
25
|
+
name: mutmut over core
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
timeout-minutes: 60
|
|
28
|
+
# Reporting only. Nothing depends on this job and it never blocks a merge.
|
|
29
|
+
continue-on-error: true
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
|
|
35
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
cache-dependency-glob: uv.lock
|
|
39
|
+
|
|
40
|
+
- name: Sync the locked environment with the mutation group
|
|
41
|
+
run: uv sync --locked --group mutation
|
|
42
|
+
|
|
43
|
+
- name: Run mutmut
|
|
44
|
+
continue-on-error: true
|
|
45
|
+
run: uv run --no-sync mutmut run
|
|
46
|
+
|
|
47
|
+
- name: Write the results to the job summary
|
|
48
|
+
if: always()
|
|
49
|
+
run: |
|
|
50
|
+
{
|
|
51
|
+
echo '## mutmut results'
|
|
52
|
+
echo '```'
|
|
53
|
+
uv run --no-sync mutmut results || true
|
|
54
|
+
echo '```'
|
|
55
|
+
} >> "$GITHUB_STEP_SUMMARY"
|