markkit 0.1.1__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.
Files changed (113) hide show
  1. markkit-0.1.1/.agents/skills/process-issues/SKILL.md +52 -0
  2. markkit-0.1.1/.agents/skills/ship/SKILL.md +61 -0
  3. markkit-0.1.1/.claude/commands/maintain.md +49 -0
  4. markkit-0.1.1/.claude/hooks/fix-git-identity.sh +13 -0
  5. markkit-0.1.1/.claude/settings.json +20 -0
  6. markkit-0.1.1/.github/dependabot.yml +25 -0
  7. markkit-0.1.1/.github/workflows/ci.yml +80 -0
  8. markkit-0.1.1/.github/workflows/coverage.yml +25 -0
  9. markkit-0.1.1/.github/workflows/js.yml +38 -0
  10. markkit-0.1.1/.github/workflows/nightly.yml +21 -0
  11. markkit-0.1.1/.github/workflows/publish-js.yml +49 -0
  12. markkit-0.1.1/.github/workflows/publish-python.yml +128 -0
  13. markkit-0.1.1/.github/workflows/publish.yml +65 -0
  14. markkit-0.1.1/.github/workflows/python.yml +32 -0
  15. markkit-0.1.1/.github/workflows/release.yml +118 -0
  16. markkit-0.1.1/.gitignore +45 -0
  17. markkit-0.1.1/AGENTS.md +33 -0
  18. markkit-0.1.1/CHANGELOG.md +80 -0
  19. markkit-0.1.1/CLAUDE.md +1 -0
  20. markkit-0.1.1/CODE_OF_CONDUCT.md +44 -0
  21. markkit-0.1.1/CONTRIBUTING.md +62 -0
  22. markkit-0.1.1/Cargo.lock +3430 -0
  23. markkit-0.1.1/Cargo.toml +88 -0
  24. markkit-0.1.1/LICENSE +21 -0
  25. markkit-0.1.1/NOTICE +37 -0
  26. markkit-0.1.1/PKG-INFO +41 -0
  27. markkit-0.1.1/README.md +158 -0
  28. markkit-0.1.1/SECURITY.md +45 -0
  29. markkit-0.1.1/crates/markkit/Cargo.toml +100 -0
  30. markkit-0.1.1/crates/markkit/benches/extraction.rs +18 -0
  31. markkit-0.1.1/crates/markkit/src/archive.rs +173 -0
  32. markkit-0.1.1/crates/markkit/src/config.rs +134 -0
  33. markkit-0.1.1/crates/markkit/src/docx.rs +945 -0
  34. markkit-0.1.1/crates/markkit/src/error.rs +30 -0
  35. markkit-0.1.1/crates/markkit/src/extract.rs +292 -0
  36. markkit-0.1.1/crates/markkit/src/format.rs +265 -0
  37. markkit-0.1.1/crates/markkit/src/html/mod.rs +7 -0
  38. markkit-0.1.1/crates/markkit/src/html/parser.rs +517 -0
  39. markkit-0.1.1/crates/markkit/src/ir.rs +116 -0
  40. markkit-0.1.1/crates/markkit/src/lib.rs +68 -0
  41. markkit-0.1.1/crates/markkit/src/render/markdown.rs +402 -0
  42. markkit-0.1.1/crates/markkit/src/render/mod.rs +9 -0
  43. markkit-0.1.1/crates/markkit/src/spreadsheet.rs +716 -0
  44. markkit-0.1.1/crates/markkit/src/xml.rs +234 -0
  45. markkit-0.1.1/crates/markkit/tests/common/mod.rs +430 -0
  46. markkit-0.1.1/crates/markkit/tests/document_detection.rs +245 -0
  47. markkit-0.1.1/crates/markkit/tests/document_security.rs +405 -0
  48. markkit-0.1.1/crates/markkit/tests/docx_integration.rs +355 -0
  49. markkit-0.1.1/crates/markkit/tests/fixtures/html/basic_headings.html +6 -0
  50. markkit-0.1.1/crates/markkit/tests/fixtures/html/basic_headings.md +6 -0
  51. markkit-0.1.1/crates/markkit/tests/fixtures/html/blockquotes.html +8 -0
  52. markkit-0.1.1/crates/markkit/tests/fixtures/html/blockquotes.md +4 -0
  53. markkit-0.1.1/crates/markkit/tests/fixtures/html/code_blocks.html +8 -0
  54. markkit-0.1.1/crates/markkit/tests/fixtures/html/code_blocks.md +16 -0
  55. markkit-0.1.1/crates/markkit/tests/fixtures/html/full_article.html +29 -0
  56. markkit-0.1.1/crates/markkit/tests/fixtures/html/full_article.md +15 -0
  57. markkit-0.1.1/crates/markkit/tests/fixtures/html/inline_formatting.html +6 -0
  58. markkit-0.1.1/crates/markkit/tests/fixtures/html/inline_formatting.md +6 -0
  59. markkit-0.1.1/crates/markkit/tests/fixtures/html/links_and_images.html +7 -0
  60. markkit-0.1.1/crates/markkit/tests/fixtures/html/links_and_images.md +7 -0
  61. markkit-0.1.1/crates/markkit/tests/fixtures/html/lists.html +23 -0
  62. markkit-0.1.1/crates/markkit/tests/fixtures/html/lists.md +12 -0
  63. markkit-0.1.1/crates/markkit/tests/fixtures/html/malformed.html +5 -0
  64. markkit-0.1.1/crates/markkit/tests/fixtures/html/malformed.md +7 -0
  65. markkit-0.1.1/crates/markkit/tests/fixtures/html/tables.html +25 -0
  66. markkit-0.1.1/crates/markkit/tests/fixtures/html/tables.md +14 -0
  67. markkit-0.1.1/crates/markkit/tests/html_fixtures.rs +66 -0
  68. markkit-0.1.1/crates/markkit/tests/html_integration.rs +695 -0
  69. markkit-0.1.1/crates/markkit/tests/html_proptest.proptest-regressions +7 -0
  70. markkit-0.1.1/crates/markkit/tests/html_proptest.rs +71 -0
  71. markkit-0.1.1/crates/markkit/tests/html_security.rs +206 -0
  72. markkit-0.1.1/crates/markkit/tests/spreadsheet_integration.rs +277 -0
  73. markkit-0.1.1/crates/markkit-bench/Cargo.toml +31 -0
  74. markkit-0.1.1/crates/markkit-bench/src/main.rs +21 -0
  75. markkit-0.1.1/crates/markkit-cli/Cargo.toml +37 -0
  76. markkit-0.1.1/crates/markkit-cli/src/main.rs +124 -0
  77. markkit-0.1.1/crates/markkit-cli/tests/cli_integration.rs +201 -0
  78. markkit-0.1.1/crates/markkit-eval/Cargo.toml +25 -0
  79. markkit-0.1.1/crates/markkit-eval/src/main.rs +22 -0
  80. markkit-0.1.1/crates/markkit-js/Cargo.toml +25 -0
  81. markkit-0.1.1/crates/markkit-js/README.md +17 -0
  82. markkit-0.1.1/crates/markkit-js/build.rs +5 -0
  83. markkit-0.1.1/crates/markkit-js/package.json +38 -0
  84. markkit-0.1.1/crates/markkit-js/src/lib.rs +39 -0
  85. markkit-0.1.1/crates/markkit-python/Cargo.toml +22 -0
  86. markkit-0.1.1/crates/markkit-python/README.md +29 -0
  87. markkit-0.1.1/crates/markkit-python/src/lib.rs +90 -0
  88. markkit-0.1.1/deny.toml +32 -0
  89. markkit-0.1.1/docs/getting-started.md +56 -0
  90. markkit-0.1.1/examples/basic.rs +27 -0
  91. markkit-0.1.1/examples/configuration.rs +37 -0
  92. markkit-0.1.1/examples/custom_selectors.rs +33 -0
  93. markkit-0.1.1/examples/ir_access.rs +47 -0
  94. markkit-0.1.1/justfile +111 -0
  95. markkit-0.1.1/pyproject.toml +16 -0
  96. markkit-0.1.1/rust-toolchain.toml +3 -0
  97. markkit-0.1.1/scripts/init-cloud-env.sh +90 -0
  98. markkit-0.1.1/scripts/lib/common.sh +60 -0
  99. markkit-0.1.1/specs/architecture.md +45 -0
  100. markkit-0.1.1/specs/documentation.md +26 -0
  101. markkit-0.1.1/specs/eval.md +25 -0
  102. markkit-0.1.1/specs/examples-spec.md +67 -0
  103. markkit-0.1.1/specs/format-support.md +108 -0
  104. markkit-0.1.1/specs/html-extraction.md +153 -0
  105. markkit-0.1.1/specs/ir-spec.md +132 -0
  106. markkit-0.1.1/specs/js-package.md +33 -0
  107. markkit-0.1.1/specs/maintenance.md +35 -0
  108. markkit-0.1.1/specs/python-package.md +34 -0
  109. markkit-0.1.1/specs/release-process.md +51 -0
  110. markkit-0.1.1/specs/security.md +38 -0
  111. markkit-0.1.1/specs/testing.md +43 -0
  112. markkit-0.1.1/specs/threat-model.md +242 -0
  113. markkit-0.1.1/supply-chain/config.toml +16 -0
