DamianEdwards / DamianEdwards/kusto-cli

Package macOS releases as an app bundle for Finder icon support

Open
#82 0 comments 0 reactions 0 assignees View on GitHub

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:

  • CFBundleName and CFBundleDisplayName: Kusto CLI
  • CFBundleIdentifier: a stable reverse-DNS identifier
  • CFBundleExecutable: kusto
  • CFBundlePackageType: APPL
  • CFBundleIconFile: kusto.icns
  • normalized CFBundleShortVersionString / CFBundleVersion values
  • optionally LSMinimumSystemVersion; evaluate LSBackgroundOnly rather 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 .icns icon.
  • Launching the bundle through open registers an NSRunningApplication with the bundle URL and icon.
  • Launching the executable normally from the shell, including through a kusto symlink into Contents/MacOS, does not register an NSRunningApplication.

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.ps1 must build the macOS bundle and generate Info.plist with the release version.
  • scripts/Test-PackagedArchive.ps1 and packaging-required-file checks must validate the bundle paths and execute the root launcher.
  • scripts/install/install-kusto-cli.sh currently 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.ExtractReleaseArchive and PayloadInstaller.ValidateManifest require a root kusto; retaining the launcher preserves that contract.
  • UpdateService derives the install root from Environment.ProcessPath. After the launcher calls exec, 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, so Info.plist, .icns, and the binary remain version-aligned.
  • Completion generation and PATH setup should continue to use the root kusto launcher.

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.app with Info.plist and multi-resolution kusto.icns.
  • Finder displays the custom icon for the bundle.
  • kusto remains 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.