diff --git a/crates/launcher-common/src/template.rs b/crates/launcher-common/src/template.rs index 1ae1dcb..d9ca545 100644 --- a/crates/launcher-common/src/template.rs +++ b/crates/launcher-common/src/template.rs @@ -717,6 +717,51 @@ mod tests { ); } + /// The emitted licence is the app's `[project].license`, once per header. + /// + /// The template used to carry a literal MPL-2.0 header — twice — and never + /// read `app_license`, so realigning an AGPL repo's launcher silently + /// relicensed it (game-server-admin, 2026-09-30). A non-default licence is + /// the only input that tells "reads the config" from "happens to match". + #[test] + fn rendered_licence_is_the_projects_and_appears_once_per_header() { + let mut cfg = sample_config(); + cfg.project.license = Some("AGPL-3.0-or-later".into()); + let std_ = LauncherStandard::baked().expect("baked standard should parse"); + let out = render(&cfg, &std_, None).expect("template should render"); + + let spdx: Vec<&str> = out + .lines() + .filter(|l| l.contains("SPDX-License-Identifier")) + .collect(); + assert_eq!( + spdx, + [ + "# SPDX-License-Identifier: AGPL-3.0-or-later", + "# ;; SPDX-License-Identifier: AGPL-3.0-or-later", + ], + "exactly one script header and one deed header, both the project's licence" + ); + assert_eq!( + out.lines().nth(1), + Some("# SPDX-License-Identifier: AGPL-3.0-or-later"), + "the licence header must sit directly under the shebang" + ); + } + + /// With no `[project].license` the launcher keeps its historical MPL-2.0. + #[test] + fn rendered_licence_defaults_to_mpl_when_the_project_sets_none() { + let cfg = sample_config(); + assert_eq!(cfg.project.license, None); + let std_ = LauncherStandard::baked().expect("baked standard should parse"); + let out = render(&cfg, &std_, None).expect("template should render"); + assert_eq!( + out.lines().nth(1), + Some("# SPDX-License-Identifier: MPL-2.0") + ); + } + /// The port has exactly one spelling in the generated script (#49 AC3). /// /// `APP_PORT` used to be emitted into every `server-url` launcher and read diff --git a/crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-23_stapeln-launcher-deed.sh b/crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-23_stapeln-launcher-deed.sh index 017d215..9c66e5d 100644 --- a/crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-23_stapeln-launcher-deed.sh +++ b/crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-23_stapeln-launcher-deed.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash # SPDX-License-Identifier: MPL-2.0 -# SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # @launcher-deed begin diff --git a/templates/launcher.sh.tera b/templates/launcher.sh.tera index 3a0489c..632fe1a 100644 --- a/templates/launcher.sh.tera +++ b/templates/launcher.sh.tera @@ -1,5 +1,4 @@ #!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 {# NOTE: the shebang MUST be the literal first line of this template. It previously sat below this comment block, and every launcher the generator emitted began with a blank line -- shellcheck SC2148, and a @@ -23,12 +22,18 @@ All three shapes emit the same --integ / --disinteg / --help cross-platform surface so desktop integration stays uniform across the estate. + + The emitted script's licence is the APP's (`[project].license`, default + MPL-2.0), not this template's. It used to be a literal MPL-2.0 -- emitted + twice -- so realigning a launcher in an AGPL repo silently relicensed it + (game-server-admin, 2026-09-30). The tag name is a Tera string so that a + licence scanner reading THIS file sees only the template's own header above. -#} -# SPDX-License-Identifier: MPL-2.0 +# {{ "SPDX-License-Identifier" }}: {{ app_license }} # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # @launcher-deed begin -# ;; SPDX-License-Identifier: MPL-2.0 +# ;; {{ "SPDX-License-Identifier" }}: {{ app_license }} # (praxis-deed # :schema-version "1.0.0" # :canonical-name "{{ app_name | deedstr }}-launcher"