Skip to content

- fix: write real app version into mac Info.plist instead of hard-coded 1.0.0 - #482

Open
jpage4500 wants to merge 3 commits into
shannah:masterfrom
jpage4500:feature/09-17-mac-dmg-version
Open

jpage4500 wants to merge 3 commits into
shannah:masterfrom
jpage4500:feature/09-17-mac-dmg-version

Conversation

@jpage4500

@jpage4500 jpage4500 commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Hi @shannah - I noticed when installing an app it shows version "1.0.0" in the installer screen:
image

Claude tracked it down to the follow code which hardcoded "1.0.0". I asked it to fix this with the smallest changes possible -- I'm sure you've also seen AI generated code with 2 changes and 10 comments ;)

This is what it came up with and verified. I've reviewed it and it looks clean. Here's Claude's concise description of the issue (not as concise as I was hoping for but...)


macOS app bundles built by jDeploy always report version 1.0.0: MacBundler.processInfoPlist writes that literal into both CFBundleVersion and CFBundleShortVersionString.

Updated per review: the bundle is built once and then auto-updates without being rebuilt, so an exact version would go stale. The plist now only gets the part of the version that stays true for the bundle's lifetime, derived from the npm range the installer already computes from the user's auto-update setting:

Auto-update setting npm range Plist version
Off 1.0.138 1.0.138
Off, pre-release 1.2.3-beta.1 1.2.3
PatchesOnly ~1.0 1.0
MinorOnly ^1 1
Stable latest placeholder (1.0.0)
branch builds 0.0.0-<branch> placeholder (1.0.0)

Changes:

  • Main.createBundleVersion(npmRange) (installer, next to createSemVerForVersion) computes the value above, stripping any pre-release or build suffix. install() sets it on AppInfo.version, on Mac only — AppInfo.version also feeds the Windows uninstall DisplayVersion, which this PR leaves alone.
  • AppDescription gains a version field (kept distinct from npmVersion); Bundler.createAppDescription copies it from AppInfo with no initialAppVersion fallback.
  • MacBundler writes it to both plist keys, falling back to 1.0.0 when it is unset.
  • The CLI/DMG path (PackageService.loadAppInfo) does not set a version, so DMG-built bundles keep the placeholder.
  • Tests: MainBundleVersionTest covers each row of the table, including suffix stripping; MacBundlerInfoPlistVersionTest covers the versioned and fallback plist output.

Not included: the opt-in exact version for publishers who force a full reinstall via minInitialAppVersion. Note that with Stable as the default setting, default installs still show 1.0.0.

🤖 Generated with Claude Code

@shannah shannah left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracking this down. The plumbing is clean (keeping version separate from npmVersion is the right call), but there's a design problem with what ends up in the plist.

The problem: installed bundles go stale

The .app is built once, at install time or DMG build time. After that, the launcher auto-updates the app without rebuilding the bundle. Info.plist is covered by the code signature, so it can't be patched in place on update either. So with this PR, an app installed at 1.0.138 that auto-updates to 1.0.150 keeps saying 1.0.138 in Finder → Get Info and in replace dialogs. A specific but wrong version is arguably worse than an obvious placeholder, which is why 1.0.0 has stayed hard-coded so far.

Suggested approach: only write a version that stays true for the bundle's lifetime

At install time we already know how far the app can drift: Main.java:1911 turns the user's auto-update setting into the npm range the launcher follows (createSemVerForVersion). The plist version could be derived the same way:

Auto-update setting npm range in app.xml Plist version
Off exact version real version, e.g. 1.0.138
PatchesOnly ~1.0 1.0
MinorOnly ^1 1
Stable latest placeholder (keep 1.0.0)
branch builds (0.0.0-<branch>) the branch placeholder

Concretely:

  • Add a small helper next to createSemVerForVersion in the installer that computes this value. Set it on AppInfo as the version, instead of falling back to initialAppVersion in Bundler.createAppDescription.
  • On the CLI/DMG path (PackageService.loadAppInfo), the user's update policy isn't known yet, and the app copied out of the DMG auto-updates too. So the placeholder is the honest default there, unless we can tell updates are disabled.
  • Strip any pre-release or build suffix before writing (1.2.3-beta.1 → 1.2.3). VersionCleaner keeps the -… suffix, and CFBundleVersion / CFBundleShortVersionString should be numeric (one to three dot-separated integers). Please add a test case for this.

The rest of the PR (the new AppDescription.version field, the 1.0.0 fallback in MacBundler, the test class) can stay as is. Only the value fed into it changes.

A case where the exact version can be meaningful

There's one more case where writing the exact version is fine. Publishers can set package.json properties (e.g. minInitialAppVersion, and similar launcher-version minimums) that force the full installer to run on update, and that rebuilds the bundle and its plist. If every release that matters sets these, the bundle never falls far behind. The catch is that this depends on how future releases are configured, not on anything we know at bundle time. So it can't be the default. It could be an opt-in for publishers who commit to that workflow, and it's worth noting in the docs if that route is taken.

FYI, the build-mac-x64 (17) failure is unrelated: JDeployIntegrationTest.testJDeployScript hit a DNS error (ENOTFOUND api.azul.com) downloading a JRE.


Generated by Claude Code

@jpage4500

Copy link
Copy Markdown
Contributor Author

Thanks for tracking this down. The plumbing is clean (keeping version separate from npmVersion is the right call), but there's a design problem with what ends up in the plist.

I updated the PR with suggested fixes I believe. I'll paste what they were below. Happy to do further testing as well.

On a side-note.. I'm seeing a near future where Claude just creates issues on it's own.. then disagrees with itself and finally comes up with something that makes all of it's instances happy. And then someone will get a huge bill ;)


Simplest fix

The installer already computes the npm range from the auto-update setting (Main.java:1911), so the plist version can be derived from that string rather than from a second switch statement. It comes to three small edits plus a test.

  1. installer/.../Main.java: add a helper next to createSemVerForVersion and call it right after line 1911:
    static String createBundleVersion(String npmRange) {
    if ("latest".equals(npmRange) || npmRange.startsWith("0.0.0-")) return null;
    return npmRange.replaceFirst("^[~^]", "").replaceFirst("[-+].*$", "");
    }
    if (Platform.getSystemPlatform().isMac()) {
    appInfo().setVersion(createBundleVersion(appInfo().getNpmVersion()));
    }
    This gives every row of shannah's table, including the suffix stripping:
Auto-update setting npm range Plist version
Off 1.0.138 1.0.138
Off, pre-release 1.2.3-beta.1 1.2.3
PatchesOnly ~1.0 1.0
MinorOnly ^1 1
Stable or branch build latest / 0.0.0-… null, so MacBundler falls back to 1.0.0
  1. Bundler.java:122: drop the initialAppVersion fallback, leaving app.setVersion(appInfo.getVersion());.
  2. PackageService.java:865-867: delete the three added lines, so the CLI/DMG path keeps the placeholder as requested.
  3. Test: add a small unit test for createBundleVersion covering the rows above; that satisfies the request for a pre-release test case.

The Mac guard in step 1 matters: AppInfo.version also feeds the Windows uninstall DisplayVersion (InstallWindowsRegistry.java:1040). Without the guard this PR would change Windows behaviour too.

I'd skip the opt-in exact-version idea (minInitialAppVersion); shannah framed it as optional.

The catch

The default auto-update setting is Stable (InstallationSettings.java:44), and DMG builds get the placeholder too. So after this fix, most installs will still show 1.0.0, and only users who pick Off, Patches or Minor see a real version. If your screenshot came from a default install, this version of the PR won't change what you saw (I couldn't view the image).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants