Files
NeitzelFXDesigner/docs/standalone-distribution.md

8.0 KiB

Bundled standalone distribution

The first packaging slice produces a relocatable macOS NeitzelFXDesigner.app, a ZIP containing that app, and a SHA-256 file inventory. The application carries a Java 25 runtime, JavaFX 26, Kit 26 and the shared designer modules. It does not use JAVA_HOME to choose its host JVM. Application builds and previews still use the explicitly configured application JDKs and dependencies.

Build and launch

On the qualified macOS workstation, with JDK 25 and the recorded local NeitzelLib artifacts installed:

python3 scripts/package-standalone.py --java-home "$JAVA_HOME"

The script verifies the runtime artifact baseline, builds/tests the standalone dependency reactor and target tooling, resolves production dependencies, creates a runtime with jlink, and builds the native app image with jpackage. It refuses to replace existing package outputs; use --destination for another output directory. Default output is target/distribution/.

Launch the bundled executable directly, including from an IDE external-tool command:

"/path/to/NeitzelFXDesigner.app/Contents/MacOS/NeitzelFXDesigner" \
  --configuration /project/neitzel-fx-designer.json \
  --settings /project/.neitzel-fx-designer/runtimes.json \
  --context desktop \
  /project/app/src/main/resources/example/Person.fxml

--context can be omitted when exactly one context exists. The active context appears in each editor window title and Component navigation inherits it. For multiple contexts this slice requires an explicit selection; it does not yet provide the graphical selection/remembering flow. --maven /path/to/mvnw provides a fallback when the project has no Maven wrapper. See configuration and runtimes for the shared configuration and machine-local JDK mappings. No target JDK is silently substituted with the bundled host JVM.

Omit the FXML argument to select a View in a file chooser. --help and --version run without opening JavaFX; unknown, duplicate or incomplete options fail with usage and exit status 2. The prior host.properties View.fxml invocation and the explicit --launch host.properties [View.fxml] form remain supported. The project-options form obtains worker tooling from the bundle; legacy launch settings retain their explicit tool paths.

This slice is command-line launched. Finder project selection, file associations, install/update flows and remembered context selection remain follow-ups. There is no claim that double-clicking an unconfigured app opens a project.

Package boundaries and provenance

The launcher manifest orders the patched shared Kit classes before upstream Kit. Production JARs retain their embedded license/notice resources, the app includes THIRD-PARTY-NOTICES.md, and the linked JDK retains its legal files. The package inventory records the source commit and dirty-tree flag, JDK release metadata, architecture and every bundled file hash. It identifies a development artifact, not a published release or a reproducible-build claim.

jpackage adds nested input JARs to its generated host classpath. Worker tools are therefore copied to Contents/app/tools after launcher creation; an assembly assertion rejects leaked tool paths. The host package includes no IntelliJ SDK, target application library or test JARs. Target tools, contracts and preview API are supplied separately to the target JVM, which resolves application dependencies from the immutable build snapshot.

The application version remains 0.1.0-SNAPSHOT. The macOS bundle uses numeric version 1.0 because this JDK's jpackage rejects a zero leading version component; that bundle value is not a product 1.0 release. The archive is a local development distribution, without Developer ID signing or notarization. Public distribution still needs the product license decision, release/provenance review and separately published project-facing tools/runtime artifacts. Application building still requires its configured JDK, Maven and dependency access; bundling the designer does not make builds offline.

Automated qualification

python3 scripts/check-standalone-package.py \
  --app target/distribution/NeitzelFXDesigner.app \
  --java-home "$JAVA_HOME" --target-java-home "$TARGET_JAVA_HOME"

The test verifies the inventory, extracts the ZIP into a path containing spaces, verifies the extracted files against the inventory, and launches its native executable with an invalid JAVA_HOME and a restricted PATH. An external Java agent observes the real production application, drives the shared authoring suite, and verifies runtime identity, JavaFX version, patched Kit origin and absence of the preview API from the host classloader. Each run extracts JavaFX natives into a fresh cache and verifies their hashes against the bundled JARs. Test classes are never included in the distribution. Agent options are added only to the disposable copy's launcher configuration, so they do not propagate into Maven or target JVMs.

The classpath and named-module fixtures exercise Build and Refresh, rendering, binding/structure edits, diagnostics, reviewed generation, navigation, exact source/history/save behavior and target loading. Tests close actual application windows and check worker termination. Invalid configuration exercises source-only editing and saving; missing application-JDK setup must fail the build without disabling source editing. A native crash guard checks for new JVM crash reports. --mode selects one scenario for focused regression runs.

The optional-FXML file chooser is implemented but native chooser interaction is not part of this automated qualification. Existing skipped human-input checks stay skipped. Other CPU architectures, Windows/Linux, quarantined downloads and signed/notarized installation remain unqualified.

Qualification record — 2026-11-13

Qualified on macOS 27.2 arm64 with Homebrew Java 25.0.5.0.3 as the bundled host and the existing Java 21 / JavaFX 21.0.7 target fixtures. The full bootstrap passed 160 unit/adapter tests with zero failures/errors/skips, 38 target processes with 548 checks, both native standalone workflows, 48 lifecycle cycles, abrupt owner death, bytecode/dependency boundaries, plugin packaging and documentation checks. No registered IntelliJ GUI rerun was needed for this standalone-only change; its document adapters are included in the bootstrap.

The relocated production app passed complete classpath and module-path authoring workflows, then invalid-configuration source editing/saving and missing-JDK failure/source-availability scenarios. Every case verified bundled runtime identity, Kit patch origin, target-API exclusion, fresh native-library hashes and worker shutdown. The final ZIP was extracted into another path with spaces; every extracted file matched the qualified app inventory, and its startup/source-only save/cleanup smoke passed. Output-collision refusal and CLI usage/version/error checks passed.

Initial assembly caught manifest-line folding and macOS numeric bundle-version requirements. Launcher inspection caught jpackage's recursive tool-JAR classpath inclusion, corrected before qualification. Two early harness runs were invalidated: environment-wide agent injection reached Maven, and a concurrent clean build removed a test copy under target. The final harness uses host-only launcher options and disposable copies outside the build tree. Neither invalid run is counted as a pass.

Local evidence: /tmp/standalone-package-build-qualified.log, /tmp/standalone-package-bootstrap.log, /tmp/standalone-package-qualified.log, /tmp/standalone-package-archive.log, and the designer-package-check-b46en3a8 / designer-package-check-wq50e0ct temporary directories reported by those logs. The archive is target/distribution/NeitzelFXDesigner-macos.zip, SHA-256 dcbb95c4d80371daca612b863701cb120a1a922a114984c3f0683c9edb176fa1; the sibling package-manifest.json records individual file hashes and the pre-commit working-tree build provenance. These local outputs are not committed or published releases. No manual testing was used.