@@ -0,0 +1,52 @@
1
+ Process open GitHub issues and resolve them via merged pull requests.
2
+
3
+ ## Qualifying Issues
4
+
5
+ Only process issues created by authorized users or containing approval
6
+ comments from those users.
7
+
8
+ ## Core Principle
9
+
10
+ One issue = one PR. No bundling. No skipping.
11
+
12
+ ## Process Per Issue
13
+
14
+ ### 1. Understand & Scope
15
+ - Read the issue thoroughly
16
+ - Classify: bug, feature, docs, refactor
17
+ - Identify affected crates and files
18
+ - Create branch: `fix/issue-{N}-{short-slug}`
19
+
20
+ ### 2. Test First
21
+ - For bugs: create a failing test that reproduces the issue
22
+ - For features: scaffold validation tests
23
+ - Place tests alongside existing test modules
24
+
25
+ ### 3. Implement
26
+ - Make minimal, focused changes
27
+ - Cover positive and negative test cases
28
+ - Consider security implications for parsing changes
29
+
30
+ ### 4. Ship
31
+ - Use the `/ship` skill for:
32
+ - Spec updates
33
+ - Quality gates
34
+ - PR creation
35
+ - CI verification
36
+ - Merge
37
+
38
+ ### 5. Return to Main
39
+ ```bash
40
+ git checkout main && git pull origin main
41
+ ```
42
+ Then proceed to next issue.
43
+
44
+ ## Post-Resolution
45
+
46
+ After all issues are resolved, identify any `#[ignore]` tests
47
+ that now pass and un-ignore them in a single PR.
48
+
49
+ ## Arguments
50
+
51
+ Optional: specific issue numbers or labels.
52
+ Default: process all qualifying open issues.
@@ -0,0 +1,61 @@
1
+ Run the full ship flow: verify quality, ensure test coverage, update
2
+ artifacts, smoke test, then push, create PR, address review comments,
3
+ and merge only when CI is green, all threads resolved, and user confirms.
4
+
5
+ ## Phases
6
+
7
+ ### 1. Pre-flight
8
+ - Verify on a feature branch (not main)
9
+ - Check for uncommitted changes
10
+ - Fetch latest main and rebase if needed
11
+
12
+ ### 2. Test Coverage
13
+ - Identify changed code paths
14
+ - Ensure tests exist for:
15
+ - Positive cases (happy path)
16
+ - Negative cases (errors, edge cases)
17
+ - Security scenarios (malicious input)
18
+ - Add missing tests
19
+
20
+ ### 3. Artifact Updates
21
+ - Update specs if behavior changed
22
+ - Update CHANGELOG.md for user-facing changes
23
+ - Update documentation for API changes
24
+
25
+ ### 4. Simplification
26
+ - Remove dead code introduced by changes
27
+ - Reduce duplication
28
+ - Simplify where possible without changing behavior
29
+
30
+ ### 5. Security Review
31
+ - Check for injection risks in new parsing code
32
+ - Verify resource limits are enforced
33
+ - Review input validation
34
+
35
+ ### 6. Smoke Test
36
+ - Run `just test` for full test suite
37
+ - Run `just lint` for code quality
38
+ - Build CLI and verify basic extraction works
39
+
40
+ ### 7. Quality Gates
41
+ - `cargo fmt --all -- --check`
42
+ - `cargo clippy --workspace -- -D warnings`
43
+ - `cargo deny check`
44
+
45
+ ### 8. Push & PR
46
+ - Push branch to remote
47
+ - Create or update PR with summary
48
+ - Wait for CI to pass
49
+
50
+ ### 9. Review & Merge
51
+ - Check for unresolved review comments — address or reply to each
52
+ - Ensure all review threads are resolved
53
+ - Confirm no changes-requested reviews remain outstanding
54
+ - Ask the user whether to merge now or wait for reviewer feedback
55
+ - Only merge when: CI green, no unresolved threads, no pending change requests
56
+
57
+ **IMPORTANT**: Phases 2-5 (tests, artifacts, simplification, security review,
58
+ smoke testing) are the quality core — do NOT skip them.
59
+
60
+ **IMPORTANT**: Never auto-merge without confirming with the user. PRs should
61
+ have time for human review before merging.
@@ -0,0 +1,49 @@
1
+ Run pre-release maintenance checks on the markkit codebase.
2
+
3
+ Each section below is an outcome to achieve, not a script to follow.
4
+ Use whatever tools and approaches make sense to verify and fix.
5
+
6
+ Fix issues as you find them — don't just report. Use conventional
7
+ commit messages for any fixes.
8
+
9
+ Sections (run all unless $ARGUMENTS specifies a subset):
10
+
11
+ ## 1. Dependencies
12
+ - Run `cargo update` and verify tests pass
13
+ - Check for outdated dependencies
14
+ - Review Dependabot PRs if any
15
+
16
+ ## 2. Security
17
+ - Run `cargo audit` for vulnerabilities
18
+ - Run `cargo deny check` for license compliance
19
+ - Review any new advisories
20
+
21
+ ## 3. Tests
22
+ - Run full test suite: `just test`
23
+ - Check coverage isn't regressing
24
+ - Identify any `#[ignore]` tests that should be re-enabled
25
+
26
+ ## 4. Documentation
27
+ - Verify README is current
28
+ - Check rustdoc builds without warnings
29
+ - Ensure specs match implementation
30
+
31
+ ## 5. Examples
32
+ - Verify examples compile and run
33
+ - Update examples for any API changes
34
+
35
+ ## 6. Specs
36
+ - Review specs/ for accuracy
37
+ - Update implementation status
38
+
39
+ ## 7. Simplification
40
+ - Look for dead code, unused dependencies
41
+ - Identify duplication opportunities
42
+ - Reduce complexity where possible
43
+
44
+ ## 8. CI Health
45
+ - Check recent GitHub Actions runs
46
+ - Verify nightly builds are passing
47
+ - Review any flaky tests
48
+
49
+ Deliver a summary of findings and actions taken.
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+ # SessionStart hook: override agent-like git identity with real user
3
+
4
+ set -euo pipefail
5
+
6
+ git rev-parse --git-dir >/dev/null 2>&1 || exit 0
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
10
+
11
+ source "$REPO_ROOT/scripts/lib/common.sh"
12
+
13
+ configure_commit_git_identity_if_needed 2>/dev/null || true
@@ -0,0 +1,20 @@
1
+ {
2
+ "gitAttribution": false,
3
+ "attribution": {
4
+ "commit": "",
5
+ "pr": ""
6
+ },
7
+ "hooks": {
8
+ "SessionStart": [
9
+ {
10
+ "matcher": "",
11
+ "hooks": [
12
+ {
13
+ "type": "command",
14
+ "command": ".claude/hooks/fix-git-identity.sh"
15
+ }
16
+ ]
17
+ }
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,25 @@
1
+ version: 2
2
+ updates:
3
+ # Rust dependencies
4
+ - package-ecosystem: "cargo"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ commit-message:
9
+ prefix: "chore(deps)"
10
+ groups:
11
+ rust-dependencies:
12
+ patterns:
13
+ - "*"
14
+
15
+ # GitHub Actions
16
+ - package-ecosystem: "github-actions"
17
+ directory: "/"
18
+ schedule:
19
+ interval: "weekly"
20
+ commit-message:
21
+ prefix: "chore(ci)"
22
+ groups:
23
+ github-actions:
24
+ patterns:
25
+ - "*"
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ env:
11
+ CARGO_TERM_COLOR: always
12
+ RUSTFLAGS: -Dwarnings
13
+
14
+ jobs:
15
+ lint:
16
+ name: Lint
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: dtolnay/rust-toolchain@stable
21
+ with:
22
+ components: rustfmt, clippy
23
+ - uses: Swatinem/rust-cache@v2
24
+ - name: Check formatting
25
+ run: cargo fmt --all -- --check
26
+ - name: Clippy
27
+ run: cargo clippy --workspace --all-targets -- -D warnings
28
+ - name: Build docs
29
+ run: cargo doc --workspace --no-deps
30
+ env:
31
+ RUSTDOCFLAGS: -Dwarnings
32
+
33
+ audit:
34
+ name: Audit
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - uses: dtolnay/rust-toolchain@stable
39
+ - name: Install cargo-audit
40
+ run: cargo install cargo-audit
41
+ - name: Install cargo-deny
42
+ run: cargo install cargo-deny
43
+ - name: Security audit
44
+ run: cargo audit
45
+ - name: License and dependency check
46
+ run: cargo deny check
47
+
48
+ test:
49
+ name: Test
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: dtolnay/rust-toolchain@stable
54
+ - uses: Swatinem/rust-cache@v2
55
+ - name: Run tests
56
+ run: cargo test --workspace
57
+ - name: Run tests (all features)
58
+ run: cargo test --workspace --all-features
59
+ - name: Run tests (office formats, no HTTP client)
60
+ run: |
61
+ cargo test -p markkit --no-default-features --features docx,spreadsheet
62
+ if cargo tree -p markkit --no-default-features --features docx,spreadsheet -e normal | grep -E 'reqwest|hyper'; then
63
+ echo "HTTP client leaked into the docx,spreadsheet feature set"
64
+ exit 1
65
+ fi
66
+
67
+ check:
68
+ name: Check
69
+ needs: [lint, audit, test]
70
+ runs-on: ubuntu-latest
71
+ if: always()
72
+ steps:
73
+ - name: All jobs passed
74
+ run: |
75
+ if [[ "${{ needs.lint.result }}" != "success" ]] || \
76
+ [[ "${{ needs.audit.result }}" != "success" ]] || \
77
+ [[ "${{ needs.test.result }}" != "success" ]]; then
78
+ echo "One or more jobs failed"
79
+ exit 1
80
+ fi
@@ -0,0 +1,25 @@
1
+ name: Coverage
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ coverage:
11
+ name: Code Coverage
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: dtolnay/rust-toolchain@stable
16
+ - uses: Swatinem/rust-cache@v2
17
+ - name: Install cargo-tarpaulin
18
+ run: cargo install cargo-tarpaulin
19
+ - name: Generate coverage
20
+ run: cargo tarpaulin --workspace --out xml
21
+ - name: Upload coverage
22
+ uses: codecov/codecov-action@v4
23
+ with:
24
+ file: cobertura.xml
25
+ fail_ci_if_error: false
@@ -0,0 +1,38 @@
1
+ name: JavaScript
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "crates/markkit-js/**"
8
+ - "crates/markkit/**"
9
+ pull_request:
10
+ branches: [main]
11
+ paths:
12
+ - "crates/markkit-js/**"
13
+ - "crates/markkit/**"
14
+
15
+ jobs:
16
+ build:
17
+ name: Build Node.js bindings
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: dtolnay/rust-toolchain@stable
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: "24.22.1"
25
+ - name: Install dependencies
26
+ run: cd crates/markkit-js && npm install --ignore-scripts --audit=false --fund=false
27
+ - name: Build
28
+ run: cd crates/markkit-js && npx --no-install napi build --release --platform
29
+ - name: Verify build preserved the patched lockfile
30
+ run: git diff --exit-code -- Cargo.lock
31
+ - name: Verify installed npm package
32
+ run: |
33
+ cd crates/markkit-js
34
+ npm pack --ignore-scripts --json > "$RUNNER_TEMP/markkit-pack.json"
35
+ node -e 'const p=require(process.argv[1])[0]; const files=p.files.map(f=>f.path); if(!files.includes("index.js") || !files.some(f=>f.endsWith(".node"))) throw Error("npm package must contain its loader and native binary")' "$RUNNER_TEMP/markkit-pack.json"
36
+ TARBALL=$(node -e 'console.log(require(process.argv[1])[0].filename)' "$RUNNER_TEMP/markkit-pack.json")
37
+ npm install --prefix "$RUNNER_TEMP/markkit-npm-smoke" --ignore-scripts --audit=false --fund=false "$PWD/$TARBALL"
38
+ node -e 'const m=require(process.argv[1]); if(m.extractHtmlSync("<h1>Smoke</h1>").trim()!=="# Smoke") throw Error("native extraction smoke failed")' "$RUNNER_TEMP/markkit-npm-smoke/node_modules/@everruns/markkit"
@@ -0,0 +1,21 @@
1
+ name: Nightly
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 4 * * *"
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ nightly:
10
+ name: Nightly checks
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: dtolnay/rust-toolchain@nightly
15
+ with:
16
+ components: rustfmt, clippy
17
+ - uses: Swatinem/rust-cache@v2
18
+ - name: Build on nightly
19
+ run: cargo build --workspace
20
+ - name: Test on nightly
21
+ run: cargo test --workspace
@@ -0,0 +1,49 @@
1
+ name: Publish JavaScript
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ dry_run:
7
+ description: "Dry run"
8
+ required: false
9
+ default: "true"
10
+
11
+ jobs:
12
+ publish:
13
+ name: Publish npm package
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: dtolnay/rust-toolchain@stable
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: "24.22.1"
21
+ registry-url: "https://registry.npmjs.org"
22
+ - name: Require npm publisher credential
23
+ if: inputs.dry_run != 'true'
24
+ env:
25
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
26
+ run: |
27
+ if [ -z "$NODE_AUTH_TOKEN" ]; then
28
+ echo "::error::NPM_TOKEN is unavailable for this publisher"
29
+ exit 1
30
+ fi
31
+ - name: Install dependencies
32
+ run: cd crates/markkit-js && npm install --ignore-scripts --audit=false --fund=false
33
+ - name: Build
34
+ run: cd crates/markkit-js && npx --no-install napi build --release --platform
35
+ - name: Verify build preserved the patched lockfile
36
+ run: git diff --exit-code -- Cargo.lock
37
+ - name: Verify installed npm package
38
+ run: |
39
+ cd crates/markkit-js
40
+ npm pack --ignore-scripts --json > "$RUNNER_TEMP/markkit-pack.json"
41
+ node -e 'const p=require(process.argv[1])[0]; const files=p.files.map(f=>f.path); if(!files.includes("index.js") || !files.some(f=>f.endsWith(".node"))) throw Error("npm package must contain its loader and native binary")' "$RUNNER_TEMP/markkit-pack.json"
42
+ TARBALL=$(node -e 'console.log(require(process.argv[1])[0].filename)' "$RUNNER_TEMP/markkit-pack.json")
43
+ npm install --prefix "$RUNNER_TEMP/markkit-npm-smoke" --ignore-scripts --audit=false --fund=false "$PWD/$TARBALL"
44
+ node -e 'const m=require(process.argv[1]); if(m.extractHtmlSync("<h1>Smoke</h1>").trim()!=="# Smoke") throw Error("native extraction smoke failed")' "$RUNNER_TEMP/markkit-npm-smoke/node_modules/@everruns/markkit"
45
+ - name: Publish
46
+ if: inputs.dry_run != 'true'
47
+ run: cd crates/markkit-js && npm publish --ignore-scripts
48
+ env:
49
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,128 @@
1
+ name: Publish Python
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ dry_run:
7
+ description: "Build and verify without publishing"
8
+ required: false
9
+ default: "true"
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ build:
16
+ name: Build and verify Python distributions
17
+ runs-on: ubuntu-latest
18
+ outputs:
19
+ version: ${{ steps.metadata.outputs.version }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: dtolnay/rust-toolchain@stable
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+ - name: Install packaging tools
27
+ run: python -m pip install maturin==1.15.0 twine==7.0.0
28
+ - name: Build from source distribution
29
+ run: maturin build --release --locked --sdist
30
+ - name: Verify build preserved the patched lockfile
31
+ run: git diff --exit-code -- Cargo.lock
32
+ - name: Inspect distribution identity
33
+ id: metadata
34
+ run: |
35
+ python <<'PYTHON'
36
+ import email.parser
37
+ import os
38
+ import pathlib
39
+ import zipfile
40
+ wheels = list(pathlib.Path("target/wheels").glob("*.whl"))
41
+ assert len(wheels) == 1
42
+ with zipfile.ZipFile(wheels[0]) as wheel:
43
+ metadata = email.parser.BytesParser().parsebytes(wheel.read(next(name for name in wheel.namelist() if name.endswith(".dist-info/METADATA"))))
44
+ assert metadata["Name"] == "markkit"
45
+ with open(os.environ["GITHUB_OUTPUT"], "a") as output:
46
+ print(f"version={metadata['Version']}", file=output)
47
+ print(f"markkit {metadata['Version']} from {os.environ['GITHUB_SHA']}")
48
+ PYTHON
49
+ - name: Verify distributions and native import
50
+ run: |
51
+ python -m twine check --strict target/wheels/*.whl target/wheels/*.tar.gz
52
+ python -m venv "$RUNNER_TEMP/markkit-wheel-smoke"
53
+ "$RUNNER_TEMP/markkit-wheel-smoke/bin/python" -m pip install --no-deps target/wheels/*.whl
54
+ "$RUNNER_TEMP/markkit-wheel-smoke/bin/python" -c 'import importlib.metadata, importlib.util, markkit; assert importlib.metadata.metadata("markkit")["Name"] == "markkit"; assert importlib.util.find_spec("markkit_python") is None; assert markkit.extract_html_sync("<h1>Smoke</h1>").strip() == "# Smoke"'
55
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
56
+ with:
57
+ name: python-distributions
58
+ path: |
59
+ target/wheels/*.whl
60
+ target/wheels/*.tar.gz
61
+ if-no-files-found: error
62
+
63
+ publish:
64
+ name: Publish Python package with OIDC
65
+ needs: build
66
+ if: inputs.dry_run != 'true'
67
+ runs-on: ubuntu-latest
68
+ permissions:
69
+ contents: read
70
+ id-token: write
71
+ steps:
72
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
73
+ with:
74
+ name: python-distributions
75
+ path: dist
76
+ - name: Publish to PyPI
77
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
78
+ with:
79
+ packages-dir: dist/
80
+
81
+ verify:
82
+ name: Verify public PyPI installation
83
+ needs: [build, publish]
84
+ runs-on: ubuntu-latest
85
+ env:
86
+ MARKKIT_VERSION: ${{ needs.build.outputs.version }}
87
+ steps:
88
+ - uses: actions/setup-python@v5
89
+ with:
90
+ python-version: "3.12"
91
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
92
+ with:
93
+ name: python-distributions
94
+ path: dist
95
+ - name: Download the published wheel
96
+ run: |
97
+ for attempt in 1 2 3 4 5; do
98
+ if python -m pip download --no-cache-dir --only-binary=:all: --no-deps --dest registry "markkit==$MARKKIT_VERSION"; then
99
+ break
100
+ fi
101
+ if [ "$attempt" -eq 5 ]; then exit 1; fi
102
+ sleep 10
103
+ done
104
+ - name: Verify public files match the build
105
+ run: |
106
+ python <<'PYTHON'
107
+ import hashlib
108
+ import json
109
+ import os
110
+ import pathlib
111
+ import urllib.request
112
+ version = os.environ["MARKKIT_VERSION"]
113
+ with urllib.request.urlopen(f"https://pypi.org/pypi/markkit/{version}/json") as response:
114
+ published = json.load(response)
115
+ assert published["info"]["name"] == "markkit"
116
+ assert published["info"]["version"] == version
117
+ digests = {entry["filename"]: entry["digests"]["sha256"] for entry in published["urls"]}
118
+ for artifact in pathlib.Path("dist").iterdir():
119
+ assert digests[artifact.name] == hashlib.sha256(artifact.read_bytes()).hexdigest()
120
+ for wheel in pathlib.Path("registry").glob("*.whl"):
121
+ assert digests[wheel.name] == hashlib.sha256(wheel.read_bytes()).hexdigest()
122
+ print(f"Public markkit {version} distributions match the build")
123
+ PYTHON
124
+ - name: Install and smoke-test the public wheel
125
+ run: |
126
+ python -m venv "$RUNNER_TEMP/markkit-public-smoke"
127
+ "$RUNNER_TEMP/markkit-public-smoke/bin/python" -m pip install --no-index --no-deps registry/*.whl
128
+ "$RUNNER_TEMP/markkit-public-smoke/bin/python" -c 'import importlib.metadata, importlib.util, markkit; assert importlib.metadata.version("markkit") == __import__("os").environ["MARKKIT_VERSION"]; assert importlib.util.find_spec("markkit_python") is None; assert markkit.extract_html_sync("<h1>Published smoke</h1>").strip() == "# Published smoke"; print("Public markkit import and native extraction PASS")'
@@ -0,0 +1,65 @@
1
+ name: Publish
2
+
3
+ # Decisions:
4
+ # - Runs from a release tag (dispatched by release.yml, or manually from a tag).
5
+ # - Idempotent: a crate whose version is already on crates.io is skipped, so
6
+ # re-running a release never fails on an already-published crate.
7
+ # - Each crate is verified with a dry run before the credentialed upload.
8
+ on:
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ env:
15
+ CARGO_TERM_COLOR: always
16
+
17
+ jobs:
18
+ publish:
19
+ name: Publish to crates.io
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - uses: dtolnay/rust-toolchain@stable
27
+
28
+ - name: Verify tag and version
29
+ id: version
30
+ run: |
31
+ if [[ ! "$GITHUB_REF" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
32
+ echo "Error: publish must run from a release tag (vX.Y.Z), got $GITHUB_REF"
33
+ exit 1
34
+ fi
35
+ TAG_VERSION="${GITHUB_REF#refs/tags/v}"
36
+ CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
37
+ if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
38
+ echo "Error: tag version ($TAG_VERSION) does not match Cargo.toml ($CARGO_VERSION)"
39
+ exit 1
40
+ fi
41
+ git fetch --no-tags origin main:refs/remotes/origin/main
42
+ if ! git merge-base --is-ancestor HEAD origin/main; then
43
+ echo "Error: publish source is not reachable from origin/main"
44
+ exit 1
45
+ fi
46
+ echo "version=$CARGO_VERSION" >> "$GITHUB_OUTPUT"
47
+
48
+ - name: Publish crates
49
+ env:
50
+ VERSION: ${{ steps.version.outputs.version }}
51
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
52
+ run: |
53
+ published() {
54
+ curl -sf -A "markkit-release (github.com/everruns/markkit)" \
55
+ "https://crates.io/api/v1/crates/$1/$VERSION" > /dev/null
56
+ }
57
+ # Dependency order: markkit-cli depends on markkit.
58
+ for crate in markkit markkit-cli; do
59
+ if published "$crate"; then
60
+ echo "$crate $VERSION already on crates.io; skipping"
61
+ continue
62
+ fi
63
+ cargo publish --dry-run -p "$crate"
64
+ cargo publish --no-verify -p "$crate"
65
+ done
@@ -0,0 +1,32 @@
1
+ name: Python
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "crates/markkit-python/**"
8
+ - "crates/markkit/**"
9
+ pull_request:
10
+ branches: [main]
11
+ paths:
12
+ - "crates/markkit-python/**"
13
+ - "crates/markkit/**"
14
+
15
+ jobs:
16
+ build:
17
+ name: Build Python bindings
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: dtolnay/rust-toolchain@stable
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.12"
25
+ - name: Install maturin
26
+ run: pip install maturin
27
+ - name: Build
28
+ run: maturin build
29
+ - name: Test import
30
+ run: |
31
+ pip install target/wheels/*.whl
32
+ python -c 'from importlib.metadata import metadata; import markkit; assert metadata("markkit")["Name"] == "markkit"; assert markkit.extract_html_sync("<h1>Smoke</h1>").strip() == "# Smoke"; print("Distribution and import OK")'