strictcli 0.24.0__tar.gz → 0.25.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.
Files changed (84) hide show
  1. strictcli-0.25.0/.github/workflows/ci-pypi.yml +29 -0
  2. strictcli-0.25.0/.github/workflows/publish.yml +152 -0
  3. {strictcli-0.24.0 → strictcli-0.25.0}/.gitignore +2 -0
  4. strictcli-0.25.0/.rlsbl/config.json +18 -0
  5. {strictcli-0.24.0 → strictcli-0.25.0}/.rlsbl/hashes.json +4 -3
  6. strictcli-0.25.0/.rlsbl/managed-files.json +11 -0
  7. {strictcli-0.24.0 → strictcli-0.25.0}/PKG-INFO +1 -1
  8. {strictcli-0.24.0 → strictcli-0.25.0}/package-lock.json +2 -2
  9. {strictcli-0.24.0 → strictcli-0.25.0}/package.json +1 -1
  10. {strictcli-0.24.0 → strictcli-0.25.0}/pyproject.toml +1 -1
  11. {strictcli-0.24.0 → strictcli-0.25.0}/strictcli/__init__.py +619 -158
  12. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_config.py +406 -10
  13. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_dump_schema.py +29 -3
  14. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_global_flags.py +19 -19
  15. strictcli-0.25.0/tests/test_hermetic.py +204 -0
  16. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_invoke.py +14 -14
  17. strictcli-0.25.0/tests/test_provenance.py +211 -0
  18. strictcli-0.25.0/tests/test_provenance_phase2.py +346 -0
  19. strictcli-0.25.0/tests/test_reserved_global_flags.py +48 -0
  20. {strictcli-0.24.0 → strictcli-0.25.0}/uv.lock +1 -1
  21. strictcli-0.24.0/.github/workflows/publish.yml +0 -27
  22. strictcli-0.24.0/.rlsbl/config.json +0 -12
  23. strictcli-0.24.0/.rlsbl/version +0 -1
  24. strictcli-0.24.0/CHANGELOG.md +0 -52
  25. {strictcli-0.24.0 → strictcli-0.25.0}/.claude/settings.json +0 -0
  26. {strictcli-0.24.0 → strictcli-0.25.0}/.github/workflows/ci.yml +0 -0
  27. {strictcli-0.24.0 → strictcli-0.25.0}/.npmignore +0 -0
  28. {strictcli-0.24.0 → strictcli-0.25.0}/.strictcli/schema.json +0 -0
  29. {strictcli-0.24.0 → strictcli-0.25.0}/CLAUDE.md +0 -0
  30. {strictcli-0.24.0 → strictcli-0.25.0}/LICENSE +0 -0
  31. {strictcli-0.24.0 → strictcli-0.25.0}/README.md +0 -0
  32. {strictcli-0.24.0 → strictcli-0.25.0}/index.js +0 -0
  33. {strictcli-0.24.0 → strictcli-0.25.0}/postinstall.js +0 -0
  34. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_arg_default.py +0 -0
  35. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_arg_default_validation.py +0 -0
  36. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_at_prefix.py +0 -0
  37. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_auto_version.py +0 -0
  38. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_call.py +0 -0
  39. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_check_command.py +0 -0
  40. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_check_discovery.py +0 -0
  41. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_check_public_api.py +0 -0
  42. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_check_runner.py +0 -0
  43. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_check_schema.py +0 -0
  44. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_check_types.py +0 -0
  45. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_choices.py +0 -0
  46. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_choices_none.py +0 -0
  47. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_command_help_suggestion.py +0 -0
  48. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_command_tags.py +0 -0
  49. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_compound_types.py +0 -0
  50. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_config_fields.py +0 -0
  51. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_config_file_path.py +0 -0
  52. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_config_set_bugs.py +0 -0
  53. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_context.py +0 -0
  54. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_deep_nesting.py +0 -0
  55. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_dependencies.py +0 -0
  56. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_deprecated.py +0 -0
  57. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_e2e.py +0 -0
  58. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_env.py +0 -0
  59. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_exit_codes.py +0 -0
  60. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_flag_sets.py +0 -0
  61. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_float_type.py +0 -0
  62. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_help.py +0 -0
  63. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_int_type.py +0 -0
  64. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_keyword_flags.py +0 -0
  65. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_mcp.py +0 -0
  66. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_mutex.py +0 -0
  67. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_nesting.py +0 -0
  68. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_parser.py +0 -0
  69. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_passthrough.py +0 -0
  70. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_registration.py +0 -0
  71. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_repeatable.py +0 -0
  72. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_tagdsl.py +0 -0
  73. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_toml_loading.py +0 -0
  74. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_tool_export.py +0 -0
  75. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_typed_args.py +0 -0
  76. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_unique.py +0 -0
  77. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_utilities.py +0 -0
  78. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_validate.py +0 -0
  79. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_variadic.py +0 -0
  80. {strictcli-0.24.0 → strictcli-0.25.0}/tests/test_visibility.py +0 -0
  81. {strictcli-0.24.0 → strictcli-0.25.0}/todo/.defer/deferred.md +0 -0
  82. {strictcli-0.24.0 → strictcli-0.25.0}/todo/.done/keyword-collision-in-flag-param-name.md +0 -0
  83. {strictcli-0.24.0 → strictcli-0.25.0}/todo/.done/original-idea.md +0 -0
  84. {strictcli-0.24.0 → strictcli-0.25.0}/todo/.done/public-check-runner-api.md +0 -0
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ # Per-SHA group: re-runs of the same commit dedupe, but a new commit never
11
+ # cancels an earlier commit's in-flight run (release CI conclusions stay intact).
12
+ concurrency:
13
+ group: ${{ github.workflow_ref }}-${{ github.sha }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ # requires-python: >= 3.11
22
+ python-version: ["3.12", "3.13", "3.14"]
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+ - uses: astral-sh/setup-uv@v7
26
+ - run: uv python install ${{ matrix.python-version }}
27
+ - run: uv sync --locked
28
+ - run: uv run python -c "import strictcli"
29
+ - run: uv run pytest
@@ -0,0 +1,152 @@
1
+ name: Publish
2
+ on:
3
+ release:
4
+ types: [published]
5
+ workflow_dispatch:
6
+ concurrency:
7
+ group: ${{ github.workflow_ref }}-${{ github.ref }}
8
+ cancel-in-progress: false
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+ jobs:
13
+ gate:
14
+ name: Gate on CI
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ checks: read
18
+ env:
19
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20
+ GATE_TIMEOUT_MINUTES: '20'
21
+ GATE_GRACE_MINUTES: '5'
22
+ GATE_POLL_SECONDS: '15'
23
+ CI_CHECK_REGEX: ^(test)( \(.*\))?$
24
+ steps:
25
+ - name: Wait for CI to succeed on the release commit
26
+ run: |
27
+ set -euo pipefail
28
+
29
+ # Ref-based commit resolution: GITHUB_SHA is the tag's commit for both
30
+ # release-triggered runs and workflow_dispatch retries at the tag ref.
31
+ # Never read the release event payload -- dispatch retries have none.
32
+ sha="$GITHUB_SHA"
33
+ echo "Publish gate: waiting for CI on $GITHUB_REF_NAME (commit $sha)"
34
+ echo "Check-run name filter: $CI_CHECK_REGEX"
35
+ # Timeout, grace window, and poll interval come from the job env above;
36
+ # edit them there if this repository's CI needs different limits.
37
+
38
+ now() { date +%s; }
39
+ start="$(now)"
40
+ deadline=$(( start + GATE_TIMEOUT_MINUTES * 60 ))
41
+ grace_deadline=$(( start + GATE_GRACE_MINUTES * 60 ))
42
+
43
+ while :; do
44
+ if ! resp="$(gh api --paginate "repos/$GITHUB_REPOSITORY/commits/$sha/check-runs?per_page=100")"; then
45
+ echo "Checks API request failed; retrying in ${GATE_POLL_SECONDS}s..."
46
+ sleep "$GATE_POLL_SECONDS"
47
+ continue
48
+ fi
49
+ # Match this project's CI check runs by name; exclude check runs that
50
+ # belong to THIS workflow run (the gate itself and the queued publish
51
+ # jobs would otherwise deadlock the poll loop).
52
+ runs="$(jq -s --arg re "$CI_CHECK_REGEX" --arg run_id "$GITHUB_RUN_ID" '
53
+ [ .[].check_runs[]
54
+ | select(.name | test($re))
55
+ | select((.details_url // "") | contains("/actions/runs/" + $run_id + "/") | not)
56
+ | {name, status, conclusion} ]' <<< "$resp")"
57
+ total="$(jq 'length' <<< "$runs")"
58
+
59
+ if [ "$total" -eq 0 ]; then
60
+ if [ "$(now)" -ge "$grace_deadline" ]; then
61
+ echo "::error::Publish gate: no CI check runs matching $CI_CHECK_REGEX appeared on $sha within $GATE_GRACE_MINUTES minutes."
62
+ echo "A scaffolded repository always has a CI workflow, so the release commit must produce CI check runs."
63
+ echo "If CI jobs were renamed, update CI_CHECK_REGEX in this workflow's gate job to match the new names."
64
+ exit 1
65
+ fi
66
+ echo "No matching CI check runs yet; retrying in ${GATE_POLL_SECONDS}s..."
67
+ sleep "$GATE_POLL_SECONDS"
68
+ continue
69
+ fi
70
+
71
+ pending="$(jq '[ .[] | select(.status != "completed") ] | length' <<< "$runs")"
72
+ if [ "$pending" -gt 0 ]; then
73
+ if [ "$(now)" -ge "$deadline" ]; then
74
+ echo "::error::Publish gate: timed out after $GATE_TIMEOUT_MINUTES minutes waiting for CI to complete on $sha."
75
+ jq -r '.[] | " \(.name): status=\(.status) conclusion=\(.conclusion // "none")"' <<< "$runs"
76
+ exit 1
77
+ fi
78
+ echo "$pending of $total matching CI check runs still running; retrying in ${GATE_POLL_SECONDS}s..."
79
+ sleep "$GATE_POLL_SECONDS"
80
+ continue
81
+ fi
82
+
83
+ not_success="$(jq '[ .[] | select(.conclusion != "success") ]' <<< "$runs")"
84
+ if [ "$(jq 'length' <<< "$not_success")" -gt 0 ]; then
85
+ echo "::error::Publish gate: CI did not pass on $sha -- refusing to publish."
86
+ jq -r '.[] | " \(.name): \(.conclusion)"' <<< "$not_success"
87
+ while IFS= read -r conclusion; do
88
+ case "$conclusion" in
89
+ failure|timed_out)
90
+ echo "CI concluded '$conclusion' on the release commit. Fix the failure, re-run the CI workflow to green on this exact commit (gh run rerun <run-id>), then re-dispatch this publish workflow at the tag ref: gh workflow run <publish workflow> --ref $GITHUB_REF_NAME"
91
+ ;;
92
+ cancelled)
93
+ echo "A CI check run was CANCELLED. A cancelled run proves nothing about the commit, so the gate treats it as a hard failure instead of waiting for a conclusion that will never come. Re-run the cancelled CI workflow (gh run rerun <run-id>), then re-dispatch this publish workflow at the tag ref."
94
+ ;;
95
+ skipped)
96
+ echo "A CI check run matching the filter was SKIPPED. The gate cannot treat a skipped check as passing: this project's own CI must actually run on the release commit. Check paths filters and job conditions, re-run CI on this commit, then re-dispatch this publish workflow at the tag ref."
97
+ ;;
98
+ *)
99
+ echo "CI check concluded '$conclusion' (not success). The gate only proceeds when every matching check concluded success."
100
+ ;;
101
+ esac
102
+ done <<< "$(jq -r '.[].conclusion' <<< "$not_success" | sort -u)"
103
+ exit 1
104
+ fi
105
+
106
+ echo "Publish gate: all $total matching CI check runs succeeded."
107
+ jq -r '.[] | " \(.name): \(.conclusion)"' <<< "$runs"
108
+ exit 0
109
+ done
110
+ pypi:
111
+ needs: gate
112
+ runs-on: ubuntu-latest
113
+ steps:
114
+ - uses: actions/checkout@v6
115
+ - uses: astral-sh/setup-uv@v7
116
+ - run: uv build --out-dir dist
117
+ - uses: pypa/gh-action-pypi-publish@release/v1
118
+ with:
119
+ skip-existing: true
120
+ npm:
121
+ needs: gate
122
+ runs-on: ubuntu-latest
123
+ steps:
124
+ - uses: actions/checkout@v6
125
+ - uses: actions/setup-node@v6
126
+ with:
127
+ node-version: 24
128
+ registry-url: https://registry.npmjs.org
129
+ - name: Check if already published
130
+ id: check-npm
131
+ run: |
132
+ PKG_NAME=$(node -p "require('./package.json').name")
133
+ PKG_VERSION=$(node -p "require('./package.json').version")
134
+ if npm view "${PKG_NAME}@${PKG_VERSION}" version 2>/dev/null; then
135
+ echo "skip=true" >> "$GITHUB_OUTPUT"
136
+ echo "Already published: ${PKG_NAME}@${PKG_VERSION}"
137
+ fi
138
+ - name: Determine dist-tag
139
+ id: dist-tag
140
+ run: |
141
+ PKG_VERSION=$(node -p "require('./package.json').version")
142
+ if echo "$PKG_VERSION" | grep -q '-'; then
143
+ PREID=$(echo "$PKG_VERSION" | sed 's/[^-]*-//' | sed 's/\.[^.]*$//')
144
+ echo "tag=--tag $PREID" >> "$GITHUB_OUTPUT"
145
+ else
146
+ echo "tag=" >> "$GITHUB_OUTPUT"
147
+ fi
148
+ - run: npm publish --provenance --access public ${{
149
+ steps.dist-tag.outputs.tag }}
150
+ if: steps.check-npm.outputs.skip != 'true'
151
+ env:
152
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -13,3 +13,5 @@ dist/
13
13
  .env
