launchline 0.2.1.dev0__tar.gz → 0.4.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.4.0/.github/dependabot.yml +6 -0
- launchline-0.4.0/.github/workflows/ci.yml +67 -0
- launchline-0.4.0/.github/workflows/release.yml +234 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/.gitignore +7 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/.markdownlint.json +1 -0
- launchline-0.4.0/.semrelrc +21 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/CHANGELOG.md +24 -3
- {launchline-0.2.1.dev0 → launchline-0.4.0}/PKG-INFO +47 -22
- {launchline-0.2.1.dev0 → launchline-0.4.0}/README.md +45 -20
- launchline-0.4.0/docs/compatibility.md +39 -0
- launchline-0.4.0/docs/menus.md +135 -0
- launchline-0.4.0/docs/releasing.md +46 -0
- launchline-0.4.0/docs/validation/baseline.json +31 -0
- launchline-0.4.0/docs/validation/benchmark.py +45 -0
- launchline-0.4.0/docs/validation/current.json +31 -0
- launchline-0.4.0/docs/validation/dependency-alerts.json +814 -0
- launchline-0.4.0/docs/validation/dependency-triage.md +33 -0
- launchline-0.4.0/docs/validation/hosted-ci.md +22 -0
- launchline-0.4.0/docs/validation/menu-checkpoint.md +103 -0
- launchline-0.4.0/docs/validation/menu-mutation.json +56 -0
- launchline-0.4.0/docs/validation/menu-packages.json +12 -0
- launchline-0.4.0/docs/validation/menu-performance.json +38 -0
- launchline-0.4.0/docs/validation/menu-protocol-limit.json +51 -0
- launchline-0.4.0/docs/validation/mutation.json +74 -0
- launchline-0.4.0/docs/validation/package-smoke.json +40 -0
- launchline-0.4.0/docs/validation/parity.json +77 -0
- launchline-0.4.0/docs/validation/report.md +70 -0
- launchline-0.4.0/docs/validation/search-comparison.json +152 -0
- launchline-0.4.0/docs/validation/semantic-release-dry-run.json +16 -0
- launchline-0.4.0/docs/validation/summary.json +69 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/pyproject.toml +43 -7
- launchline-0.4.0/scripts/benchmark_compare.py +64 -0
- launchline-0.4.0/scripts/benchmark_menus.py +88 -0
- launchline-0.4.0/scripts/mutate_menus.py +207 -0
- launchline-0.4.0/scripts/mutation_check.py +196 -0
- launchline-0.4.0/scripts/package_smoke.py +122 -0
- launchline-0.4.0/scripts/parity_check.py +70 -0
- launchline-0.4.0/scripts/prepare_semrel.py +49 -0
- launchline-0.4.0/scripts/release_dryrun.py +123 -0
- launchline-0.4.0/scripts/release_version.py +28 -0
- launchline-0.4.0/scripts/semantic-release-plugins.json +98 -0
- launchline-0.4.0/src/launchline/__main__.py +138 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/cli.py +25 -3
- launchline-0.4.0/src/launchline/config.py +393 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/exceptions.py +5 -1
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/fuzzy.py +22 -18
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/keys.py +102 -24
- launchline-0.4.0/src/launchline/menus.py +284 -0
- launchline-0.4.0/src/launchline/providers.py +290 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/runner.py +4 -1
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/ui.py +111 -10
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tests/conftest.py +31 -0
- launchline-0.4.0/tests/fixtures/fuzzy.tsv +405 -0
- launchline-0.4.0/tests/fixtures/menu-contract.json +161 -0
- launchline-0.4.0/tests/fixtures/menu_provider.py +97 -0
- launchline-0.4.0/tests/test_application.py +142 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tests/test_config.py +49 -77
- launchline-0.4.0/tests/test_contract.py +240 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tests/test_cross_platform.py +114 -60
- launchline-0.4.0/tests/test_keys_unix.py +192 -0
- launchline-0.4.0/tests/test_menu_application.py +188 -0
- launchline-0.4.0/tests/test_menus.py +310 -0
- launchline-0.4.0/tests/test_providers.py +122 -0
- launchline-0.4.0/tests/test_release_policy.py +109 -0
- launchline-0.4.0/tests/test_release_version.py +40 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tests/test_runner.py +24 -10
- launchline-0.4.0/tests/test_terminal_e2e.py +525 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tests/test_ui.py +228 -112
- launchline-0.4.0/uv.lock +446 -0
- launchline-0.2.1.dev0/.generated-go-semantic-release-changelog.md +0 -23
- launchline-0.2.1.dev0/.github/workflows/ci.yml +0 -40
- launchline-0.2.1.dev0/.github/workflows/release.yml +0 -73
- launchline-0.2.1.dev0/.semrel/linux_amd64/changelog-generator-default/1.15.0/changelog-generator-default_v1.15.0_linux_amd64 +0 -0
- launchline-0.2.1.dev0/.semrel/linux_amd64/commit-analyzer-default/1.15.0/commit-analyzer-cz_v1.15.0_linux_amd64 +0 -0
- launchline-0.2.1.dev0/.semrel/linux_amd64/condition-github/1.11.0/condition-github_v1.11.0_linux_amd64 +0 -0
- launchline-0.2.1.dev0/.semrel/linux_amd64/files-updater-npm/1.11.0/files-updater-npm_v1.11.0_linux_amd64 +0 -0
- launchline-0.2.1.dev0/.semrel/linux_amd64/provider-github/1.19.0/provider-github_v1.19.0_linux_amd64 +0 -0
- launchline-0.2.1.dev0/src/launchline/__main__.py +0 -109
- launchline-0.2.1.dev0/src/launchline/config.py +0 -271
- launchline-0.2.1.dev0/uv.lock +0 -427
- {launchline-0.2.1.dev0 → launchline-0.4.0}/.python-version +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/LICENSE +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/assets/launchline.ico +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/assets/launchline.png +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/assets/launchline.svg +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/docs/images/demo.gif +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/docs/images/hero.png +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/docs/images/navigate.png +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/docs/images/search.png +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/__init__.py +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/src/launchline/assets/launchline.ico +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tests/__init__.py +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tests/test_cli.py +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tools/generate_icon.py +0 -0
- {launchline-0.2.1.dev0 → launchline-0.4.0}/tools/generate_screenshots.py +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
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: Check seeded menu mutations
|
|
47
|
+
run: uv run poe mutation-menus
|
|
48
|
+
if: matrix.os == 'windows-latest'
|
|
49
|
+
- name: Test results
|
|
50
|
+
if: always()
|
|
51
|
+
uses: mikepenz/action-junit-report@6ef3fdcfcd5f0b5d14df3e659ef19b8c6820f93d
|
|
52
|
+
with:
|
|
53
|
+
report_paths: junit.xml
|
|
54
|
+
annotate_only: true
|
|
55
|
+
check_annotations: false
|
|
56
|
+
check_retries: false
|
|
57
|
+
require_tests: true
|
|
58
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
|
59
|
+
if: always()
|
|
60
|
+
with:
|
|
61
|
+
name: evidence-${{ matrix.os }}-${{ matrix.python }}
|
|
62
|
+
path: 'junit.xml
|
|
63
|
+
|
|
64
|
+
docs/validation/*json
|
|
65
|
+
|
|
66
|
+
'
|
|
67
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,234 @@
|
|
|
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: Check seeded menu mutations
|
|
86
|
+
run: uv run poe mutation-menus
|
|
87
|
+
if: matrix.os == 'windows-latest'
|
|
88
|
+
- name: Test results
|
|
89
|
+
if: always()
|
|
90
|
+
uses: mikepenz/action-junit-report@6ef3fdcfcd5f0b5d14df3e659ef19b8c6820f93d
|
|
91
|
+
with:
|
|
92
|
+
report_paths: junit.xml
|
|
93
|
+
annotate_only: true
|
|
94
|
+
check_annotations: false
|
|
95
|
+
check_retries: false
|
|
96
|
+
require_tests: true
|
|
97
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
|
98
|
+
if: always()
|
|
99
|
+
with:
|
|
100
|
+
name: evidence-${{ matrix.os }}-${{ matrix.python }}
|
|
101
|
+
path: 'junit.xml
|
|
102
|
+
|
|
103
|
+
docs/validation/*json
|
|
104
|
+
|
|
105
|
+
'
|
|
106
|
+
if-no-files-found: error
|
|
107
|
+
needs: target
|
|
108
|
+
if: needs.target.outputs.version != ''
|
|
109
|
+
build:
|
|
110
|
+
needs:
|
|
111
|
+
- target
|
|
112
|
+
- ci
|
|
113
|
+
runs-on: ubuntu-latest
|
|
114
|
+
timeout-minutes: 20
|
|
115
|
+
outputs:
|
|
116
|
+
artifact-id: ${{ steps.dist.outputs.artifact-id }}
|
|
117
|
+
steps:
|
|
118
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
|
119
|
+
with:
|
|
120
|
+
persist-credentials: false
|
|
121
|
+
fetch-depth: 0
|
|
122
|
+
ref: ${{ github.sha }}
|
|
123
|
+
- uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
|
|
124
|
+
- name: Build tested version without publishing credentials
|
|
125
|
+
run: '[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 1
|
|
126
|
+
|
|
127
|
+
uv sync --locked --group dev
|
|
128
|
+
|
|
129
|
+
uv run poe check
|
|
130
|
+
|
|
131
|
+
python3 - <<''PY''
|
|
132
|
+
|
|
133
|
+
import os,pathlib
|
|
134
|
+
|
|
135
|
+
p=pathlib.Path(''README.md'')
|
|
136
|
+
|
|
137
|
+
p.write_text(p.read_text().replace(''(docs/'', ''(https://raw.githubusercontent.com/''+os.environ[''GITHUB_REPOSITORY'']+''/v''+os.environ[''VERSION'']+''/docs/''))
|
|
138
|
+
|
|
139
|
+
PY
|
|
140
|
+
|
|
141
|
+
uv build
|
|
142
|
+
|
|
143
|
+
uv run python scripts/package_smoke.py
|
|
144
|
+
|
|
145
|
+
printf ''Built from reviewed main commit %s\n'' "$GITHUB_SHA" > release-notes.md
|
|
146
|
+
|
|
147
|
+
'
|
|
148
|
+
env:
|
|
149
|
+
VERSION: ${{ needs.target.outputs.version }}
|
|
150
|
+
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.target.outputs.version }}
|
|
151
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
|
152
|
+
id: dist
|
|
153
|
+
with:
|
|
154
|
+
name: trusted-release
|
|
155
|
+
path: 'dist/*
|
|
156
|
+
|
|
157
|
+
release-notes.md'
|
|
158
|
+
if-no-files-found: error
|
|
159
|
+
retention-days: 7
|
|
160
|
+
name: Build
|
|
161
|
+
github-release:
|
|
162
|
+
if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.build.result
|
|
163
|
+
== 'success'
|
|
164
|
+
needs:
|
|
165
|
+
- target
|
|
166
|
+
- build
|
|
167
|
+
- attest
|
|
168
|
+
runs-on: ubuntu-latest
|
|
169
|
+
timeout-minutes: 20
|
|
170
|
+
permissions:
|
|
171
|
+
contents: write # Create immutable refs and upload tested release artifacts.
|
|
172
|
+
steps:
|
|
173
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
|
|
174
|
+
with:
|
|
175
|
+
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
176
|
+
path: release
|
|
177
|
+
- name: Publish same-run artifacts from trusted merge
|
|
178
|
+
run: "# The approved immutable-v* ruleset must already prohibit tag updates/deletion.\nancestor=$(gh api \"repos/$GITHUB_REPOSITORY/compare/$GITHUB_SHA...main\"\
|
|
179
|
+
\ --jq .merge_base_commit.sha)\ntest \"$ancestor\" = \"$GITHUB_SHA\" || { echo 'Release commit is no longer on main.'\
|
|
180
|
+
\ >&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\
|
|
181
|
+
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\
|
|
182
|
+
PY\n# Ref creation is atomic: if another process created it first, accept only the same commit.\nif ! gh api --method\
|
|
183
|
+
\ POST \"repos/$GITHUB_REPOSITORY/git/refs\" --input release-ref.json >/dev/null 2>tag-create-error.txt; then\n actual=$(gh\
|
|
184
|
+
\ api \"repos/$GITHUB_REPOSITORY/commits/$TAG\" --jq .sha)\n test \"$actual\" = \"$GITHUB_SHA\" || { echo 'Tag creation\
|
|
185
|
+
\ failed or a conflicting tag exists.' >&2; exit 1; }\nfi\nactual=$(gh api \"repos/$GITHUB_REPOSITORY/commits/$TAG\"\
|
|
186
|
+
\ --jq .sha)\ntest \"$actual\" = \"$GITHUB_SHA\" || { echo 'Tag does not match the trusted merge.' >&2; exit 1; }\n\
|
|
187
|
+
if gh release view \"$TAG\" --repo \"$GITHUB_REPOSITORY\" >/dev/null 2>&1; then\n :\nelse\n gh release create \"\
|
|
188
|
+
$TAG\" --repo \"$GITHUB_REPOSITORY\" --verify-tag --title \"$TAG\" --generate-notes --notes-file release/release-notes.md\n\
|
|
189
|
+
fi\ngh release upload \"$TAG\" release/dist/* --repo \"$GITHUB_REPOSITORY\" --clobber\n"
|
|
190
|
+
env:
|
|
191
|
+
GH_TOKEN: ${{ github.token }}
|
|
192
|
+
TAG: v${{ needs.target.outputs.version }}
|
|
193
|
+
name: Github Release
|
|
194
|
+
pypi:
|
|
195
|
+
if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.github-release.result
|
|
196
|
+
== 'success'
|
|
197
|
+
needs:
|
|
198
|
+
- target
|
|
199
|
+
- build
|
|
200
|
+
- github-release
|
|
201
|
+
runs-on: ubuntu-latest
|
|
202
|
+
timeout-minutes: 20
|
|
203
|
+
environment: pypi
|
|
204
|
+
permissions:
|
|
205
|
+
id-token: write # OIDC identity for attestation or trusted PyPI publishing.
|
|
206
|
+
steps:
|
|
207
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
|
|
208
|
+
with:
|
|
209
|
+
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
210
|
+
path: release
|
|
211
|
+
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086
|
|
212
|
+
with:
|
|
213
|
+
packages-dir: release/dist/
|
|
214
|
+
skip-existing: true
|
|
215
|
+
name: Pypi
|
|
216
|
+
attest:
|
|
217
|
+
name: Attest tested artifacts
|
|
218
|
+
needs: build
|
|
219
|
+
if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.build.result
|
|
220
|
+
== 'success'
|
|
221
|
+
runs-on: ubuntu-latest
|
|
222
|
+
timeout-minutes: 10
|
|
223
|
+
permissions:
|
|
224
|
+
contents: read
|
|
225
|
+
id-token: write # OIDC identity for attestation or trusted PyPI publishing.
|
|
226
|
+
attestations: write # Store provenance for these same-run build artifacts.
|
|
227
|
+
steps:
|
|
228
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
|
|
229
|
+
with:
|
|
230
|
+
path: release
|
|
231
|
+
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
|
232
|
+
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6
|
|
233
|
+
with:
|
|
234
|
+
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
|
+
}
|
|
@@ -7,11 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Menus
|
|
11
|
+
|
|
12
|
+
- Add nested menus with Back navigation, lazy JSON providers, explicit refresh/retry,
|
|
13
|
+
configurable parent refresh, and stable selection restoration.
|
|
14
|
+
- Bound generated output, provider timeouts and process cleanup; cache up to 128 menu
|
|
15
|
+
snapshots with inactive least-recently-used eviction. Existing static configs remain valid.
|
|
16
|
+
- Add shared declaration fixtures, actual provider/terminal tests and seeded menu mutations.
|
|
17
|
+
See [menu lifecycle and provider contract](docs/menus.md) for examples and limits.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- Reverted array syntax for `command` config key; `command` is string-only,
|
|
22
|
+
use `args` for arguments
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Improved test naming to follow `[unit]_[scenario]_[expectedResult]` pattern
|
|
27
|
+
- Added diagnostic failure messages to all test assertions
|
|
28
|
+
- Release workflow now supports `force-patch` input for non-bumping commits
|
|
29
|
+
|
|
30
|
+
## [0.2.0] - 2026-03-30
|
|
31
|
+
|
|
10
32
|
### Added
|
|
11
33
|
|
|
12
|
-
- `command` config key now accepts a list of strings (e.g.,
|
|
13
|
-
`command = ["ssh", "-t", "my server"]`) for easier argument handling,
|
|
14
|
-
especially with values containing spaces
|
|
15
34
|
- `--config-path` CLI flag to print the active configuration file path
|
|
16
35
|
|
|
17
36
|
### Fixed
|
|
@@ -19,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
38
|
- PyPI package page now renders README images correctly (paths are rewritten
|
|
20
39
|
to absolute URLs during the release build)
|
|
21
40
|
- Release artifacts no longer accidentally include non-distribution files
|
|
41
|
+
- Release workflow now produces correct version numbers (fixed dirty-tree
|
|
42
|
+
version detection via `SETUPTOOLS_SCM_PRETEND_VERSION`)
|
|
22
43
|
|
|
23
44
|
## [0.1.0] - 2026-03-30
|
|
24
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: launchline
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -24,12 +24,14 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
|
|
25
25
|
[](https://github.com/mikejhill/launchline/actions/workflows/ci.yml)
|
|
26
26
|
[](https://github.com/mikejhill/launchline/actions/workflows/release.yml)
|
|
27
|
+
[](https://pypi.org/project/launchline/)
|
|
28
|
+
[](https://pypi.org/project/launchline/)
|
|
27
29
|
|
|
28
30
|
A lightweight terminal launcher for interactive CLI tools. Define your frequently
|
|
29
31
|
used commands in a TOML config file, then pick them from a fuzzy-searchable menu
|
|
30
32
|
instead of remembering flags and paths.
|
|
31
33
|
|
|
32
|
-

|
|
33
35
|
|
|
34
36
|
## Why
|
|
35
37
|
|
|
@@ -50,7 +52,7 @@ instead.
|
|
|
50
52
|
|
|
51
53
|
| Fuzzy search | Arrow-key navigation |
|
|
52
54
|
| :-------------------------------: | :-----------------------------------: |
|
|
53
|
-
|  |  |
|
|
54
56
|
|
|
55
57
|
## Installation
|
|
56
58
|
|
|
@@ -118,29 +120,29 @@ Config file format is [TOML](https://toml.io). The file has an optional
|
|
|
118
120
|
| `clear_on_launch` | bool | `true` | Clear terminal before launching an entry |
|
|
119
121
|
| `ghost_text` | bool | `true` | Show the highlighted entry name as an autocomplete hint on prompt |
|
|
120
122
|
| `numeric_trigger` | bool | `true` | Pressing a digit immediately launches the matching entry |
|
|
123
|
+
| `refresh_on_return` | boolean | `false` | Regenerate a dynamic root after returning from a submenu |
|
|
121
124
|
|
|
122
125
|
### `[[entries]]`
|
|
123
126
|
|
|
124
127
|
Each `[[entries]]` table defines one launchable tool:
|
|
125
128
|
|
|
126
|
-
| Key | Type
|
|
127
|
-
| ------------------- |
|
|
128
|
-
| `name` | string
|
|
129
|
-
| `command` | string
|
|
130
|
-
| `args` | list of strings
|
|
131
|
-
| `description` | string
|
|
132
|
-
| `working_directory` | string
|
|
133
|
-
| `env` | table
|
|
129
|
+
| Key | Type | Required | Default | Description |
|
|
130
|
+
| ------------------- | --------------- | -------- | ------- | -------------------------------------------------------------- |
|
|
131
|
+
| `name` | string | yes | — | Display name shown in the menu |
|
|
132
|
+
| `command` | string | yes | — | Executable to run |
|
|
133
|
+
| `args` | list of strings | no | `[]` | Arguments passed to the command |
|
|
134
|
+
| `description` | string | no | `""` | Short description shown next to the name |
|
|
135
|
+
| `working_directory` | string | no | — | Working directory for the subprocess |
|
|
136
|
+
| `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
|
|
137
|
+
| `id` | string | no | `""` | Stable per-menu identity for refreshed selection |
|
|
138
|
+
| `menu` | table | no | - | Submenu declaration, exclusive with command fields |
|
|
134
139
|
|
|
135
140
|
### Validation Rules
|
|
136
141
|
|
|
137
|
-
-
|
|
138
|
-
- Every entry
|
|
139
|
-
- `command` may be a string (`"pwsh"`) or a list (`["ssh", "-t", "host"]`).
|
|
140
|
-
When a list, the first element is the executable and the rest are arguments.
|
|
142
|
+
- A root requires nonempty `[[entries]]` or a `[provider]` declaration.
|
|
143
|
+
- Every entry needs `name` and either `command` or `menu`.
|
|
141
144
|
- `on_exit` must be `"restart"` or `"exit"`.
|
|
142
|
-
- `args` must be a list (not a bare string).
|
|
143
|
-
arguments provided inline in a `command` list.
|
|
145
|
+
- `args` must be a list (not a bare string).
|
|
144
146
|
- `env` must be a TOML table (not a string or list).
|
|
145
147
|
- If `working_directory` does not exist at load time, it is silently reset to
|
|
146
148
|
`None` (a warning is logged).
|
|
@@ -171,10 +173,6 @@ name = "PowerShell"
|
|
|
171
173
|
command = "pwsh"
|
|
172
174
|
args = ["-NoProfile"]
|
|
173
175
|
env = { TERM = "xterm-256color" }
|
|
174
|
-
|
|
175
|
-
[[entries]]
|
|
176
|
-
name = "Remote Server"
|
|
177
|
-
command = ["ssh", "-t", "my server", "bash"]
|
|
178
176
|
```
|
|
179
177
|
|
|
180
178
|
## Usage
|
|
@@ -220,3 +218,30 @@ uv run mypy src
|
|
|
220
218
|
## License
|
|
221
219
|
|
|
222
220
|
MIT
|
|
221
|
+
|
|
222
|
+
## See Also
|
|
223
|
+
|
|
224
|
+
- **[LaunchLine (Rust)](https://github.com/mikejhill/launchline-rust)** — a Rust
|
|
225
|
+
implementation with the shared configuration and keyboard contract,
|
|
226
|
+
ships as a single native binary.
|
|
227
|
+
|
|
228
|
+
## Compatibility and validation
|
|
229
|
+
|
|
230
|
+
Use `launchline --check-config` to validate configuration without opening the menu.
|
|
231
|
+
Set `NO_COLOR` for a selection marker without ANSI color. See the
|
|
232
|
+
[compatibility matrix](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/compatibility.md), [reproducible test and performance
|
|
233
|
+
results](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/validation/report.md), and [release readiness](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/releasing.md).
|
|
234
|
+
|
|
235
|
+
### `[provider]`
|
|
236
|
+
|
|
237
|
+
The same table is accepted under `entries.menu.provider`. See the
|
|
238
|
+
[menu lifecycle and provider contract](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/menus.md) for nesting, caching,
|
|
239
|
+
refresh/back behavior, limits, errors and examples.
|
|
240
|
+
|
|
241
|
+
| Field | Type | Default | Description |
|
|
242
|
+
| --- | --- | --- | --- |
|
|
243
|
+
| `command` | string | required | Explicit trusted provider executable |
|
|
244
|
+
| `args` | string array | `[]` | Literal argv |
|
|
245
|
+
| `env` | string table | `{}` | Environment overlay |
|
|
246
|
+
| `working_directory` | string | inherited | Provider working directory |
|
|
247
|
+
| `timeout_seconds` | integer | `10` | Deadline from 1 to 300 seconds |
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mikejhill/launchline/actions/workflows/ci.yml)
|
|
4
4
|
[](https://github.com/mikejhill/launchline/actions/workflows/release.yml)
|
|
5
|
+
[](https://pypi.org/project/launchline/)
|
|
6
|
+
[](https://pypi.org/project/launchline/)
|
|
5
7
|
|
|
6
8
|
A lightweight terminal launcher for interactive CLI tools. Define your frequently
|
|
7
9
|
used commands in a TOML config file, then pick them from a fuzzy-searchable menu
|
|
8
10
|
instead of remembering flags and paths.
|
|
9
11
|
|
|
10
|
-

|
|
11
13
|
|
|
12
14
|
## Why
|
|
13
15
|
|
|
@@ -28,7 +30,7 @@ instead.
|
|
|
28
30
|
|
|
29
31
|
| Fuzzy search | Arrow-key navigation |
|
|
30
32
|
| :-------------------------------: | :-----------------------------------: |
|
|
31
|
-
|  |  |
|
|
32
34
|
|
|
33
35
|
## Installation
|
|
34
36
|
|
|
@@ -96,29 +98,29 @@ Config file format is [TOML](https://toml.io). The file has an optional
|
|
|
96
98
|
| `clear_on_launch` | bool | `true` | Clear terminal before launching an entry |
|
|
97
99
|
| `ghost_text` | bool | `true` | Show the highlighted entry name as an autocomplete hint on prompt |
|
|
98
100
|
| `numeric_trigger` | bool | `true` | Pressing a digit immediately launches the matching entry |
|
|
101
|
+
| `refresh_on_return` | boolean | `false` | Regenerate a dynamic root after returning from a submenu |
|
|
99
102
|
|
|
100
103
|
### `[[entries]]`
|
|
101
104
|
|
|
102
105
|
Each `[[entries]]` table defines one launchable tool:
|
|
103
106
|
|
|
104
|
-
| Key | Type
|
|
105
|
-
| ------------------- |
|
|
106
|
-
| `name` | string
|
|
107
|
-
| `command` | string
|
|
108
|
-
| `args` | list of strings
|
|
109
|
-
| `description` | string
|
|
110
|
-
| `working_directory` | string
|
|
111
|
-
| `env` | table
|
|
107
|
+
| Key | Type | Required | Default | Description |
|
|
108
|
+
| ------------------- | --------------- | -------- | ------- | -------------------------------------------------------------- |
|
|
109
|
+
| `name` | string | yes | — | Display name shown in the menu |
|
|
110
|
+
| `command` | string | yes | — | Executable to run |
|
|
111
|
+
| `args` | list of strings | no | `[]` | Arguments passed to the command |
|
|
112
|
+
| `description` | string | no | `""` | Short description shown next to the name |
|
|
113
|
+
| `working_directory` | string | no | — | Working directory for the subprocess |
|
|
114
|
+
| `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
|
|
115
|
+
| `id` | string | no | `""` | Stable per-menu identity for refreshed selection |
|
|
116
|
+
| `menu` | table | no | - | Submenu declaration, exclusive with command fields |
|
|
112
117
|
|
|
113
118
|
### Validation Rules
|
|
114
119
|
|
|
115
|
-
-
|
|
116
|
-
- Every entry
|
|
117
|
-
- `command` may be a string (`"pwsh"`) or a list (`["ssh", "-t", "host"]`).
|
|
118
|
-
When a list, the first element is the executable and the rest are arguments.
|
|
120
|
+
- A root requires nonempty `[[entries]]` or a `[provider]` declaration.
|
|
121
|
+
- Every entry needs `name` and either `command` or `menu`.
|
|
119
122
|
- `on_exit` must be `"restart"` or `"exit"`.
|
|
120
|
-
- `args` must be a list (not a bare string).
|
|
121
|
-
arguments provided inline in a `command` list.
|
|
123
|
+
- `args` must be a list (not a bare string).
|
|
122
124
|
- `env` must be a TOML table (not a string or list).
|
|
123
125
|
- If `working_directory` does not exist at load time, it is silently reset to
|
|
124
126
|
`None` (a warning is logged).
|
|
@@ -149,10 +151,6 @@ name = "PowerShell"
|
|
|
149
151
|
command = "pwsh"
|
|
150
152
|
args = ["-NoProfile"]
|
|
151
153
|
env = { TERM = "xterm-256color" }
|
|
152
|
-
|
|
153
|
-
[[entries]]
|
|
154
|
-
name = "Remote Server"
|
|
155
|
-
command = ["ssh", "-t", "my server", "bash"]
|
|
156
154
|
```
|
|
157
155
|
|
|
158
156
|
## Usage
|
|
@@ -198,3 +196,30 @@ uv run mypy src
|
|
|
198
196
|
## License
|
|
199
197
|
|
|
200
198
|
MIT
|
|
199
|
+
|
|
200
|
+
## See Also
|
|
201
|
+
|
|
202
|
+
- **[LaunchLine (Rust)](https://github.com/mikejhill/launchline-rust)** — a Rust
|
|
203
|
+
implementation with the shared configuration and keyboard contract,
|
|
204
|
+
ships as a single native binary.
|
|
205
|
+
|
|
206
|
+
## Compatibility and validation
|
|
207
|
+
|
|
208
|
+
Use `launchline --check-config` to validate configuration without opening the menu.
|
|
209
|
+
Set `NO_COLOR` for a selection marker without ANSI color. See the
|
|
210
|
+
[compatibility matrix](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/compatibility.md), [reproducible test and performance
|
|
211
|
+
results](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/validation/report.md), and [release readiness](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/releasing.md).
|
|
212
|
+
|
|
213
|
+
### `[provider]`
|
|
214
|
+
|
|
215
|
+
The same table is accepted under `entries.menu.provider`. See the
|
|
216
|
+
[menu lifecycle and provider contract](https://raw.githubusercontent.com/mikejhill/launchline/v0.4.0/docs/menus.md) for nesting, caching,
|
|
217
|
+
refresh/back behavior, limits, errors and examples.
|
|
218
|
+
|
|
219
|
+
| Field | Type | Default | Description |
|
|
220
|
+
| --- | --- | --- | --- |
|
|
221
|
+
| `command` | string | required | Explicit trusted provider executable |
|
|
222
|
+
| `args` | string array | `[]` | Literal argv |
|
|
223
|
+
| `env` | string table | `{}` | Environment overlay |
|
|
224
|
+
| `working_directory` | string | inherited | Provider working directory |
|
|
225
|
+
| `timeout_seconds` | integer | `10` | Deadline from 1 to 300 seconds |
|
|
@@ -0,0 +1,39 @@
|
|
|
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).
|
|
32
|
+
|
|
33
|
+
## Nested and dynamic menu extension
|
|
34
|
+
|
|
35
|
+
See the [shared lifecycle and provider schema](menus.md) and
|
|
36
|
+
[feature validation matrix](validation/menu-checkpoint.md) for current evidence.
|
|
37
|
+
The earlier matrix above describes the modernization baseline; feature-head hosted
|
|
38
|
+
CI and ordered releases are tracked separately. Windows raw/Kitty input differences
|
|
39
|
+
are explicitly documented rather than asserted equivalent.
|