From 4685c7eb877d3ea8d1d853abbff24fe725d26940 Mon Sep 17 00:00:00 2001 From: Chris Barber Date: Tue, 1 Sep 2026 10:23:04 -0500 Subject: [PATCH 1/2] Warn when a module manifest carries the scaffolding licence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SDK's module templates seed `license: Specify your license`, and nothing prompts anyone to replace it. Three modules under tidev have shipped releases with it still in place — titanium-identity, titanium-onboarding and ti.previewinteraction — and it surfaces anywhere the manifest does. `readManifest()` now warns when it sees one. It deliberately does not throw: the placeholder belongs to a dependency, and failing an app build because somebody else's metadata is unfilled punishes the wrong person. The predicate is exported so a packaging or release step, which is talking to the author who can actually fix it, can reject what this only warns about. --- src/titanium/modules/detect-ti-modules.ts | 28 +++++++++++++++++++ .../modules/placeholder-license.test.ts | 26 +++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 test/titanium/modules/placeholder-license.test.ts diff --git a/src/titanium/modules/detect-ti-modules.ts b/src/titanium/modules/detect-ti-modules.ts index 80001ae0..b00c213f 100644 --- a/src/titanium/modules/detect-ti-modules.ts +++ b/src/titanium/modules/detect-ti-modules.ts @@ -8,6 +8,28 @@ import { snooplogg } from 'snooplogg'; const { log, warn } = snooplogg('ti:modules'); +/** + * The `license` values the module scaffolding writes, which mean the author + * never filled one in. + * + * The SDK's module templates seed `Specify your license`, and nothing prompts + * anyone to replace it. Three modules under tidev have shipped releases with it + * still in place — `titanium-identity`, `titanium-onboarding` and + * `ti.previewinteraction` — and it reaches anywhere the manifest is surfaced. + */ +const PLACEHOLDER_LICENSES = new Set(['specify your license', 'your license here']); + +/** + * Whether a manifest's `license` is a scaffolding default rather than a licence. + * + * Exported so a packaging or release step can reject what this only warns about: + * failing an app build because a *dependency's* metadata is unfilled would + * punish the wrong person. + */ +export function isPlaceholderLicense(license: string | undefined): boolean { + return license !== undefined && PLACEHOLDER_LICENSES.has(license.trim().toLowerCase()); +} + /** * Detects Titanium modules in the Titanium SDK install locations and user search paths, then * returns a registry of found modules. @@ -334,6 +356,12 @@ async function readManifest(manifestFile: string) { throw new Error(`Module manifest has invalid version: ${manifest.version}`); } + if (isPlaceholderLicense(manifest.license)) { + warn( + `Module ${manifest.moduleid} declares the scaffolding placeholder as its license: "${manifest.license}" (${manifestFile})` + ); + } + const platform = platformAliases[manifest.platform] || manifest.platform; return { diff --git a/test/titanium/modules/placeholder-license.test.ts b/test/titanium/modules/placeholder-license.test.ts new file mode 100644 index 00000000..7220b79a --- /dev/null +++ b/test/titanium/modules/placeholder-license.test.ts @@ -0,0 +1,26 @@ +import { isPlaceholderLicense } from '../../../src/titanium/index.js'; +import { describe, expect, it } from 'vitest'; + +describe('isPlaceholderLicense()', () => { + it('recognises the scaffolding default', () => { + expect(isPlaceholderLicense('Specify your license')).toBe(true); + }); + + it('ignores case and surrounding whitespace', () => { + // The templates emit it with a trailing newline, and manifests are + // hand-edited, so neither is a reliable shape. + expect(isPlaceholderLicense(' specify your LICENSE ')).toBe(true); + }); + + it('accepts a real licence', () => { + for (const license of ['Apache-2.0', 'MIT', 'Apache Public License v2', 'Proprietary']) { + expect(isPlaceholderLicense(license)).toBe(false); + } + }); + + it('treats a missing licence as not a placeholder', () => { + // Absent is a different problem from unfilled, and is not this check's. + expect(isPlaceholderLicense(undefined)).toBe(false); + expect(isPlaceholderLicense('')).toBe(false); + }); +}); From ecd5d9e431668e11f5c440243ea783c66f3c7061 Mon Sep 17 00:00:00 2001 From: Chris Barber Date: Wed, 2 Sep 2026 09:26:07 -0500 Subject: [PATCH 2/2] Say what to do instead of naming the template The warning called the value a "scaffolding placeholder", which describes where the string came from rather than what the reader should do about it. It now names the fix: Module ti.foo has not set a license (ios/manifest): choose an SPDX license such as "Apache-2.0". Suggesting SPDX rather than any free-form string is the point: sixteen modules currently spell Apache 2.0 six different ways, which is what made this worth warning about at all. --- src/titanium/modules/detect-ti-modules.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/titanium/modules/detect-ti-modules.ts b/src/titanium/modules/detect-ti-modules.ts index b00c213f..b36f4528 100644 --- a/src/titanium/modules/detect-ti-modules.ts +++ b/src/titanium/modules/detect-ti-modules.ts @@ -20,7 +20,8 @@ const { log, warn } = snooplogg('ti:modules'); const PLACEHOLDER_LICENSES = new Set(['specify your license', 'your license here']); /** - * Whether a manifest's `license` is a scaffolding default rather than a licence. + * Whether a manifest's `license` is one of the template defaults, meaning no + * licence was ever chosen. * * Exported so a packaging or release step can reject what this only warns about: * failing an app build because a *dependency's* metadata is unfilled would @@ -358,7 +359,8 @@ async function readManifest(manifestFile: string) { if (isPlaceholderLicense(manifest.license)) { warn( - `Module ${manifest.moduleid} declares the scaffolding placeholder as its license: "${manifest.license}" (${manifestFile})` + `Module ${manifest.moduleid} has not set a license (${manifestFile}): ` + + `choose an SPDX license such as "Apache-2.0".` ); }