launchline 0.2.1__tar.gz → 0.3.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.
- launchline-0.3.0/.github/dependabot.yml +6 -0
- launchline-0.3.0/.github/workflows/ci.yml +64 -0
- launchline-0.3.0/.github/workflows/release.yml +231 -0
- {launchline-0.2.1 → launchline-0.3.0}/.gitignore +7 -0
- launchline-0.3.0/.semrelrc +21 -0
- {launchline-0.2.1 → launchline-0.3.0}/PKG-INFO +17 -4
- {launchline-0.2.1 → launchline-0.3.0}/README.md +15 -2
- launchline-0.3.0/docs/compatibility.md +31 -0
- launchline-0.3.0/docs/releasing.md +40 -0
- launchline-0.3.0/docs/validation/baseline.json +31 -0
- launchline-0.3.0/docs/validation/benchmark.py +45 -0
- launchline-0.3.0/docs/validation/current.json +31 -0
- launchline-0.3.0/docs/validation/hosted-ci.md +22 -0
- launchline-0.3.0/docs/validation/mutation.json +74 -0
- launchline-0.3.0/docs/validation/package-smoke.json +37 -0
- launchline-0.3.0/docs/validation/parity.json +77 -0
- launchline-0.3.0/docs/validation/report.md +70 -0
- launchline-0.3.0/docs/validation/search-comparison.json +152 -0
- launchline-0.3.0/docs/validation/semantic-release-dry-run.json +16 -0
- launchline-0.3.0/docs/validation/summary.json +69 -0
- {launchline-0.2.1 → launchline-0.3.0}/pyproject.toml +38 -5
- launchline-0.3.0/scripts/benchmark_compare.py +64 -0
- launchline-0.3.0/scripts/mutation_check.py +188 -0
- launchline-0.3.0/scripts/package_smoke.py +63 -0
- launchline-0.3.0/scripts/parity_check.py +70 -0
- launchline-0.3.0/scripts/prepare_semrel.py +49 -0
- launchline-0.3.0/scripts/release_dryrun.py +123 -0
- launchline-0.3.0/scripts/release_version.py +28 -0
- launchline-0.3.0/scripts/semantic-release-plugins.json +98 -0
- launchline-0.3.0/src/launchline/__main__.py +130 -0
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/cli.py +25 -3
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/config.py +104 -75
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/exceptions.py +5 -1
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/fuzzy.py +22 -18
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/keys.py +66 -18
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/runner.py +4 -1
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/ui.py +37 -10
- {launchline-0.2.1 → launchline-0.3.0}/tests/conftest.py +31 -0
- launchline-0.3.0/tests/fixtures/fuzzy.tsv +405 -0
- launchline-0.3.0/tests/test_application.py +136 -0
- launchline-0.3.0/tests/test_contract.py +240 -0
- {launchline-0.2.1 → launchline-0.3.0}/tests/test_cross_platform.py +9 -7
- launchline-0.3.0/tests/test_keys_unix.py +153 -0
- launchline-0.3.0/tests/test_release_policy.py +109 -0
- launchline-0.3.0/tests/test_release_version.py +40 -0
- launchline-0.3.0/tests/test_terminal_e2e.py +213 -0
- {launchline-0.2.1 → launchline-0.3.0}/tests/test_ui.py +1 -2
- {launchline-0.2.1 → launchline-0.3.0}/uv.lock +124 -112
- launchline-0.2.1/.generated-go-semantic-release-changelog.md +0 -18
- launchline-0.2.1/.github/workflows/ci.yml +0 -40
- launchline-0.2.1/.github/workflows/release.yml +0 -81
- launchline-0.2.1/.semrel/linux_amd64/changelog-generator-default/1.15.0/changelog-generator-default_v1.15.0_linux_amd64 +0 -0
- launchline-0.2.1/.semrel/linux_amd64/commit-analyzer-default/1.15.0/commit-analyzer-cz_v1.15.0_linux_amd64 +0 -0
- launchline-0.2.1/.semrel/linux_amd64/condition-github/1.11.0/condition-github_v1.11.0_linux_amd64 +0 -0
- launchline-0.2.1/.semrel/linux_amd64/files-updater-npm/1.11.0/files-updater-npm_v1.11.0_linux_amd64 +0 -0
- launchline-0.2.1/.semrel/linux_amd64/provider-github/1.19.0/provider-github_v1.19.0_linux_amd64 +0 -0
- launchline-0.2.1/src/launchline/__main__.py +0 -109
- {launchline-0.2.1 → launchline-0.3.0}/.markdownlint.json +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/.python-version +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/CHANGELOG.md +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/LICENSE +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/assets/launchline.ico +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/assets/launchline.png +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/assets/launchline.svg +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/docs/images/demo.gif +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/docs/images/hero.png +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/docs/images/navigate.png +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/docs/images/search.png +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/__init__.py +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/src/launchline/assets/launchline.ico +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/tests/__init__.py +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/tests/test_cli.py +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/tests/test_config.py +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/tests/test_runner.py +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/tools/generate_icon.py +0 -0
- {launchline-0.2.1 → launchline-0.3.0}/tools/generate_screenshots.py +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
'on':
|
|
3
|
+
workflow_call: null
|
|
4
|
+
workflow_dispatch: null
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
pull_request:
|
|
9
|
+
branches:
|
|
10
|
+
- main
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
jobs:
|
|
17
|
+
check:
|
|
18
|
+
name: Python (${{ matrix.os }}, ${{ matrix.python }})
|
|
19
|
+
runs-on: ${{ matrix.os }}
|
|
20
|
+
timeout-minutes: 15
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
os:
|
|
25
|
+
- ubuntu-latest
|
|
26
|
+
- windows-latest
|
|
27
|
+
- macos-latest
|
|
28
|
+
python:
|
|
29
|
+
- '3.12'
|
|
30
|
+
- '3.13'
|
|
31
|
+
- '3.14'
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
37
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
|
|
38
|
+
with:
|
|
39
|
+
python-version: ${{ matrix.python }}
|
|
40
|
+
- run: uv sync --locked --group dev
|
|
41
|
+
- run: uv run poe check
|
|
42
|
+
- run: uv build
|
|
43
|
+
- run: uv run python scripts/package_smoke.py
|
|
44
|
+
- run: uv run poe mutation
|
|
45
|
+
if: matrix.os == 'windows-latest'
|
|
46
|
+
- name: Test results
|
|
47
|
+
if: always()
|
|
48
|
+
uses: mikepenz/action-junit-report@6ef3fdcfcd5f0b5d14df3e659ef19b8c6820f93d
|
|
49
|
+
with:
|
|
50
|
+
report_paths: junit.xml
|
|
51
|
+
annotate_only: true
|
|
52
|
+
check_annotations: false
|
|
53
|
+
check_retries: false
|
|
54
|
+
require_tests: true
|
|
55
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
|
56
|
+
if: always()
|
|
57
|
+
with:
|
|
58
|
+
name: evidence-${{ matrix.os }}-${{ matrix.python }}
|
|
59
|
+
path: 'junit.xml
|
|
60
|
+
|
|
61
|
+
docs/validation/*json
|
|
62
|
+
|
|
63
|
+
'
|
|
64
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
'on':
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
force-patch:
|
|
9
|
+
description: Force patch bump when no releasable commits exist
|
|
10
|
+
type: boolean
|
|
11
|
+
default: false
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
concurrency:
|
|
15
|
+
group: release
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
jobs:
|
|
18
|
+
target:
|
|
19
|
+
if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill'
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 20
|
|
22
|
+
outputs:
|
|
23
|
+
version: ${{ steps.semrel.outputs.version }}
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
fetch-depth: 0
|
|
29
|
+
ref: ${{ github.sha }}
|
|
30
|
+
- name: Download checksum-pinned semantic-release CLI
|
|
31
|
+
shell: bash
|
|
32
|
+
run: "set -euo pipefail\ndest=\"$RUNNER_TEMP/semantic-release-v2.31.0\"\ncurl --fail --silent --show-error --location\
|
|
33
|
+
\ --proto '=https' --tlsv1.2 \\\n https://github.com/go-semantic-release/semantic-release/releases/download/v2.31.0/semantic-release_v2.31.0_linux_amd64\
|
|
34
|
+
\ \\\n --output \"$dest\"\nprintf '%s %s\\n' 'b8f518b1aeb1d1742f4e6e91a179b707cea4309a64fe3854f465bf724f04f6d3'\
|
|
35
|
+
\ \"$dest\" | sha256sum --check --strict\nchmod 0755 \"$dest\"\n"
|
|
36
|
+
- name: Preload verified runtime plugins
|
|
37
|
+
run: python3 scripts/prepare_semrel.py
|
|
38
|
+
- name: Determine version without publishing
|
|
39
|
+
id: semrel
|
|
40
|
+
shell: bash
|
|
41
|
+
env:
|
|
42
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
43
|
+
FORCE_PATCH: ${{ inputs.force-patch || false }}
|
|
44
|
+
run: 'set -euo pipefail
|
|
45
|
+
|
|
46
|
+
args=(--dry --allow-initial-development-versions --changelog release-changelog.md --allow-no-changes --plugin-resolver-disable-batch-prefetch)
|
|
47
|
+
|
|
48
|
+
if [ "$FORCE_PATCH" = true ]; then args+=(--force-bump-patch-version); fi
|
|
49
|
+
|
|
50
|
+
"$RUNNER_TEMP/semantic-release-v2.31.0" "${args[@]}"
|
|
51
|
+
|
|
52
|
+
python3 scripts/release_version.py
|
|
53
|
+
|
|
54
|
+
'
|
|
55
|
+
name: Target
|
|
56
|
+
ci:
|
|
57
|
+
name: Python (${{ matrix.os }}, ${{ matrix.python }})
|
|
58
|
+
runs-on: ${{ matrix.os }}
|
|
59
|
+
timeout-minutes: 15
|
|
60
|
+
strategy:
|
|
61
|
+
fail-fast: false
|
|
62
|
+
matrix:
|
|
63
|
+
os:
|
|
64
|
+
- ubuntu-latest
|
|
65
|
+
- windows-latest
|
|
66
|
+
- macos-latest
|
|
67
|
+
python:
|
|
68
|
+
- '3.12'
|
|
69
|
+
- '3.13'
|
|
70
|
+
- '3.14'
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
|
73
|
+
with:
|
|
74
|
+
persist-credentials: false
|
|
75
|
+
ref: ${{ github.sha }}
|
|
76
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
|
|
77
|
+
with:
|
|
78
|
+
python-version: ${{ matrix.python }}
|
|
79
|
+
- run: uv sync --locked --group dev
|
|
80
|
+
- run: uv run poe check
|
|
81
|
+
- run: uv build
|
|
82
|
+
- run: uv run python scripts/package_smoke.py
|
|
83
|
+
- run: uv run poe mutation
|
|
84
|
+
if: matrix.os == 'windows-latest'
|
|
85
|
+
- name: Test results
|
|
86
|
+
if: always()
|
|
87
|
+
uses: mikepenz/action-junit-report@6ef3fdcfcd5f0b5d14df3e659ef19b8c6820f93d
|
|
88
|
+
with:
|
|
89
|
+
report_paths: junit.xml
|
|
90
|
+
annotate_only: true
|
|
91
|
+
check_annotations: false
|
|
92
|
+
check_retries: false
|
|
93
|
+
require_tests: true
|
|
94
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
|
95
|
+
if: always()
|
|
96
|
+
with:
|
|
97
|
+
name: evidence-${{ matrix.os }}-${{ matrix.python }}
|
|
98
|
+
path: 'junit.xml
|
|
99
|
+
|
|
100
|
+
docs/validation/*json
|
|
101
|
+
|
|
102
|
+
'
|
|
103
|
+
if-no-files-found: error
|
|
104
|
+
needs: target
|
|
105
|
+
if: needs.target.outputs.version != ''
|
|
106
|
+
build:
|
|
107
|
+
needs:
|
|
108
|
+
- target
|
|
109
|
+
- ci
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
timeout-minutes: 20
|
|
112
|
+
outputs:
|
|
113
|
+
artifact-id: ${{ steps.dist.outputs.artifact-id }}
|
|
114
|
+
steps:
|
|
115
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
|
116
|
+
with:
|
|
117
|
+
persist-credentials: false
|
|
118
|
+
fetch-depth: 0
|
|
119
|
+
ref: ${{ github.sha }}
|
|
120
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
|
|
121
|
+
- name: Build tested version without publishing credentials
|
|
122
|
+
run: '[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 1
|
|
123
|
+
|
|
124
|
+
uv sync --locked --group dev
|
|
125
|
+
|
|
126
|
+
uv run poe check
|
|
127
|
+
|
|
128
|
+
python3 - <<''PY''
|
|
129
|
+
|
|
130
|
+
import os,pathlib
|
|
131
|
+
|
|
132
|
+
p=pathlib.Path(''README.md'')
|
|
133
|
+
|
|
134
|
+
p.write_text(p.read_text().replace(''(docs/'', ''(https://raw.githubusercontent.com/''+os.environ[''GITHUB_REPOSITORY'']+''/v''+os.environ[''VERSION'']+''/docs/''))
|
|
135
|
+
|
|
136
|
+
PY
|
|
137
|
+
|
|
138
|
+
uv build
|
|
139
|
+
|
|
140
|
+
uv run python scripts/package_smoke.py
|
|
141
|
+
|
|
142
|
+
printf ''Built from reviewed main commit %s\n'' "$GITHUB_SHA" > release-notes.md
|
|
143
|
+
|
|
144
|
+
'
|
|
145
|
+
env:
|
|
146
|
+
VERSION: ${{ needs.target.outputs.version }}
|
|
147
|
+
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.target.outputs.version }}
|
|
148
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
|
149
|
+
id: dist
|
|
150
|
+
with:
|
|
151
|
+
name: trusted-release
|
|
152
|
+
path: 'dist/*
|
|
153
|
+
|
|
154
|
+
release-notes.md'
|
|
155
|
+
if-no-files-found: error
|
|
156
|
+
retention-days: 7
|
|
157
|
+
name: Build
|
|
158
|
+
github-release:
|
|
159
|
+
if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.build.result
|
|
160
|
+
== 'success'
|
|
161
|
+
needs:
|
|
162
|
+
- target
|
|
163
|
+
- build
|
|
164
|
+
- attest
|
|
165
|
+
runs-on: ubuntu-latest
|
|
166
|
+
timeout-minutes: 20
|
|
167
|
+
permissions:
|
|
168
|
+
contents: write # Create immutable refs and upload tested release artifacts.
|
|
169
|
+
steps:
|
|
170
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
|
|
171
|
+
with:
|
|
172
|
+
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
173
|
+
path: release
|
|
174
|
+
- name: Publish same-run artifacts from trusted merge
|
|
175
|
+
run: "# The approved immutable-v* ruleset must already prohibit tag updates/deletion.\nancestor=$(gh api \"repos/$GITHUB_REPOSITORY/compare/$GITHUB_SHA...main\"\
|
|
176
|
+
\ --jq .merge_base_commit.sha)\ntest \"$ancestor\" = \"$GITHUB_SHA\" || { echo 'Release commit is no longer on main.'\
|
|
177
|
+
\ >&2; exit 1; }\npython3 - <<'PY'\nimport json,os,pathlib,re\nassert re.fullmatch(r'v[0-9]+\\.[0-9]+\\.[0-9]+',os.environ['TAG'])\n\
|
|
178
|
+
assert re.fullmatch(r'[0-9a-f]{40}',os.environ['GITHUB_SHA'])\npathlib.Path('release-ref.json').write_text(json.dumps({'ref':'refs/tags/'+os.environ['TAG'],'sha':os.environ['GITHUB_SHA']}))\n\
|
|
179
|
+
PY\n# Ref creation is atomic: if another process created it first, accept only the same commit.\nif ! gh api --method\
|
|
180
|
+
\ POST \"repos/$GITHUB_REPOSITORY/git/refs\" --input release-ref.json >/dev/null 2>tag-create-error.txt; then\n actual=$(gh\
|
|
181
|
+
\ api \"repos/$GITHUB_REPOSITORY/commits/$TAG\" --jq .sha)\n test \"$actual\" = \"$GITHUB_SHA\" || { echo 'Tag creation\
|
|
182
|
+
\ failed or a conflicting tag exists.' >&2; exit 1; }\nfi\nactual=$(gh api \"repos/$GITHUB_REPOSITORY/commits/$TAG\"\
|
|
183
|
+
\ --jq .sha)\ntest \"$actual\" = \"$GITHUB_SHA\" || { echo 'Tag does not match the trusted merge.' >&2; exit 1; }\n\
|
|
184
|
+
if gh release view \"$TAG\" --repo \"$GITHUB_REPOSITORY\" >/dev/null 2>&1; then\n :\nelse\n gh release create \"\
|
|
185
|
+
$TAG\" --repo \"$GITHUB_REPOSITORY\" --verify-tag --title \"$TAG\" --generate-notes --notes-file release/release-notes.md\n\
|
|
186
|
+
fi\ngh release upload \"$TAG\" release/dist/* --repo \"$GITHUB_REPOSITORY\" --clobber\n"
|
|
187
|
+
env:
|
|
188
|
+
GH_TOKEN: ${{ github.token }}
|
|
189
|
+
TAG: v${{ needs.target.outputs.version }}
|
|
190
|
+
name: Github Release
|
|
191
|
+
pypi:
|
|
192
|
+
if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.github-release.result
|
|
193
|
+
== 'success'
|
|
194
|
+
needs:
|
|
195
|
+
- target
|
|
196
|
+
- build
|
|
197
|
+
- github-release
|
|
198
|
+
runs-on: ubuntu-latest
|
|
199
|
+
timeout-minutes: 20
|
|
200
|
+
environment: pypi
|
|
201
|
+
permissions:
|
|
202
|
+
id-token: write # OIDC identity for attestation or trusted PyPI publishing.
|
|
203
|
+
steps:
|
|
204
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
|
|
205
|
+
with:
|
|
206
|
+
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
207
|
+
path: release
|
|
208
|
+
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086
|
|
209
|
+
with:
|
|
210
|
+
packages-dir: release/dist/
|
|
211
|
+
skip-existing: true
|
|
212
|
+
name: Pypi
|
|
213
|
+
attest:
|
|
214
|
+
name: Attest tested artifacts
|
|
215
|
+
needs: build
|
|
216
|
+
if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.build.result
|
|
217
|
+
== 'success'
|
|
218
|
+
runs-on: ubuntu-latest
|
|
219
|
+
timeout-minutes: 10
|
|
220
|
+
permissions:
|
|
221
|
+
contents: read
|
|
222
|
+
id-token: write # OIDC identity for attestation or trusted PyPI publishing.
|
|
223
|
+
attestations: write # Store provenance for these same-run build artifacts.
|
|
224
|
+
steps:
|
|
225
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
|
|
226
|
+
with:
|
|
227
|
+
path: release
|
|
228
|
+
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
229
|
+
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6
|
|
230
|
+
with:
|
|
231
|
+
subject-path: release/dist/*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": {
|
|
3
|
+
"provider": {
|
|
4
|
+
"name": "github@1.19.0"
|
|
5
|
+
},
|
|
6
|
+
"commit-analyzer": {
|
|
7
|
+
"name": "default@1.15.0"
|
|
8
|
+
},
|
|
9
|
+
"changelog-generator": {
|
|
10
|
+
"name": "default@1.15.0"
|
|
11
|
+
},
|
|
12
|
+
"ci-condition": {
|
|
13
|
+
"name": "default@1.11.0"
|
|
14
|
+
},
|
|
15
|
+
"files-updater": {
|
|
16
|
+
"names": [
|
|
17
|
+
"npm@1.11.0"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: launchline
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A lightweight terminal launcher for interactive CLI tools.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mikejhill/launchline
|
|
6
6
|
Project-URL: Repository, https://github.com/mikejhill/launchline
|
|
@@ -31,7 +31,7 @@ A lightweight terminal launcher for interactive CLI tools. Define your frequentl
|
|
|
31
31
|
used commands in a TOML config file, then pick them from a fuzzy-searchable menu
|
|
32
32
|
instead of remembering flags and paths.
|
|
33
33
|
|
|
34
|
-

|
|
35
35
|
|
|
36
36
|
## Why
|
|
37
37
|
|
|
@@ -52,7 +52,7 @@ instead.
|
|
|
52
52
|
|
|
53
53
|
| Fuzzy search | Arrow-key navigation |
|
|
54
54
|
| :-------------------------------: | :-----------------------------------: |
|
|
55
|
-
|  |  |
|
|
56
56
|
|
|
57
57
|
## Installation
|
|
58
58
|
|
|
@@ -215,3 +215,16 @@ uv run mypy src
|
|
|
215
215
|
## License
|
|
216
216
|
|
|
217
217
|
MIT
|
|
218
|
+
|
|
219
|
+
## See Also
|
|
220
|
+
|
|
221
|
+
- **[LaunchLine (Rust)](https://github.com/mikejhill/launchline-rust)** — a Rust
|
|
222
|
+
implementation with the shared configuration and keyboard contract,
|
|
223
|
+
ships as a single native binary.
|
|
224
|
+
|
|
225
|
+
## Compatibility and validation
|
|
226
|
+
|
|
227
|
+
Use `launchline --check-config` to validate configuration without opening the menu.
|
|
228
|
+
Set `NO_COLOR` for a selection marker without ANSI color. See the
|
|
229
|
+
[compatibility matrix](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/compatibility.md), [reproducible test and performance
|
|
230
|
+
results](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/validation/report.md), and [release readiness](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/releasing.md).
|
|
@@ -9,7 +9,7 @@ A lightweight terminal launcher for interactive CLI tools. Define your frequentl
|
|
|
9
9
|
used commands in a TOML config file, then pick them from a fuzzy-searchable menu
|
|
10
10
|
instead of remembering flags and paths.
|
|
11
11
|
|
|
12
|
-

|
|
13
13
|
|
|
14
14
|
## Why
|
|
15
15
|
|
|
@@ -30,7 +30,7 @@ instead.
|
|
|
30
30
|
|
|
31
31
|
| Fuzzy search | Arrow-key navigation |
|
|
32
32
|
| :-------------------------------: | :-----------------------------------: |
|
|
33
|
-
|  |  |
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
36
36
|
|
|
@@ -193,3 +193,16 @@ uv run mypy src
|
|
|
193
193
|
## License
|
|
194
194
|
|
|
195
195
|
MIT
|
|
196
|
+
|
|
197
|
+
## See Also
|
|
198
|
+
|
|
199
|
+
- **[LaunchLine (Rust)](https://github.com/mikejhill/launchline-rust)** — a Rust
|
|
200
|
+
implementation with the shared configuration and keyboard contract,
|
|
201
|
+
ships as a single native binary.
|
|
202
|
+
|
|
203
|
+
## Compatibility and validation
|
|
204
|
+
|
|
205
|
+
Use `launchline --check-config` to validate configuration without opening the menu.
|
|
206
|
+
Set `NO_COLOR` for a selection marker without ANSI color. See the
|
|
207
|
+
[compatibility matrix](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/compatibility.md), [reproducible test and performance
|
|
208
|
+
results](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/validation/report.md), and [release readiness](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/releasing.md).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Shared compatibility contract
|
|
2
|
+
|
|
3
|
+
Baseline: Python `904ace1`, Rust `109a68b`. Modernized together on MHILL-LAPTOP.
|
|
4
|
+
This matrix records the supported contract and evidence, not universal parity.
|
|
5
|
+
|
|
6
|
+
| Surface | Baseline gap | Implemented contract | Evidence |
|
|
7
|
+
| --- | --- | --- | --- |
|
|
8
|
+
| Configuration | Python coerced types; Rust discarded invalid args/env | Strict strings, booleans, string arrays/maps; reject controls/NUL and illegal environment keys | Both unit suites; 15 differential real CLI cases in validation/parity.json |
|
|
9
|
+
| Resolution | Same CLI > LAUNCHLINE_CONFIG > default precedence | Explicit missing paths fail; exclusive default creation; missing/file cwd warns and inherits | Config tests; actual CLI and child processes |
|
|
10
|
+
| Search | Equivalent greedy subsequence scoring | Identical score bonuses and stable ties; monotonic search avoids tail scans | Shared 405-case ASCII/Unicode score corpus; paired benchmarks |
|
|
11
|
+
| Numeric selection | Python non-ASCII digit crash | ASCII digits only, existing prefix and shortcut semantics | Keyboard and mutation tests |
|
|
12
|
+
| Editing | Rust UTF-8 byte cursor could panic | Character boundary editing; Emacs keys, arrows, Escape clear/quit | Rust unit UX; Python decoder/UI tests; real PTY flows |
|
|
13
|
+
| CLI | Rust accepted arbitrary log levels; Python config-path tracebacks | Same five uppercase levels; friendly failures; --check-config validates without opening TUI; non-TTY guard | Actual subprocess and wheel smoke tests |
|
|
14
|
+
| Launch | Rust cloned entire environment; POSIX status representation differed | Literal argv, inherited env plus overlay, cwd, child exit code; interrupted child 130; POSIX signal 128+signal | Actual child probe; Python Linux SIGTERM test; Rust Unix test added but not run locally |
|
|
15
|
+
| Terminal | Windows inherited ignore flag; Unix queued input loss | Enable own Ctrl+C; restore terminal; preserve queued UTF-8 input; stop on EOF/read error | Windows PTY both; Linux PTY Python; faithful OS-boundary simulations |
|
|
16
|
+
| Accessibility | Color-only emphasis | NO_COLOR suppresses SGR with visible selection marker; keyboard navigation and resize | PTY output/assertions both; no screen-reader certification |
|
|
17
|
+
| Distribution | Rust archives lacked icons; wrapper used wrong version metadata | Bundle icons; wrapper uses launchline-rust version, verifies SHA256SUMS before atomic cache install | Built native archive and isolated installed wheel; tamper/interruption tests |
|
|
18
|
+
| Release | Python #1 pending; Rust broad permissions | Preserve #1 pin/generated notes; owner + rerun owner gates, exact SHA, read-only build, isolated publishers, provenance | actionlint, zizmor auditor, mocked actual publisher body, no-remote semantic-release fixture |
|
|
19
|
+
|
|
20
|
+
Unknown TOML keys remain ignored for compatibility. Shell expansion is not supported.
|
|
21
|
+
Portable home expansion is `~`/`~/...`; Python also recognizes OS-supported named-user
|
|
22
|
+
forms. Diagnostic wording, argparse/clap help formatting, platform process lookup,
|
|
23
|
+
batch execution, Unicode database versions, terminal escape timing and font widths
|
|
24
|
+
can differ. Cursor accounting uses code points rather than grapheme/display columns;
|
|
25
|
+
wide/combining glyph layout is not claimed equivalent. Python needs CPython >=3.12;
|
|
26
|
+
Rust is native, with a separate Python >=3.9 download wrapper. Binary and interpreter
|
|
27
|
+
startup/memory costs therefore differ. No maximum-performance claim is made.
|
|
28
|
+
|
|
29
|
+
Executed: Windows x64 both; Ubuntu/WSL Python. macOS, ARM targets, Rust Linux/macOS,
|
|
30
|
+
physical terminal/screen-reader combinations and hosted CI have not been executed
|
|
31
|
+
here. See [validation report](validation/report.md) and [release readiness](releasing.md).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Release readiness
|
|
2
|
+
|
|
3
|
+
Trusted owner merges to main trigger conventional-commit version calculation.
|
|
4
|
+
Read-only jobs test/build the exact merge SHA before separate publishers consume
|
|
5
|
+
same-run artifacts. Both actor and rerun initiator must be mikejhill. Publisher
|
|
6
|
+
jobs do not check out repository code; write/OIDC permissions are job-scoped.
|
|
7
|
+
Artifacts receive provenance. Conflicting tags fail; generated GitHub notes are
|
|
8
|
+
retained. Release publication remains a production action and was not invoked.
|
|
9
|
+
|
|
10
|
+
The go-semantic-release CLI is pinned to 2.31.0 with a verified checksum; exact
|
|
11
|
+
runtime plugin versions and Linux/Windows checksums are in
|
|
12
|
+
scripts/semantic-release-plugins.json. prepare_semrel.py verifies/preloads them.
|
|
13
|
+
release_version.py validates the generated changelog header (the CLI dry-run does
|
|
14
|
+
not produce its version-file output). The disposable fixture script removes token
|
|
15
|
+
variables, has no remote and verifies no tags; its evidence is in validation/.
|
|
16
|
+
It exercises the Git provider, not the production GitHub provider or PyPI upload.
|
|
17
|
+
Publisher policy tests execute the real workflow shell body with an intercepting
|
|
18
|
+
gh shell function; no requests reach GitHub.
|
|
19
|
+
|
|
20
|
+
Current VerdiClip workflow was read as the reference; Python PR #1 at
|
|
21
|
+
96d72c1e157e57e1b1aa9c5ad1dfbf51aaa606ab was inspected and its CLI pin,
|
|
22
|
+
immutable tag conflict check, owner/rerun gates and generated notes preserved
|
|
23
|
+
selectively. Neither PR nor production settings were merged or modified.
|
|
24
|
+
|
|
25
|
+
## Remaining setup (owner action required)
|
|
26
|
+
|
|
27
|
+
Read-only inspection found Python's existing pypi environment still has a required
|
|
28
|
+
reviewer and no deployment branch policy. Rust's pypi environment returned 404
|
|
29
|
+
(absent). PyPI trusted-publisher identities could not be verified. Thus production
|
|
30
|
+
approval-free PyPI delivery is NOT ready. Before rollout, establish/verify main-only
|
|
31
|
+
environment restrictions and the PyPI identity for the corresponding repository,
|
|
32
|
+
release.yml and pypi environment. Any removal of the existing reviewer requires
|
|
33
|
+
separate authorization; this task deliberately leaves the gate unchanged. Verify
|
|
34
|
+
version-tag protection and GitHub attestation availability as well. No credentials
|
|
35
|
+
were created and no repository security setting was changed.
|
|
36
|
+
|
|
37
|
+
To reproduce a safe version fixture, explicitly set SEMREL_BIN to the verified CLI
|
|
38
|
+
and run scripts/release_dryrun.py through the repository's uv environment. Do not
|
|
39
|
+
substitute a production provider or run publication commands as a dry run. A failed
|
|
40
|
+
production upload should be retried on its original run; never move an existing tag.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"search_ns": [
|
|
3
|
+
101495.7,
|
|
4
|
+
100916.65,
|
|
5
|
+
99897.25,
|
|
6
|
+
100382.7,
|
|
7
|
+
100143.6,
|
|
8
|
+
100576.2,
|
|
9
|
+
101188.25
|
|
10
|
+
],
|
|
11
|
+
"search_median_ns": 100576.2,
|
|
12
|
+
"help_ms": [
|
|
13
|
+
200.2209,
|
|
14
|
+
193.2263,
|
|
15
|
+
193.9891,
|
|
16
|
+
185.2878,
|
|
17
|
+
182.4954,
|
|
18
|
+
193.7004,
|
|
19
|
+
188.1265,
|
|
20
|
+
193.9806,
|
|
21
|
+
188.3141,
|
|
22
|
+
213.0736,
|
|
23
|
+
196.606,
|
|
24
|
+
187.4159,
|
|
25
|
+
194.0006,
|
|
26
|
+
184.4261,
|
|
27
|
+
189.1814
|
|
28
|
+
],
|
|
29
|
+
"help_median_ms": 193.2263,
|
|
30
|
+
"python": "3.12.13 (main, Mar 20 2026, 00:36:00) [MSC v.1944 64 bit (AMD64)]"
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Reproduce search microbenchmarks and CLI process startup samples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import statistics
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from launchline.fuzzy import FuzzyMatcher
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Benchmark:
|
|
14
|
+
"""Record medians without pass/fail timing thresholds."""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def main() -> None:
|
|
18
|
+
"""Write raw samples for a short match followed by a long tail."""
|
|
19
|
+
samples = []
|
|
20
|
+
for _ in range(7):
|
|
21
|
+
start = time.perf_counter_ns()
|
|
22
|
+
for _ in range(2000):
|
|
23
|
+
assert FuzzyMatcher.score("abc", "abc" + "x" * 4096) == 10
|
|
24
|
+
samples.append((time.perf_counter_ns() - start) / 2000)
|
|
25
|
+
startup = []
|
|
26
|
+
for _ in range(15):
|
|
27
|
+
start = time.perf_counter_ns()
|
|
28
|
+
subprocess.run(
|
|
29
|
+
[sys.executable, "-m", "launchline", "--help"],
|
|
30
|
+
check=True,
|
|
31
|
+
capture_output=True,
|
|
32
|
+
)
|
|
33
|
+
startup.append((time.perf_counter_ns() - start) / 1e6)
|
|
34
|
+
result = {
|
|
35
|
+
"search_ns": samples,
|
|
36
|
+
"search_median_ns": statistics.median(samples),
|
|
37
|
+
"help_ms": startup,
|
|
38
|
+
"help_median_ms": statistics.median(startup),
|
|
39
|
+
"python": sys.version,
|
|
40
|
+
}
|
|
41
|
+
Path(sys.argv[1]).write_text(json.dumps(result, indent=2), encoding="utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
Benchmark.main()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"search_ns": [
|
|
3
|
+
1389.75,
|
|
4
|
+
1386.6,
|
|
5
|
+
1255.0,
|
|
6
|
+
1225.6,
|
|
7
|
+
1257.05,
|
|
8
|
+
1271.6,
|
|
9
|
+
1237.85
|
|
10
|
+
],
|
|
11
|
+
"search_median_ns": 1257.05,
|
|
12
|
+
"help_ms": [
|
|
13
|
+
198.415,
|
|
14
|
+
184.8518,
|
|
15
|
+
187.7005,
|
|
16
|
+
186.6807,
|
|
17
|
+
193.6874,
|
|
18
|
+
186.1085,
|
|
19
|
+
188.476,
|
|
20
|
+
185.1123,
|
|
21
|
+
192.4172,
|
|
22
|
+
189.8451,
|
|
23
|
+
183.0751,
|
|
24
|
+
181.1014,
|
|
25
|
+
184.7011,
|
|
26
|
+
194.8148,
|
|
27
|
+
183.4375
|
|
28
|
+
],
|
|
29
|
+
"help_median_ms": 186.6807,
|
|
30
|
+
"python": "3.12.13 (main, Mar 20 2026, 00:36:00) [MSC v.1944 64 bit (AMD64)]"
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Hosted CI follow-up
|
|
2
|
+
|
|
3
|
+
Draft PR: https://github.com/mikejhill/launchline/pull/2
|
|
4
|
+
|
|
5
|
+
Mike authorized non-force pushes and draft PRs after the local validation snapshot.
|
|
6
|
+
Python's initial hosted run 39871242844 passed all nine Windows/Linux/macOS and
|
|
7
|
+
Python 3.12/3.13/3.14 combinations. That run checked GitHub's generated merge SHA;
|
|
8
|
+
CI now explicitly checks out pull_request.head.sha so subsequent PR runs execute
|
|
9
|
+
the exact proposed source commit. Main/release checkout semantics are unchanged.
|
|
10
|
+
|
|
11
|
+
Rust's initial run 39871247740 exposed two portable-test issues: macOS temp-directory
|
|
12
|
+
aliases (/var versus /private/var), and a Windows test that mocked global os.name
|
|
13
|
+
on Unix, thereby changing pathlib behavior. The child cwd assertion now compares
|
|
14
|
+
canonical directory identities while still asserting literal argv/environment.
|
|
15
|
+
Wrapper platform mocks affect only the wrapper's os reference, and LOCALAPPDATA
|
|
16
|
+
no longer eagerly evaluates an unused home fallback. No test was disabled.
|
|
17
|
+
|
|
18
|
+
The PR checks provide final run status and commit identities; the accompanying
|
|
19
|
+
report.md and summary.json retain the earlier local validation snapshot. Native
|
|
20
|
+
Linux x64, Windows x64 and macOS ARM64 are in Rust PR CI. Release-only Linux ARM64,
|
|
21
|
+
Windows ARM64 and macOS x64 targets are not exercised by this PR matrix. Production
|
|
22
|
+
publishing, repository security settings and credentials remain untouched.
|