14
14
  .env.local
15
15
  *.local-only
16
+
17
+ .rlsbl-monorepo/lock
@@ -0,0 +1,18 @@
1
+ {
2
+ "targets": [
3
+ "pypi",
4
+ "npm"
5
+ ],
6
+ "private": false,
7
+ "push_timeout": 300,
8
+ "pipelines": {
9
+ "pypi": {
10
+ "type": "pypi",
11
+ "local": false
12
+ },
13
+ "npm": {
14
+ "type": "npm",
15
+ "local": false
16
+ }
17
+ }
18
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  ".github/workflows/ci.yml": "b4e075171b131d25f9cce55a3e3a30732e95a2a794af00f354ee3200626d6a04",
3
- ".github/workflows/publish.yml": "e77dcd0b5d1c1abae5424e5f3b73862f44f06b5d46209cbe71bc04d6baa6b5be",
3
+ ".github/workflows/publish.yml": "20db711b8a7745c3ab41814b827e946d60e937e82ee3984e86f39567717199b1",
4
4
  "CHANGELOG.md": "7a6ec46141007c18090a0ff325e2dd68cd51588c4e0b5af7b580365e5eaaca85",
5
- ".gitignore": "c89d76c6c43e7f0897b3472a15b7c0103e0be5b3a04c2b44e585fa48bdcda2bb",
5
+ ".gitignore": "c0582f9b1c640f9f9a8acbbe02cd19fff50e66cc595f25e129b7cc9b6cd357db",
6
6
  "LICENSE": "e958892abc1dd5d991fca6d528e695d335f25773d373d2e0be5f4a9507d8f8d5",
7
7
  "CLAUDE.md": "7294b791570185dc31020dadb092b5b79681ba8ee932fe9a3974e27eaae56c4f",
8
8
  ".rlsbl/hooks/pre-release.sh": "b2dc17c243d0b1a2c1e23c1c25db6f94878596df81dc76c41b32d95f740a7d44",
@@ -12,5 +12,6 @@
12
12
  ".rlsbl/lint/python.toml": "9cfbcef2e010d5fad243437bf2fd8df54a1fae70ee0762f9d11da70fe207501c",
13
13
  ".rlsbl/lint/go.toml": "807d5cf760baad6d571a5bfd43cb163efe356f7462ba9f1918d72de45f62339f",
14
14
  ".rlsbl/lint/npm.toml": "baa36014e65923d8cc5c8b83c77fa651da79351627570476402da66840027645",
15
- ".npmignore": "7c0eea9785c0318aeb39bf8b670ce8384f4a5b812fde69ce23d7d018057325a3"
15
+ ".npmignore": "7c0eea9785c0318aeb39bf8b670ce8384f4a5b812fde69ce23d7d018057325a3",
16
+ ".github/workflows/ci-pypi.yml": "6bde3f6dd33979636f6a8f51721f85519c4d2b6106cb9c9fa78e540df26471c4"
16
17
  }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "files": {
4
+ ".github/workflows/ci-pypi.yml": "6bde3f6dd33979636f6a8f51721f85519c4d2b6106cb9c9fa78e540df26471c4",
5
+ ".npmignore": "7c0eea9785c0318aeb39bf8b670ce8384f4a5b812fde69ce23d7d018057325a3",
6
+ ".github/workflows/publish.yml": "20db711b8a7745c3ab41814b827e946d60e937e82ee3984e86f39567717199b1",
7
+ ".gitignore": "c0582f9b1c640f9f9a8acbbe02cd19fff50e66cc595f25e129b7cc9b6cd357db",
8
+ ".rlsbl/lint/python.toml": "9cfbcef2e010d5fad243437bf2fd8df54a1fae70ee0762f9d11da70fe207501c",
9
+ ".rlsbl/lint/npm.toml": "baa36014e65923d8cc5c8b83c77fa651da79351627570476402da66840027645"
10
+ }
11
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strictcli
3
- Version: 0.24.0
3
+ Version: 0.25.0
4
4
  Summary: A strict, zero-dependency CLI framework for Python
5
5
  Project-URL: Homepage, https://github.com/smm-h/strictcli
6
6
  Project-URL: Repository, https://github.com/smm-h/strictcli
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "strictcli",
9
- "version": "0.24.0",
9
+ "version": "0.25.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "A strict, zero-dependency CLI framework for Python (npm wrapper)",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "strictcli"
7
- version = "0.24.0"
7
+ version = "0.25.0"
8
8
  description = "A strict, zero-dependency CLI framework for Python"
9
9
  readme = "README.md"
10
10
  license = "MIT"