diff --git a/src/titanium/modules/detect-ti-modules.ts b/src/titanium/modules/detect-ti-modules.ts index 80001ae0..b36f4528 100644 --- a/src/titanium/modules/detect-ti-modules.ts +++ b/src/titanium/modules/detect-ti-modules.ts @@ -8,6 +8,29 @@ 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 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 + * 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 +357,13 @@ async function readManifest(manifestFile: string) { throw new Error(`Module manifest has invalid version: ${manifest.version}`); } + if (isPlaceholderLicense(manifest.license)) { + warn( + `Module ${manifest.moduleid} has not set a license (${manifestFile}): ` + + `choose an SPDX license such as "Apache-2.0".` + ); + } + 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); + }); +});