@helpers4/license 3.1.1-alpha.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -20,7 +20,7 @@
20
20
  * @example
21
21
  * agree('custom', 'MIT')
22
22
  * // => true (a vague claim is never itself a conflict)
23
- * @since next
23
+ * @since 3.1.2
24
24
  */
25
25
  declare function agree(a: string, b: string): boolean;
26
26
 
@@ -50,7 +50,7 @@ declare function agree(a: string, b: string): boolean;
50
50
  * @example
51
51
  * families('custom:Acme End User License')
52
52
  * // => Set(1) { 'unknown' }
53
- * @since next
53
+ * @since 3.1.2
54
54
  */
55
55
  declare function families(raw: string): Set<string>;
56
56
 
@@ -71,7 +71,7 @@ declare function families(raw: string): Set<string>;
71
71
  * @example
72
72
  * isKnown('custom:Acme End User License')
73
73
  * // => false
74
- * @since next
74
+ * @since 3.1.2
75
75
  */
76
76
  declare function isKnown(raw: string): boolean;
77
77
 
package/lib/index.js CHANGED
@@ -156,7 +156,7 @@ function splitTokens(raw) {
156
156
  * @example
157
157
  * families('custom:Acme End User License')
158
158
  * // => Set(1) { 'unknown' }
159
- * @since next
159
+ * @since 3.1.2
160
160
  */
161
161
  function families(raw) {
162
162
  const result = /* @__PURE__ */ new Set();
@@ -194,7 +194,7 @@ function families(raw) {
194
194
  * @example
195
195
  * agree('custom', 'MIT')
196
196
  * // => true (a vague claim is never itself a conflict)
197
- * @since next
197
+ * @since 3.1.2
198
198
  */
199
199
  function agree(a, b) {
200
200
  const knownA = new Set([...families(a)].filter((f) => f !== UNKNOWN_FAMILY));
@@ -222,7 +222,7 @@ function agree(a, b) {
222
222
  * @example
223
223
  * isKnown('custom:Acme End User License')
224
224
  * // => false
225
- * @since next
225
+ * @since 3.1.2
226
226
  */
227
227
  function isKnown(raw) {
228
228
  for (const family of families(raw)) if (family !== "unknown") return true;
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../helpers/license/_unknownFamily.ts","../../../helpers/license/families.ts","../../../helpers/license/agree.ts","../../../helpers/license/isKnown.ts"],"sourcesContent":["/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\n/**\n * Sentinel family for a license token that carries no comparable content — `custom`, a bare\n * vendor name inside `custom:<...>`/`LicenseRef-<...>`, or an explicit \"we don't know\". Two\n * unknown tokens are never treated as either agreeing or conflicting with each other, since\n * neither is a real claim to compare against the other.\n *\n * Not exported from the package — `families()`, `agree()` and `isKnown()` already fully\n * encapsulate this concept; a consumer never needs the sentinel's actual value.\n * @ignore\n */\nexport const UNKNOWN_FAMILY = 'unknown';\n","/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\nimport { UNKNOWN_FAMILY } from './_unknownFamily';\n\n// Longest/most specific alias keys first within a family so a substring match never lets a\n// shorter, less specific alias shadow a longer one that also matches the same input.\nconst FAMILY_ALIASES: Record<string, readonly string[]> = {\n mit: ['mit-0', 'mit'],\n gpl: [\n 'gpl-3.0-or-later', 'gpl-3.0-only', 'gpl-2.0-or-later', 'gpl-2.0-only',\n 'gpl-1.0-or-later', 'gpl-3.0+', 'gpl-2.0+', 'gpl-3.0', 'gpl-2.0',\n 'gplv3', 'gplv2', 'gpl3+', 'gpl3', 'gpl2', 'gpl-3', 'gpl-2', 'gpl',\n ],\n lgpl: [\n 'lgpl-3.0-or-later', 'lgpl-3.0-only', 'lgpl-2.1-or-later', 'lgpl-2.1-only',\n 'lgpl-2.0-or-later', 'lgpl-2.0-only', 'lgpl-3.0+', 'lgpl-2.1+', 'lgpl-3.0', 'lgpl-2.1',\n 'lgplv3', 'lgplv2.1', 'lgpl3', 'lgpl2.1', 'lgpl2', 'lgpl',\n ],\n agpl: ['agpl-3.0-or-later', 'agpl-3.0-only', 'agpl-3.0', 'agplv3', 'agpl3', 'agpl'],\n bsd: [\n 'bsd-3-clause', 'bsd-2-clause', '0bsd', 'bsd3', 'bsd 3-clause',\n 'bsd 2-clause', 'bsd-3', 'bsd',\n ],\n apache: ['apache-2.0', 'apache license 2.0', 'apache license, version 2.0', 'apache2', 'apache 2.0', 'apache'],\n mpl: ['mpl-2.0', 'mpl2', 'mpl'],\n artistic: ['artistic-2.0', 'artistic2.0', 'perlartistic', 'artistic'],\n isc: ['isc'],\n unlicense: ['unlicense'],\n zlib: ['zlib'],\n wtfpl: ['wtfpl'],\n php: ['php'],\n epl: ['epl'],\n cc: ['cc0-1.0', 'cc0', 'cc-by-sa-4.0', 'cc-by-nc-sa-4.0', 'cc-by-sa', 'ccpl'],\n 'public-domain': ['public domain'],\n boost: ['bsl-1.0', 'boost'],\n eupl: ['eupl-1.2', 'eupl'],\n postgresql: ['postgresql'],\n psf: ['psf-2.0', 'psf'],\n gfdl: ['gfdl-1.3', 'gfdl'],\n sspl: ['sspl-1.0', 'sspl'],\n busl: ['busl-1.1', 'busl'],\n};\n\n// Non-informative tokens — a real license claim was made, but it carries no comparable content\n// (\"custom\", a bare vendor name inside `custom:<...>`/`LicenseRef-<...>`, or an explicit \"we\n// don't know\"). Checked first, before the alias table, so e.g. \"custom:MIT\" reads as\n// UNKNOWN_FAMILY rather than incorrectly matching MIT's alias substring — a `custom:<...>`\n// convention names the *vendor's own license text*, not necessarily the SPDX license the name\n// resembles.\nconst UNKNOWN_PATTERNS = [/^custom\\b/, /^licenseref-/, /unknown/, /^none$/, /^various$/, /^unknow$/];\n\nfunction matchAlias(normalized: string): string | undefined {\n for (const [family, aliases] of Object.entries(FAMILY_ALIASES)) {\n if (aliases.includes(normalized)) return family;\n }\n return undefined;\n}\n\n// Splits an SPDX-ish compound expression (\"GPL-3.0+ AND LGPL-3.0+\", or a lowercase \"GPL-2.0+ and\n// GFDL-1.3\") into its individual license tokens. `WITH` exceptions (\"Apache-2.0 WITH\n// LLVM-exception\") are dropped entirely — the exception clause never itself resembles a distinct\n// license family, so it would otherwise either land in UNKNOWN_FAMILY for no reason or dilute the\n// comparison with a token that isn't really a separate license claim.\nfunction splitTokens(raw: string): string[] {\n return raw\n .split(/\\s+(?:and|or)\\s+/i)\n .map((token) => token.split(/\\s+with\\s+/i)[0].trim())\n .filter((token) => token.length > 0);\n}\n\n/**\n * Groups a raw, possibly compound license string into its coarse family set — e.g.\n * `\"GPL-3.0-or-later AND LGPL-3.0-or-later\"` yields `{\"gpl\", \"lgpl\"}`. Two sources reporting the\n * same real license under different notation (`\"GPL3\"` vs `\"GPL-3.0-or-later\"`, `\"GPL\"` vs\n * `\"GPL2\"`) yield the same family, so they read as agreeing rather than conflicting.\n *\n * Proprietary/custom/unknown tokens all collapse to a single shared unknown-family sentinel\n * rather than each becoming their own one-off family, so a batch of otherwise-identical\n * `\"custom:<vendor>\"` strings don't look like dozens of distinct disagreeing licenses. Use\n * {@link isKnown} to check whether any real (non-sentinel) family was found.\n * @param raw - A raw license string, single (`\"MIT\"`) or compound (`\"GPL-3.0+ AND LGPL-3.0+\"`)\n * @returns The set of families found; empty only when `raw` has no tokens at all (e.g. an empty string)\n * @example\n * families('GPL3')\n * // => Set(1) { 'gpl' }\n * @example\n * families('GPL-3.0-or-later AND LGPL-3.0-or-later')\n * // => Set(2) { 'gpl', 'lgpl' }\n * @example\n * families('custom:Acme End User License')\n * // => Set(1) { 'unknown' }\n * @since next\n */\nexport function families(raw: string): Set<string> {\n const result = new Set<string>();\n for (const token of splitTokens(raw)) {\n const normalized = token.toLowerCase();\n if (UNKNOWN_PATTERNS.some((pattern) => pattern.test(normalized))) {\n result.add(UNKNOWN_FAMILY);\n continue;\n }\n result.add(matchAlias(normalized) ?? UNKNOWN_FAMILY);\n }\n return result;\n}\n","/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\nimport { UNKNOWN_FAMILY } from './_unknownFamily';\nimport { families } from './families';\n\n/**\n * Whether two raw license strings agree closely enough to count as \"no real conflict\" — true\n * when their family sets share at least one real (known) family, or when either side is entirely\n * unknown (e.g. `\"custom\"`/`\"unknown\"`) — a vague claim is never itself evidence of disagreement.\n * Only two *different*, both-known family sets (e.g. `bsd` vs `apache`) count as a real conflict.\n * @param a - The first raw license string\n * @param b - The second raw license string\n * @returns `true` when the two strings can be treated as the same license (or too vague to tell), `false` on a real family conflict\n * @example\n * agree('GPL3', 'GPL-3.0-or-later')\n * // => true\n * @example\n * agree('BSD-2-Clause', 'Apache-2.0')\n * // => false\n * @example\n * agree('custom', 'MIT')\n * // => true (a vague claim is never itself a conflict)\n * @since next\n */\nexport function agree(a: string, b: string): boolean {\n const knownA = new Set([...families(a)].filter((f) => f !== UNKNOWN_FAMILY));\n const knownB = new Set([...families(b)].filter((f) => f !== UNKNOWN_FAMILY));\n\n if (knownA.size === 0 || knownB.size === 0) return true;\n for (const family of knownA) if (knownB.has(family)) return true;\n return false;\n}\n","/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\nimport { UNKNOWN_FAMILY } from './_unknownFamily';\nimport { families } from './families';\n\n/**\n * Whether a raw license string resolves to at least one recognized family — `false` for an\n * empty string, a purely non-informative claim (`\"custom\"`, `\"unknown\"`, `\"LicenseRef-EULA\"`,\n * ...), or a compound expression where every token is non-informative.\n * @param raw - The raw license string to check\n * @returns `true` if at least one token in `raw` resolves to a real family\n * @example\n * isKnown('GPL-3.0-or-later')\n * // => true\n * @example\n * isKnown('custom:Acme End User License')\n * // => false\n * @since next\n */\nexport function isKnown(raw: string): boolean {\n for (const family of families(raw)) {\n if (family !== UNKNOWN_FAMILY) return true;\n }\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,iBAAiB;;;;;;;;ACN9B,IAAM,iBAAoD;CACxD,KAAK,CAAC,SAAS,KAAK;CACpB,KAAK;EACH;EAAoB;EAAgB;EAAoB;EACxD;EAAoB;EAAY;EAAY;EAAW;EACvD;EAAS;EAAS;EAAS;EAAQ;EAAQ;EAAS;EAAS;CAC/D;CACA,MAAM;EACJ;EAAqB;EAAiB;EAAqB;EAC3D;EAAqB;EAAiB;EAAa;EAAa;EAAY;EAC5E;EAAU;EAAY;EAAS;EAAW;EAAS;CACrD;CACA,MAAM;EAAC;EAAqB;EAAiB;EAAY;EAAU;EAAS;CAAM;CAClF,KAAK;EACH;EAAgB;EAAgB;EAAQ;EAAQ;EAChD;EAAgB;EAAS;CAC3B;CACA,QAAQ;EAAC;EAAc;EAAsB;EAA+B;EAAW;EAAc;CAAQ;CAC7G,KAAK;EAAC;EAAW;EAAQ;CAAK;CAC9B,UAAU;EAAC;EAAgB;EAAe;EAAgB;CAAU;CACpE,KAAK,CAAC,KAAK;CACX,WAAW,CAAC,WAAW;CACvB,MAAM,CAAC,MAAM;CACb,OAAO,CAAC,OAAO;CACf,KAAK,CAAC,KAAK;CACX,KAAK,CAAC,KAAK;CACX,IAAI;EAAC;EAAW;EAAO;EAAgB;EAAmB;EAAY;CAAM;CAC5E,iBAAiB,CAAC,eAAe;CACjC,OAAO,CAAC,WAAW,OAAO;CAC1B,MAAM,CAAC,YAAY,MAAM;CACzB,YAAY,CAAC,YAAY;CACzB,KAAK,CAAC,WAAW,KAAK;CACtB,MAAM,CAAC,YAAY,MAAM;CACzB,MAAM,CAAC,YAAY,MAAM;CACzB,MAAM,CAAC,YAAY,MAAM;AAC3B;AAQA,IAAM,mBAAmB;CAAC;CAAa;CAAgB;CAAW;CAAU;CAAa;AAAU;AAEnG,SAAS,WAAW,YAAwC;CAC1D,KAAK,MAAM,CAAC,QAAQ,YAAY,OAAO,QAAQ,cAAc,GAC3D,IAAI,QAAQ,SAAS,UAAU,GAAG,OAAO;AAG7C;AAOA,SAAS,YAAY,KAAuB;CAC1C,OAAO,IACJ,MAAM,mBAAmB,CAAC,CAC1B,KAAK,UAAU,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CACpD,QAAQ,UAAU,MAAM,SAAS,CAAC;AACvC;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,SAAS,KAA0B;CACjD,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,SAAS,YAAY,GAAG,GAAG;EACpC,MAAM,aAAa,MAAM,YAAY;EACrC,IAAI,iBAAiB,MAAM,YAAY,QAAQ,KAAK,UAAU,CAAC,GAAG;GAChE,OAAO,IAAI,cAAc;GACzB;EACF;EACA,OAAO,IAAI,WAAW,UAAU,KAAA,SAAmB;CACrD;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;AChFA,SAAgB,MAAM,GAAW,GAAoB;CACnD,MAAM,SAAS,IAAI,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,cAAc,CAAC;CAC3E,MAAM,SAAS,IAAI,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,cAAc,CAAC;CAE3E,IAAI,OAAO,SAAS,KAAK,OAAO,SAAS,GAAG,OAAO;CACnD,KAAK,MAAM,UAAU,QAAQ,IAAI,OAAO,IAAI,MAAM,GAAG,OAAO;CAC5D,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;ACZA,SAAgB,QAAQ,KAAsB;CAC5C,KAAK,MAAM,UAAU,SAAS,GAAG,GAC/B,IAAI,WAAA,WAA2B,OAAO;CAExC,OAAO;AACT"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../helpers/license/_unknownFamily.ts","../../../helpers/license/families.ts","../../../helpers/license/agree.ts","../../../helpers/license/isKnown.ts"],"sourcesContent":["/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\n/**\n * Sentinel family for a license token that carries no comparable content — `custom`, a bare\n * vendor name inside `custom:<...>`/`LicenseRef-<...>`, or an explicit \"we don't know\". Two\n * unknown tokens are never treated as either agreeing or conflicting with each other, since\n * neither is a real claim to compare against the other.\n *\n * Not exported from the package — `families()`, `agree()` and `isKnown()` already fully\n * encapsulate this concept; a consumer never needs the sentinel's actual value.\n * @ignore\n */\nexport const UNKNOWN_FAMILY = 'unknown';\n","/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\nimport { UNKNOWN_FAMILY } from './_unknownFamily';\n\n// Longest/most specific alias keys first within a family so a substring match never lets a\n// shorter, less specific alias shadow a longer one that also matches the same input.\nconst FAMILY_ALIASES: Record<string, readonly string[]> = {\n mit: ['mit-0', 'mit'],\n gpl: [\n 'gpl-3.0-or-later', 'gpl-3.0-only', 'gpl-2.0-or-later', 'gpl-2.0-only',\n 'gpl-1.0-or-later', 'gpl-3.0+', 'gpl-2.0+', 'gpl-3.0', 'gpl-2.0',\n 'gplv3', 'gplv2', 'gpl3+', 'gpl3', 'gpl2', 'gpl-3', 'gpl-2', 'gpl',\n ],\n lgpl: [\n 'lgpl-3.0-or-later', 'lgpl-3.0-only', 'lgpl-2.1-or-later', 'lgpl-2.1-only',\n 'lgpl-2.0-or-later', 'lgpl-2.0-only', 'lgpl-3.0+', 'lgpl-2.1+', 'lgpl-3.0', 'lgpl-2.1',\n 'lgplv3', 'lgplv2.1', 'lgpl3', 'lgpl2.1', 'lgpl2', 'lgpl',\n ],\n agpl: ['agpl-3.0-or-later', 'agpl-3.0-only', 'agpl-3.0', 'agplv3', 'agpl3', 'agpl'],\n bsd: [\n 'bsd-3-clause', 'bsd-2-clause', '0bsd', 'bsd3', 'bsd 3-clause',\n 'bsd 2-clause', 'bsd-3', 'bsd',\n ],\n apache: ['apache-2.0', 'apache license 2.0', 'apache license, version 2.0', 'apache2', 'apache 2.0', 'apache'],\n mpl: ['mpl-2.0', 'mpl2', 'mpl'],\n artistic: ['artistic-2.0', 'artistic2.0', 'perlartistic', 'artistic'],\n isc: ['isc'],\n unlicense: ['unlicense'],\n zlib: ['zlib'],\n wtfpl: ['wtfpl'],\n php: ['php'],\n epl: ['epl'],\n cc: ['cc0-1.0', 'cc0', 'cc-by-sa-4.0', 'cc-by-nc-sa-4.0', 'cc-by-sa', 'ccpl'],\n 'public-domain': ['public domain'],\n boost: ['bsl-1.0', 'boost'],\n eupl: ['eupl-1.2', 'eupl'],\n postgresql: ['postgresql'],\n psf: ['psf-2.0', 'psf'],\n gfdl: ['gfdl-1.3', 'gfdl'],\n sspl: ['sspl-1.0', 'sspl'],\n busl: ['busl-1.1', 'busl'],\n};\n\n// Non-informative tokens — a real license claim was made, but it carries no comparable content\n// (\"custom\", a bare vendor name inside `custom:<...>`/`LicenseRef-<...>`, or an explicit \"we\n// don't know\"). Checked first, before the alias table, so e.g. \"custom:MIT\" reads as\n// UNKNOWN_FAMILY rather than incorrectly matching MIT's alias substring — a `custom:<...>`\n// convention names the *vendor's own license text*, not necessarily the SPDX license the name\n// resembles.\nconst UNKNOWN_PATTERNS = [/^custom\\b/, /^licenseref-/, /unknown/, /^none$/, /^various$/, /^unknow$/];\n\nfunction matchAlias(normalized: string): string | undefined {\n for (const [family, aliases] of Object.entries(FAMILY_ALIASES)) {\n if (aliases.includes(normalized)) return family;\n }\n return undefined;\n}\n\n// Splits an SPDX-ish compound expression (\"GPL-3.0+ AND LGPL-3.0+\", or a lowercase \"GPL-2.0+ and\n// GFDL-1.3\") into its individual license tokens. `WITH` exceptions (\"Apache-2.0 WITH\n// LLVM-exception\") are dropped entirely — the exception clause never itself resembles a distinct\n// license family, so it would otherwise either land in UNKNOWN_FAMILY for no reason or dilute the\n// comparison with a token that isn't really a separate license claim.\nfunction splitTokens(raw: string): string[] {\n return raw\n .split(/\\s+(?:and|or)\\s+/i)\n .map((token) => token.split(/\\s+with\\s+/i)[0].trim())\n .filter((token) => token.length > 0);\n}\n\n/**\n * Groups a raw, possibly compound license string into its coarse family set — e.g.\n * `\"GPL-3.0-or-later AND LGPL-3.0-or-later\"` yields `{\"gpl\", \"lgpl\"}`. Two sources reporting the\n * same real license under different notation (`\"GPL3\"` vs `\"GPL-3.0-or-later\"`, `\"GPL\"` vs\n * `\"GPL2\"`) yield the same family, so they read as agreeing rather than conflicting.\n *\n * Proprietary/custom/unknown tokens all collapse to a single shared unknown-family sentinel\n * rather than each becoming their own one-off family, so a batch of otherwise-identical\n * `\"custom:<vendor>\"` strings don't look like dozens of distinct disagreeing licenses. Use\n * {@link isKnown} to check whether any real (non-sentinel) family was found.\n * @param raw - A raw license string, single (`\"MIT\"`) or compound (`\"GPL-3.0+ AND LGPL-3.0+\"`)\n * @returns The set of families found; empty only when `raw` has no tokens at all (e.g. an empty string)\n * @example\n * families('GPL3')\n * // => Set(1) { 'gpl' }\n * @example\n * families('GPL-3.0-or-later AND LGPL-3.0-or-later')\n * // => Set(2) { 'gpl', 'lgpl' }\n * @example\n * families('custom:Acme End User License')\n * // => Set(1) { 'unknown' }\n * @since 3.1.2\n */\nexport function families(raw: string): Set<string> {\n const result = new Set<string>();\n for (const token of splitTokens(raw)) {\n const normalized = token.toLowerCase();\n if (UNKNOWN_PATTERNS.some((pattern) => pattern.test(normalized))) {\n result.add(UNKNOWN_FAMILY);\n continue;\n }\n result.add(matchAlias(normalized) ?? UNKNOWN_FAMILY);\n }\n return result;\n}\n","/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\nimport { UNKNOWN_FAMILY } from './_unknownFamily';\nimport { families } from './families';\n\n/**\n * Whether two raw license strings agree closely enough to count as \"no real conflict\" — true\n * when their family sets share at least one real (known) family, or when either side is entirely\n * unknown (e.g. `\"custom\"`/`\"unknown\"`) — a vague claim is never itself evidence of disagreement.\n * Only two *different*, both-known family sets (e.g. `bsd` vs `apache`) count as a real conflict.\n * @param a - The first raw license string\n * @param b - The second raw license string\n * @returns `true` when the two strings can be treated as the same license (or too vague to tell), `false` on a real family conflict\n * @example\n * agree('GPL3', 'GPL-3.0-or-later')\n * // => true\n * @example\n * agree('BSD-2-Clause', 'Apache-2.0')\n * // => false\n * @example\n * agree('custom', 'MIT')\n * // => true (a vague claim is never itself a conflict)\n * @since 3.1.2\n */\nexport function agree(a: string, b: string): boolean {\n const knownA = new Set([...families(a)].filter((f) => f !== UNKNOWN_FAMILY));\n const knownB = new Set([...families(b)].filter((f) => f !== UNKNOWN_FAMILY));\n\n if (knownA.size === 0 || knownB.size === 0) return true;\n for (const family of knownA) if (knownB.has(family)) return true;\n return false;\n}\n","/**\n * This file is part of helpers4.\n * Copyright (C) 2025 baxyz\n * SPDX-License-Identifier: LGPL-3.0-or-later\n */\n\nimport { UNKNOWN_FAMILY } from './_unknownFamily';\nimport { families } from './families';\n\n/**\n * Whether a raw license string resolves to at least one recognized family — `false` for an\n * empty string, a purely non-informative claim (`\"custom\"`, `\"unknown\"`, `\"LicenseRef-EULA\"`,\n * ...), or a compound expression where every token is non-informative.\n * @param raw - The raw license string to check\n * @returns `true` if at least one token in `raw` resolves to a real family\n * @example\n * isKnown('GPL-3.0-or-later')\n * // => true\n * @example\n * isKnown('custom:Acme End User License')\n * // => false\n * @since 3.1.2\n */\nexport function isKnown(raw: string): boolean {\n for (const family of families(raw)) {\n if (family !== UNKNOWN_FAMILY) return true;\n }\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,iBAAiB;;;;;;;;ACN9B,IAAM,iBAAoD;CACxD,KAAK,CAAC,SAAS,KAAK;CACpB,KAAK;EACH;EAAoB;EAAgB;EAAoB;EACxD;EAAoB;EAAY;EAAY;EAAW;EACvD;EAAS;EAAS;EAAS;EAAQ;EAAQ;EAAS;EAAS;CAC/D;CACA,MAAM;EACJ;EAAqB;EAAiB;EAAqB;EAC3D;EAAqB;EAAiB;EAAa;EAAa;EAAY;EAC5E;EAAU;EAAY;EAAS;EAAW;EAAS;CACrD;CACA,MAAM;EAAC;EAAqB;EAAiB;EAAY;EAAU;EAAS;CAAM;CAClF,KAAK;EACH;EAAgB;EAAgB;EAAQ;EAAQ;EAChD;EAAgB;EAAS;CAC3B;CACA,QAAQ;EAAC;EAAc;EAAsB;EAA+B;EAAW;EAAc;CAAQ;CAC7G,KAAK;EAAC;EAAW;EAAQ;CAAK;CAC9B,UAAU;EAAC;EAAgB;EAAe;EAAgB;CAAU;CACpE,KAAK,CAAC,KAAK;CACX,WAAW,CAAC,WAAW;CACvB,MAAM,CAAC,MAAM;CACb,OAAO,CAAC,OAAO;CACf,KAAK,CAAC,KAAK;CACX,KAAK,CAAC,KAAK;CACX,IAAI;EAAC;EAAW;EAAO;EAAgB;EAAmB;EAAY;CAAM;CAC5E,iBAAiB,CAAC,eAAe;CACjC,OAAO,CAAC,WAAW,OAAO;CAC1B,MAAM,CAAC,YAAY,MAAM;CACzB,YAAY,CAAC,YAAY;CACzB,KAAK,CAAC,WAAW,KAAK;CACtB,MAAM,CAAC,YAAY,MAAM;CACzB,MAAM,CAAC,YAAY,MAAM;CACzB,MAAM,CAAC,YAAY,MAAM;AAC3B;AAQA,IAAM,mBAAmB;CAAC;CAAa;CAAgB;CAAW;CAAU;CAAa;AAAU;AAEnG,SAAS,WAAW,YAAwC;CAC1D,KAAK,MAAM,CAAC,QAAQ,YAAY,OAAO,QAAQ,cAAc,GAC3D,IAAI,QAAQ,SAAS,UAAU,GAAG,OAAO;AAG7C;AAOA,SAAS,YAAY,KAAuB;CAC1C,OAAO,IACJ,MAAM,mBAAmB,CAAC,CAC1B,KAAK,UAAU,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CACpD,QAAQ,UAAU,MAAM,SAAS,CAAC;AACvC;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,SAAS,KAA0B;CACjD,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,SAAS,YAAY,GAAG,GAAG;EACpC,MAAM,aAAa,MAAM,YAAY;EACrC,IAAI,iBAAiB,MAAM,YAAY,QAAQ,KAAK,UAAU,CAAC,GAAG;GAChE,OAAO,IAAI,cAAc;GACzB;EACF;EACA,OAAO,IAAI,WAAW,UAAU,KAAA,SAAmB;CACrD;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;AChFA,SAAgB,MAAM,GAAW,GAAoB;CACnD,MAAM,SAAS,IAAI,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,cAAc,CAAC;CAC3E,MAAM,SAAS,IAAI,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,cAAc,CAAC;CAE3E,IAAI,OAAO,SAAS,KAAK,OAAO,SAAS,GAAG,OAAO;CACnD,KAAK,MAAM,UAAU,QAAQ,IAAI,OAAO,IAAI,MAAM,GAAG,OAAO;CAC5D,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;ACZA,SAAgB,QAAQ,KAAsB;CAC5C,KAAK,MAAM,UAAU,SAAS,GAAG,GAC/B,IAAI,WAAA,WAA2B,OAAO;CAExC,OAAO;AACT"}
package/llms.txt ADDED
@@ -0,0 +1,177 @@
1
+ # @helpers4/license
2
+
3
+ > Tree-shakable TypeScript utility functions for the `license` domain.
4
+ > Package: `@helpers4/license` — Version: 3.1.2
5
+ > License: LGPL-3.0-or-later
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install @helpers4/license
11
+ # or
12
+ pnpm add @helpers4/license
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { agree, families, isKnown } from '@helpers4/license';
19
+ ```
20
+
21
+ ## Functions
22
+
23
+ | Function | Description |
24
+ |---|---|
25
+ | `agree` | Whether two raw license strings agree closely enough to count as "no real conflict" — true when thei |
26
+ | `families` | Groups a raw, possibly compound license string into its coarse family set — e.g. `"GPL-3.0-or-later |
27
+ | `isKnown` | Whether a raw license string resolves to at least one recognized family — `false` for an empty strin |
28
+
29
+ ---
30
+
31
+ ## API Reference
32
+
33
+ ### `agree`
34
+
35
+ Whether two raw license strings agree closely enough to count as "no real conflict" — true
36
+ when their family sets share at least one real (known) family, or when either side is entirely
37
+ unknown (e.g. `"custom"`/`"unknown"`) — a vague claim is never itself evidence of disagreement.
38
+ Only two *different*, both-known family sets (e.g. `bsd` vs `apache`) count as a real conflict.
39
+
40
+ ```typescript
41
+ import { agree } from '@helpers4/license';
42
+
43
+ agree(a: string, b: string): boolean
44
+ ```
45
+
46
+ **Parameters:**
47
+
48
+ - `a: string` — The first raw license string
49
+ - `b: string` — The second raw license string
50
+
51
+ **Returns:** `boolean` — `true` when the two strings can be treated as the same license (or too vague to tell), `false` on a real family conflict
52
+
53
+ **Examples:**
54
+
55
+ *Reconcile notation drift across sources*
56
+
57
+ Two package sources reporting the same real license under different notation.
58
+
59
+ ```typescript
60
+ agree('GPL3', 'GPL-3.0-or-later')
61
+ // => true
62
+ ```
63
+
64
+ *Flag a real license conflict*
65
+
66
+ Two sources genuinely disagreeing on the license family — a real data-quality signal.
67
+
68
+ ```typescript
69
+ agree('BSD-2-Clause', 'Apache-2.0')
70
+ // => false
71
+ ```
72
+
73
+ *A vague claim is never itself a conflict*
74
+
75
+ When either side is non-informative ("custom", "unknown", ...), there is nothing real to disagree with.
76
+
77
+ ```typescript
78
+ agree('custom', 'MIT')
79
+ // => true
80
+ ```
81
+
82
+ ---
83
+
84
+ ### `families`
85
+
86
+ Groups a raw, possibly compound license string into its coarse family set — e.g.
87
+ `"GPL-3.0-or-later AND LGPL-3.0-or-later"` yields `{"gpl", "lgpl"}`. Two sources reporting the
88
+ same real license under different notation (`"GPL3"` vs `"GPL-3.0-or-later"`, `"GPL"` vs
89
+ `"GPL2"`) yield the same family, so they read as agreeing rather than conflicting.
90
+
91
+ Proprietary/custom/unknown tokens all collapse to a single shared unknown-family sentinel
92
+ rather than each becoming their own one-off family, so a batch of otherwise-identical
93
+ `"custom:<vendor>"` strings don't look like dozens of distinct disagreeing licenses. Use
94
+ isKnown to check whether any real (non-sentinel) family was found.
95
+
96
+ ```typescript
97
+ import { families } from '@helpers4/license';
98
+
99
+ families(raw: string): Set<string>
100
+ ```
101
+
102
+ **Parameters:**
103
+
104
+ - `raw: string` — A raw license string, single (`"MIT"`) or compound (`"GPL-3.0+ AND LGPL-3.0+"`)
105
+
106
+ **Returns:** `Set<string>` — The set of families found; empty only when `raw` has no tokens at all (e.g. an empty string)
107
+
108
+ **Examples:**
109
+
110
+ *Collapse notation drift into one family*
111
+
112
+ "GPL3" and "GPL-3.0-or-later" are the same real license, reported differently by two sources.
113
+
114
+ ```typescript
115
+ families('GPL3')
116
+ // => Set(1) { 'gpl' }
117
+ ```
118
+
119
+ *Split a compound SPDX expression*
120
+
121
+ "AND"/"OR" (any case) separate multiple license claims; "WITH" exception clauses are dropped.
122
+
123
+ ```typescript
124
+ families('GPL-3.0-or-later AND LGPL-3.0-or-later')
125
+ // => Set(2) { 'gpl', 'lgpl' }
126
+ ```
127
+
128
+ *Non-informative claims collapse to one shared "unknown" family*
129
+
130
+ A batch of different custom:<vendor> strings all read the same way — none of them is real evidence either way.
131
+
132
+ ```typescript
133
+ families('custom:Acme End User License')
134
+ // => Set(1) { 'unknown' }
135
+ ```
136
+
137
+ ---
138
+
139
+ ### `isKnown`
140
+
141
+ Whether a raw license string resolves to at least one recognized family — `false` for an
142
+ empty string, a purely non-informative claim (`"custom"`, `"unknown"`, `"LicenseRef-EULA"`,
143
+ ...), or a compound expression where every token is non-informative.
144
+
145
+ ```typescript
146
+ import { isKnown } from '@helpers4/license';
147
+
148
+ isKnown(raw: string): boolean
149
+ ```
150
+
151
+ **Parameters:**
152
+
153
+ - `raw: string` — The raw license string to check
154
+
155
+ **Returns:** `boolean` — `true` if at least one token in `raw` resolves to a real family
156
+
157
+ **Examples:**
158
+
159
+ *Filter out entries with no real license information*
160
+
161
+ Handy for flagging catalog entries whose license field is not actually usable.
162
+
163
+ ```typescript
164
+ ['MIT', 'custom:Acme EULA', 'unknown'].filter(isKnown)
165
+ // => ['MIT']
166
+ ```
167
+
168
+ *A vendor-specific EULA is not a recognized family*
169
+
170
+ "custom:<vendor>" names the vendor's own license text, not a known open-source family.
171
+
172
+ ```typescript
173
+ isKnown('custom:Acme End User License')
174
+ // => false
175
+ ```
176
+
177
+ ---
package/meta/api.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "category": "license",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "functions": [
5
5
  {
6
6
  "name": "agree",
7
7
  "kind": "function",
8
8
  "description": "Whether two raw license strings agree closely enough to count as \"no real conflict\" — true\nwhen their family sets share at least one real (known) family, or when either side is entirely\nunknown (e.g. `\"custom\"`/`\"unknown\"`) — a vague claim is never itself evidence of disagreement.\nOnly two *different*, both-known family sets (e.g. `bsd` vs `apache`) count as a real conflict.",
9
- "since": "next",
9
+ "since": "3.1.2",
10
10
  "signatures": [
11
11
  {
12
12
  "signature": "agree(a: string, b: string): boolean",
@@ -52,7 +52,7 @@
52
52
  "name": "families",
53
53
  "kind": "function",
54
54
  "description": "Groups a raw, possibly compound license string into its coarse family set — e.g.\n`\"GPL-3.0-or-later AND LGPL-3.0-or-later\"` yields `{\"gpl\", \"lgpl\"}`. Two sources reporting the\nsame real license under different notation (`\"GPL3\"` vs `\"GPL-3.0-or-later\"`, `\"GPL\"` vs\n`\"GPL2\"`) yield the same family, so they read as agreeing rather than conflicting.\n\nProprietary/custom/unknown tokens all collapse to a single shared unknown-family sentinel\nrather than each becoming their own one-off family, so a batch of otherwise-identical\n`\"custom:<vendor>\"` strings don't look like dozens of distinct disagreeing licenses. Use\nisKnown to check whether any real (non-sentinel) family was found.",
55
- "since": "next",
55
+ "since": "3.1.2",
56
56
  "signatures": [
57
57
  {
58
58
  "signature": "families(raw: string): Set<string>",
@@ -93,7 +93,7 @@
93
93
  "name": "isKnown",
94
94
  "kind": "function",
95
95
  "description": "Whether a raw license string resolves to at least one recognized family — `false` for an\nempty string, a purely non-informative claim (`\"custom\"`, `\"unknown\"`, `\"LicenseRef-EULA\"`,\n...), or a compound expression where every token is non-informative.",
96
- "since": "next",
96
+ "since": "3.1.2",
97
97
  "signatures": [
98
98
  {
99
99
  "signature": "isKnown(raw: string): boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helpers4/license",
3
- "version": "3.1.1-alpha.0",
3
+ "version": "3.1.2",
4
4
  "description": "A set of helpers in TS/JS, compatible with tree-shaking, for license.",
5
5
  "author": "baxyz <craft@baxyz.dev>",
6
6
  "license": "LGPL-3.0",
@@ -44,4 +44,4 @@
44
44
  "engines": {
45
45
  "node": ">=26.0.0"
46
46
  }
47
- }
47
+ }