-
Notifications
You must be signed in to change notification settings - Fork 3
ci: Publish the CLI to PyPI as flagsmith-cli #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d526052
748ef80
f0436e6
0b649ec
3b57f74
c3a0327
42eef41
f3d36ff
dff6ed2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,7 @@ jobs: | |||||||||
| - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | ||||||||||
| with: | ||||||||||
| go-version-file: go.mod | ||||||||||
| - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
zizmor --pedantic .github/workflows/release.ymlRepository: Flagsmith/flagsmith-cli Length of output: 548 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- release workflow ---'
cat -n .github/workflows/release.yml
printf '%s\n' '--- setup-uv references and cache settings ---'
rg -n -C 3 'astral-sh/setup-uv|enable-cache|uv[-_ ]cache|cache' .github . || trueRepository: Flagsmith/flagsmith-cli Length of output: 43685 🌐 Web query:
💡 Result: In astral-sh/setup-uv v10.0.1, the enable-cache: auto setting provides security against cache poisoning by automatically disabling GitHub Actions caching for sensitive events [1][2]. Specifically, caching is disabled for pull_request_target, workflow_run, and release events, as well as for tag pushes [2][3]. For other events, caching remains enabled on GitHub-hosted runners to improve performance [1][4]. Cache poisoning occurs when an attacker with repository access (e.g., via a compromised or malicious pull request) populates the GitHub Actions cache with malicious artifacts, which are then restored in subsequent, more privileged workflow runs (such as those triggered by tags or releases) [5][6]. The security tool zizmor includes an audit rule for cache-poisoning that monitors the use of setup-uv and other actions with built-in caching [7][6]. While zizmor correctly identifies that enabling caching in sensitive workflows can be a risk, users have reported issues where zizmor incorrectly flagged safe configurations or offered improper auto-fixes (e.g., suggesting to flip enable-cache: false to true) [8]. These issues have been addressed in recent zizmor updates to ensure the audit logic correctly interprets the configuration toggles [8][9]. For maximum security, especially in workflows that publish artifacts or handle sensitive credentials, you can explicitly set enable-cache: false or use a conditional expression to disable caching for tag-based or release triggers [1][6]: - name: Setup uv uses: astral-sh/setup-uv@v10.0.1 with: enable-cache: ${{!startsWith(github.ref, 'refs/tags/') }} Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- packaging scripts ---'
find packaging -maxdepth 3 -type f -print
printf '%s\n' '--- wheel build script ---'
cat -n packaging/pypi/build-wheels.sh
printf '%s\n' '--- setup-uv v10.0.1 action metadata ---'
curl -fsSL https://raw.githubusercontent.com/astral-sh/setup-uv/v10.0.1/action.yml | sed -n '1,220p'
printf '%s\n' '--- setup-uv v10.0.1 caching documentation ---'
curl -fsSL https://raw.githubusercontent.com/astral-sh/setup-uv/v10.0.1/docs/caching.md | sed -n '1,240p'Repository: Flagsmith/flagsmith-cli Length of output: 15433 🌐 Web query:
💡 Result: In the setup-uv action, including version v10.0.1, the enable-cache: auto setting determines whether caching is enabled based on the environment and the GitHub event [1][2]. When set to auto, caching is enabled on GitHub-hosted runners, with specific exceptions to prevent security risks like cache poisoning [1][2]. Caching is automatically disabled for the following events, regardless of the runner type [1][3]: - release [1][3] - tag push [1] - pull_request_target [1][3] - workflow_run [1][3] Because caching is disabled for tag push events in auto mode, it will not be active for workflows triggered by tags [1]. However, workflow_dispatch events are not included in this list of exceptions, so caching remains enabled for them on GitHub-hosted runners when using auto [1][2]. If you require caching for events where it is disabled by default (such as tag push events), you must explicitly set enable-cache: true in your action configuration [1]. Example: - name: Setup uv uses: astral-sh/setup-uv@v10.0.1 with: enable-cache: true Citations:
Disable uv caching in the release job.
Proposed change - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d1 # v10.0.1
+ with:
+ enable-cache: false📝 Committable suggestion
Suggested change
🧰 Tools🪛 zizmor (1.29.0)[error] 39-39: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default (cache-poisoning) Source: Linters/SAST tools |
||||||||||
| - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 | ||||||||||
| - uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2 | ||||||||||
| with: | ||||||||||
|
|
@@ -68,6 +69,14 @@ jobs: | |||||||||
| with: | ||||||||||
| subject-checksums: ./dist/digests.txt | ||||||||||
|
|
||||||||||
| - name: Build PyPI wheels | ||||||||||
| run: ./packaging/pypi/build-wheels.sh | ||||||||||
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||||||||||
| with: | ||||||||||
| name: pypi-wheels | ||||||||||
| path: dist/pypi/*.whl | ||||||||||
| if-no-files-found: error | ||||||||||
|
|
||||||||||
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||||||||
| with: | ||||||||||
| node-version: 24 # npm >= 11.5.1 required for OIDC trusted publishing | ||||||||||
|
|
@@ -89,8 +98,24 @@ jobs: | |||||||||
| README.md | ||||||||||
| LICENSE | ||||||||||
|
|
||||||||||
| pypi: | ||||||||||
| name: Publish to PyPI | ||||||||||
| needs: goreleaser | ||||||||||
| runs-on: ubuntu-latest | ||||||||||
| environment: pypi | ||||||||||
| permissions: | ||||||||||
| id-token: write # PyPI trusted publishing + PEP 740 attestations | ||||||||||
| steps: | ||||||||||
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | ||||||||||
| with: | ||||||||||
| name: pypi-wheels | ||||||||||
| path: dist | ||||||||||
| - uses: pypa/gh-action-pypi-publish@a892a5a61159132606e93a2fa6f4358831b04d26 # v1.14.2 | ||||||||||
| with: | ||||||||||
| packages-dir: dist | ||||||||||
|
|
||||||||||
| install-script-e2e: | ||||||||||
| name: install (${{ matrix.os }}, ${{ matrix.ref && 'tag' || 'head' }}) | ||||||||||
| name: install.sh (${{ matrix.os }}) | ||||||||||
| needs: goreleaser | ||||||||||
| strategy: | ||||||||||
| fail-fast: false | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| #!/usr/bin/env bash | ||
| # Build PyPI wheels around the binaries from GoReleaser. | ||
| # | ||
| # hatchling builds each wheel (see pyproject.toml) and `wheel tags` stamps the | ||
| # platform tag on it, so there is no packaging code of our own to maintain. | ||
| # | ||
| # Reads dist/artifacts.json + dist/metadata.json, writes wheels to dist/pypi/. | ||
| # | ||
| # packaging/pypi/build-wheels.sh [dist-dir] | ||
| set -euo pipefail | ||
|
|
||
| dist=${1:-dist} | ||
| here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) | ||
| repo=$(cd "$here/../.." && pwd) | ||
| out=$repo/$dist/pypi | ||
|
|
||
| # CGO is disabled, so the Linux binaries are static and run on musl too. | ||
| # The macOS tags are a minimum, kept at https://go.dev/wiki/MinimumRequirements | ||
| platform_tag() { | ||
| case "$1/$2" in | ||
| darwin/amd64) echo macosx_12_0_x86_64 ;; | ||
| darwin/arm64) echo macosx_12_0_arm64 ;; | ||
| linux/amd64) echo manylinux2014_x86_64.musllinux_1_1_x86_64 ;; | ||
| linux/arm64) echo manylinux2014_aarch64.musllinux_1_1_aarch64 ;; | ||
| windows/amd64) echo win_amd64 ;; | ||
| windows/arm64) echo win_arm64 ;; | ||
| *) return 1 ;; | ||
| esac | ||
| } | ||
|
|
||
| version=$(jq -re '.tag | ltrimstr("v")' "$dist/metadata.json") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
jq '{tag, version}' dist/metadata.jsonRepository: Flagsmith/flagsmith-cli Length of output: 238 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(build-wheels\.sh|\.goreleaser(\.ya?ml)?$|.*\.ya?ml$|.*\.yaml$|.*\.json$)' | head -200
printf '%s\n' '--- relevant references ---'
rg -n --hidden --glob '!node_modules/**' --glob '!dist/**' \
'metadata\.json|--snapshot|goreleaser|\.tag\b|\.version\b|ldflags|Version' \
.github packaging .goreleaser.yml .goreleaser.yaml 2>/dev/null || true
printf '%s\n' '--- script ---'
script="$(git ls-files | rg '(^|/)packaging/pypi/build-wheels\.sh$' | head -1)"
if [ -n "$script" ]; then
cat -n "$script"
fi
printf '%s\n' '--- GoReleaser config candidates ---'
for f in .goreleaser.yml .goreleaser.yaml; do
if [ -f "$f" ]; then
cat -n "$f"
fi
done
printf '%s\n' '--- workflow candidates ---'
for f in $(git ls-files '.github/workflows/*' | rg 'pull-request|pr' || true); do
printf '%s\n' "### $f"
cat -n "$f"
doneRepository: Flagsmith/flagsmith-cli Length of output: 13159 🌐 Web query:
💡 Result: In GoReleaser v2, the metadata.json file is generated in the dist directory (or specific subdirectories for cross-compiled builds) and contains information about the release [1][2][3]. When running a snapshot build using the --snapshot flag, the tag and version fields in metadata.json reflect the snapshot configuration [4]. Specifically, the version field is determined by the snapshot.version_template in your configuration, which defaults to {{.Version }}-SNAPSHOT-{{.ShortCommit}} [4]. The tag field typically reflects the current Git tag, which may be modified or overridden depending on your snapshot settings [1][4]. The metadata.json file follows a consistent internal structure [1]: - project_name: The name of your project. - tag: The current Git tag [1]. - previous_tag: The previous Git tag [1]. - version: The evaluated version string [1]. - commit: The current Git commit SHA [1]. - date: The build timestamp [1]. - runtime: Details about the Go OS and architecture (goos, goarch) [1]. While GoReleaser does not maintain a single static page solely for the metadata.json schema, the file is automatically generated by the metadata pipe [1][2][5]. You can verify the exact structure in your environment by inspecting the file generated in the dist/ directory after running GoReleaser [2][3]. For complex pipelines, it is often recommended to target dist/*/metadata.json to capture files across different build targets [3]. Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- Python package metadata ---'
cat -n packaging/pypi/pyproject.toml
printf '%s\n' '--- version package files ---'
git ls-files | rg '(^|/)internal/version(/|$)|(^|/)version\.go$'
for f in $(git ls-files | rg '(^|/)internal/version(/|$)|(^|/)version\.go$'); do
printf '%s\n' "### $f"
cat -n "$f"
done
printf '%s\n' '--- version consumers and Python version handling ---'
rg -n --hidden --glob '!dist/**' \
'FLAGSMITH_CLI_VERSION|internal/version|Version\(\)|version' \
packaging internal cmd main.go 2>/dev/null | head -250Repository: Flagsmith/flagsmith-cli Length of output: 13818 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import re
from pathlib import Path
config = Path(".goreleaser.yaml").read_text()
ldflag = re.search(r'\{\{ if \.IsSnapshot \}\}v\{\{ \.Version \}\}', config)
wheel = re.search(r"jq -re '([^']+)' .*metadata\.json", Path("packaging/pypi/build-wheels.sh").read_text())
print({
"snapshot_binary_template_present": bool(ldflag),
"wheel_metadata_expression": wheel.group(1) if wheel else None,
"default_snapshot_template": "{{ .Version }}-SNAPSHOT-{{ .ShortCommit }}",
"example_snapshot_version": "2.3.4-SNAPSHOT-abcdef0",
"example_snapshot_tag": "v2.3.4",
})
if not ldflag or not wheel:
raise SystemExit("required version paths not found")
print("binary snapshot version: v2.3.4-SNAPSHOT-abcdef0")
print("wheel version from tag: 2.3.4")
print("versions_equal: False")
PYRepository: Flagsmith/flagsmith-cli Length of output: 521 🌐 Web query:
💡 Result: When using GoReleaser's snapshot functionality, the default value for the version_template is {{.Version }}-SNAPSHOT-{{.ShortCommit}} [1]. It is important to note that the version_template should not rely on the {{.Tag }} field, as the version string should not contain a leading "v" [1]. Additionally, some packaging pipes, such as nfpm, require the resulting version string to be compliant with semantic versioning [1]. When the --snapshot flag is used, GoReleaser sets the internal {{.Version }} variable to the value evaluated by the snapshot's name template [1]. Citations: 🌐 Web query:
💡 Result: In the context of GoReleaser, metadata.go is an internal source file responsible for generating metadata.json, a JSON file containing details about a specific release or snapshot execution [1][2]. Key characteristics of this process include: 1. Metadata Content: The metadata.json file stores internal state information such as the ProjectName, current Git Tag, previous Git Tag, Version, Commit hash, build Date, and runtime environment details (GOOS, GOARCH) [1]. 2. Purpose and Usage: The metadata.json file is primarily an internal representation of GoReleaser's state [2]. It is not strictly intended for public publication, though some workflows may choose to upload it to storage providers (like GCS or S3) for CI/CD tracking [2]. 3. Versioning and Snapshots: - The Version field in metadata.json is determined by GoReleaser's internal context [1]. - When running in snapshot mode (invoked with --snapshot), GoReleaser uses the snapshot configuration (often defined by a name_template in goreleaser.yaml) to generate the version string, commonly using placeholders like {{.Version }} or {{ incpatch.Tag }}-next [3]. - Discrepancies can occur if the snapshot naming template is not configured correctly, leading to unexpected prefixes (e.g., v) in the version string [3]. 4. Pipeline Order: The generation of metadata.json occurs as part of a specific "metadata" pipe [1]. To ensure all relevant information (such as Docker image digests) is captured, this pipe must be executed after publishing steps [2][4]. Citations:
Use Snapshot builds compute the version in |
||
| rm -rf "$out" "${here:?}/bin" | ||
| mkdir -p "$out" "$here/bin" | ||
| cp "$repo/README.md" "$repo/LICENSE" "$here/" | ||
|
|
||
| built=0 | ||
| while read -r goos goarch path; do | ||
| tag=$(platform_tag "$goos" "$goarch") || { | ||
| echo "skipping $goos/$goarch: no wheel platform tag" >&2 | ||
| continue | ||
| } | ||
| script=flagsmith | ||
| [ "$goos" = windows ] && script=flagsmith.exe | ||
| rm -f "$here"/bin/flagsmith* | ||
| install -m 755 "$path" "$here/bin/$script" | ||
|
|
||
| FLAGSMITH_CLI_VERSION=$version uv build --quiet --wheel "$here" --out-dir "$out" | ||
| uvx --from wheel wheel tags --python-tag py3 --abi-tag none \ | ||
| --platform-tag "$tag" --remove "$out/flagsmith_cli-"*"-py3-none-any.whl" | ||
| echo "$goos/$goarch -> $tag" | ||
| built=$((built + 1)) | ||
| done < <(jq -re '.[] | select(.type == "Binary") | [.goos, .goarch, .path] | @tsv' "$dist/artifacts.json") | ||
|
|
||
| rm -rf "${here:?}/bin" "$here/README.md" "$here/LICENSE" | ||
| [ "$built" -gt 0 ] || { | ||
| echo "no binaries found in $dist/artifacts.json" >&2 | ||
| exit 1 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [project] | ||
| name = "flagsmith-cli" | ||
| dynamic = ["version"] | ||
| description = "The Flagsmith command-line interface" | ||
| readme = "README.md" # staged by build-wheels.sh | ||
| license = "MIT" | ||
| license-files = ["LICENSE"] # staged by build-wheels.sh | ||
| requires-python = ">=3.8" | ||
| keywords = ["cli", "feature-flags", "flagsmith"] | ||
| classifiers = [ | ||
| "Development Status :: 4 - Beta", | ||
| "Environment :: Console", | ||
| "Intended Audience :: Developers", | ||
| "Programming Language :: Go", | ||
| "Topic :: Software Development", | ||
| ] | ||
|
|
||
| [project.urls] | ||
| Homepage = "https://github.com/Flagsmith/flagsmith-cli" | ||
| Source = "https://github.com/Flagsmith/flagsmith-cli" | ||
| Issues = "https://github.com/Flagsmith/flagsmith-cli/issues" | ||
|
|
||
| # The Go tag, minus its "v"; hatchling normalises it to PEP 440 | ||
| # (2.0.0-beta.3 -> 2.0.0b3). | ||
| [tool.hatch.version] | ||
| source = "env" | ||
| variable = "FLAGSMITH_CLI_VERSION" | ||
|
|
||
| # The binary ships in the wheel's .data/scripts/ directory, which every | ||
| # installer drops straight onto PATH: `uv tool install flagsmith-cli` (and pip, | ||
| # and pipx) hand you a working `flagsmith` with no Python shim in the way. | ||
| # Only one of these two exists per build; hatchling skips the other. | ||
| [tool.hatch.build.targets.wheel] | ||
| bypass-selection = true # no Python package to ship | ||
| shared-scripts = { "bin/flagsmith" = "flagsmith", "bin/flagsmith.exe" = "flagsmith.exe" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: Flagsmith/flagsmith-cli
Length of output: 239
🏁 Script executed:
Repository: Flagsmith/flagsmith-cli
Length of output: 6787
🏁 Script executed:
Repository: Flagsmith/flagsmith-cli
Length of output: 5820
🌐 Web query:
GoReleaser build --single-target documentation snapshot artifacts.json target selection💡 Result:
In GoReleaser, the --single-target flag is designed to restrict builds to a specific target, which is particularly useful for local development, CI pipelines, or creating ad-hoc builds without triggering a full project release [1][2]. Target Selection with --single-target When running goreleaser build --single-target, the tool determines the build target based on environment variables. You can specify the target using the TARGET variable or a combination of Go architecture/OS variables: GOOS, GOARCH, GOARM, GOAMD64, GOARM64, GORISCV64, GO386, GOPPC64, and GOMIPS [2]. If these are not explicitly set, GoReleaser defaults to the target of the current machine (runtime) [2]. Additionally, the --id flag can be used alongside --single-target to restrict the build to a specific build configuration ID if your project has multiple builds defined [1][2]. Snapshot Artifacts and artifacts.json When running GoReleaser in snapshot mode (using the --snapshot flag), artifacts are generated in the dist directory but are not uploaded [3]. GoReleaser maintains an artifacts.json file in the dist folder that documents all generated artifacts, including their associated metadata such as GOOS, GOARCH, and the full target (e.g., linux_amd64_v1) [4][5]. This JSON file is specifically intended for integration with external tools (like jq) to programmatically query and filter release artifacts based on these fields [4]. Key Considerations - Build Filtering: The --single-target flag provides a precise way to limit output [6][7]. Recent updates have improved its ability to infer targets from the runtime across different supported languages [2]. - Snapshot Utility: Snapshot builds (goreleaser build --snapshot) are recommended for local testing or validating build configurations before a formal release, as they avoid side effects like publishing or uploading [3]. - Integration: The artifacts.json file serves as a machine-readable manifest that allows you to automate workflows, such as identifying specific binary paths for downstream tasks or testing [4].
Citations:
🏁 Script executed:
Repository: Flagsmith/flagsmith-cli
Length of output: 3127
🌐 Web query:
uvx --from multiple wheel file paths command semantics💡 Result:
The uvx command (an alias for uv tool run) does not support passing multiple wheel file paths directly to the --from flag. The --from flag is designed to accept a single package specification—such as a package name, a versioned package, a package with extras, or an alternative source like a Git URL [1][2][3][4]. To include multiple packages or custom sources in a single isolated environment with uvx, you should use the following strategies: 1. For multiple packages: Use the --with flag in combination with --from. While --from specifies the primary package that provides the executable, --with can be specified multiple times to include additional dependencies in the same environment [2][5][4]. 2. For multiple executables: If you need the executables from multiple packages to be available in the environment, use the --with-executables-from flag [2][6][7]. 3. For local wheel files: If you need to install specific local wheel files, you generally manage them via configuration (such as a uv.toml file with a local index) or by referencing their paths within the environment resolution context [8]. You can also use --find-links to point to a directory containing your wheels [8]. In summary, --from is restricted to defining the base package context and does not accept a list of file paths. Instead, combine --from with --with or --with-executables-from to build your desired environment [2][3][7].
Citations:
🌐 Web query:
Hatchling shared-scripts wheel .data/scripts executable layout💡 Result:
In Hatchling, the
shared-scriptsconfiguration option is used to include files in thescriptssubdirectory of a wheel's.datadirectory [1][2][3]. When a wheel is installed, the contents of this directory are automatically moved to the appropriate platform-specific location—typicallybinon Unix systems andScriptson Windows—making them available on the user's PATH [1][4][5]. To configure this in yourpyproject.toml, you can use the[tool.hatch.build.targets.wheel]table [4][6]. Simple mapping: [tool.hatch.build.targets.wheel] shared-scripts = { "scripts" = "/" } Specific file mapping: [tool.hatch.build.targets.wheel.shared-scripts] "scripts/my-script.sh" = "my-script.sh" Technical details: 1. Wheel Layout: Files mapped viashared-scriptsare placed inside{distribution}-{version}.data/scripts/within the wheel archive [7][5][8]. 2. Shebang Rewriting: Hatchling automatically scans files inshared-scripts. If a file begins with#!python, Hatchling will rewrite the shebang to point to the Python interpreter of the environment where the package is installed [4][6]. 3. Execution Permissions: While the wheel format itself does not guarantee execution bits, installers (like pip) are responsible for ensuring that files in thescriptsdirectory are installed with appropriate executable permissions [7][5][9]. 4. Purpose: This functionality is specifically intended for packaging standalone scripts that are not Python entry points (which should instead be defined via[project.scripts]asconsole_scripts) [10][7][5]. For files that do not require script-specific handling or placement in thebin/Scriptsdirectory, useshared-datainstead [1][2][3].Citations:
🏁 Script executed:
Repository: Flagsmith/flagsmith-cli
Length of output: 7069
Extend pull-request wheel validation to all supported targets.
pypi-wheeluses--single-target, so it tests only the Ubuntu target. The release workflow builds all six targets before PyPI publishing. This is a pre-merge coverage gap, not a release path that publishes only one wheel. Iterate over each wheel fortwine checkand archive-content checks;uvx --from ./dist/pypi/*.whlaccepts only one source.