DamianEdwards / DamianEdwards/kusto-cli
Package macOS releases as an app bundle for Finder icon support
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 8
- Forks
- 3
- Avg merge
- 1d 35m
- Merged PRs (30d)
- 12
Description
Context
#81 adds an original application icon to the Windows executable. The equivalent .NET ApplicationIcon property is Windows/PE-specific; NativeAOT currently emits the macOS release as a bare Mach-O executable in a flat archive.
The goal is to investigate and, if viable, package the macOS release so Finder can display the Kusto CLI icon while preserving normal terminal invocation, installation, self-update, and rollback behavior.
Research findings
Standard macOS icon mechanism
Apple associates application icons with .app bundles rather than bare Mach-O executables. The conventional structure is:
Kusto CLI.app/
└── Contents/
├── Info.plist
├── MacOS/
│ ├── kusto
│ └── native dylib sidecars
└── Resources/
└── kusto.icns
The minimal Info.plist should include:
CFBundleNameandCFBundleDisplayName:Kusto CLICFBundleIdentifier: a stable reverse-DNS identifierCFBundleExecutable:kustoCFBundlePackageType:APPLCFBundleIconFile:kusto.icns- normalized
CFBundleShortVersionString/CFBundleVersionvalues - optionally
LSMinimumSystemVersion; evaluateLSBackgroundOnlyrather than enabling it by assumption
The .icns should be generated from the SVG added in #81 and contain the standard 16, 32, 128, 256, 512, and Retina representations. macOS ships iconutil for converting an .iconset directory into .icns.
References:
Activity Monitor limitation
A local prototype confirmed:
- Finder/Launch Services recognizes a correctly structured bundle and loads its
.icnsicon. - Launching the bundle through
openregisters anNSRunningApplicationwith the bundle URL and icon. - Launching the executable normally from the shell, including through a
kustosymlink intoContents/MacOS, does not register anNSRunningApplication.
Therefore, an app bundle should reliably provide the Finder icon but does not by itself guarantee that Activity Monitor will show the custom icon for ordinary CLI invocations. Launching through open is not a suitable replacement for direct execution because it changes stdin/stdout, waiting, signals, and exit-code semantics. Forcing AppKit/Launch Services registration from the CLI could also introduce Dock, GUI-session, SSH/headless, and startup behavior risks.
Activity Monitor support should be treated as best-effort and manually verified. It should not be an acceptance criterion unless a solution preserves normal CLI semantics and headless execution.
Proposed packaging design
Keep a root command entry point while making the bundle the canonical native payload:
kusto # small POSIX launcher that execs the bundled binary
Kusto CLI.app/
└── Contents/
├── Info.plist
├── MacOS/
│ ├── kusto
│ ├── libSkiaSharp.dylib
│ ├── libHarfBuzzSharp.dylib
│ └── libsodium.dylib
└── Resources/
└── kusto.icns
LICENSE
THIRD-PARTY-NOTICES.md
payload-manifest.json
A regular launcher file is preferable to shipping a symlink because the current archive safety checks intentionally reject symbolic and hard links. The launcher should use exec so signals and exit codes come from the real CLI process.
Keeping dylibs beside the bundled executable preserves the current native-library probing behavior. If they are moved into Contents/Frameworks for future signing conventions, runtime loading must be tested and adjusted explicitly.
Existing contracts that need changes
The payload manifest already supports nested files, but several paths assume the real executable is at the archive/install root:
scripts/Publish-NativeAsset.ps1must build the macOS bundle and generateInfo.plistwith the release version.scripts/Test-PackagedArchive.ps1and packaging-required-file checks must validate the bundle paths and execute the root launcher.scripts/install/install-kusto-cli.shcurrently checks, versions, smoke-tests, and installs${extract}/kusto; this can remain the command entry point, but it must verify the nested executable and icon resources too.GitHubReleaseService.ExtractReleaseArchiveandPayloadInstaller.ValidateManifestrequire a rootkusto; retaining the launcher preserves that contract.UpdateServicederives the install root fromEnvironment.ProcessPath. After the launcher callsexec, that path will be.../Kusto CLI.app/Contents/MacOS/kusto, so macOS bundle-aware install-root detection is required.- The self-update extractor explicitly restores executable permission only on the root
kusto. It must also restore permission on the nested bundle executable. - Update staging/backup/rollback must operate on the entire payload root, not only
Contents/MacOS, soInfo.plist,.icns, and the binary remain version-aligned. - Completion generation and PATH setup should continue to use the root
kustolauncher.
Signing and distribution considerations
The current release pipeline signs Windows executables and Sigstore-attests all archives, but it does not Developer ID-sign or notarize macOS payloads. Creating an .app bundle can cause Gatekeeper to treat manually downloaded artifacts differently from the current bare executable.
Before shipping this layout:
- test both installer downloads and manually downloaded/quarantined archives;
- confirm whether an unsigned/ad-hoc-signed bundle introduces a Gatekeeper regression;
- if Developer ID signing/notarization is added, sign nested code before the outer bundle and never mutate bundle contents afterward;
- stage and replace a complete signed bundle atomically during self-update rather than editing signed files in place;
- generate release hashes and attestations from the final signed/notarized archive.
Acceptance criteria
- macOS x64 and arm64 archives contain a valid
Kusto CLI.appwithInfo.plistand multi-resolutionkusto.icns. - Finder displays the custom icon for the bundle.
-
kustoremains directly executable from the shell with unchanged stdout, stderr, signals, and exit codes. - Native chart sidecars load from both a fresh archive and an installed payload.
- Installer version comparison, shell completion, PATH setup, and rollback continue to work.
- Self-update replaces the full payload root and preserves executable modes for both the launcher and bundled binary.
- Fresh install and self-update are covered on both macOS architectures.
- Gatekeeper behavior is tested for installer and manually downloaded/quarantined artifacts.
- Actual Activity Monitor behavior is documented; custom process icons remain best-effort unless normal CLI/headless semantics can be preserved.
- Linux and Windows archive layouts remain unchanged.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with scripts/Publish-NativeAsset.ps1, scripts/Test-PackagedArchive.ps1, and scripts/install/install-kusto-cli.sh, then trace UpdateService, GitHubReleaseService.ExtractReleaseArchive, and PayloadInstaller.ValidateManifest. Verify the proposed bundle and root launcher across fresh installs and self-updates on both macOS architectures. Done means the acceptance criteria pass without changing Linux or Windows layouts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, csharp, macos, powershell
- Domain
- build-system, cli, devops, release
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100