@vibgrate/cli 2026.606.2 → 2026.609.1
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.
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
baselineCommand,
|
|
3
3
|
runBaseline
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-7CIAP6ZD.js";
|
|
5
|
+
import "./chunk-7J3LXQEA.js";
|
|
6
6
|
import "./chunk-74ZJFYEM.js";
|
|
7
7
|
import "./chunk-HAT4W7NZ.js";
|
|
8
8
|
import "./chunk-JSBRDJBE.js";
|
|
@@ -1995,6 +1995,44 @@ async function appendExcludePatterns(rootDir, newPatterns) {
|
|
|
1995
1995
|
return false;
|
|
1996
1996
|
}
|
|
1997
1997
|
}
|
|
1998
|
+
var MS_PER_DAY = 864e5;
|
|
1999
|
+
var DAYS_PER_YEAR = 365.25;
|
|
2000
|
+
function ageDaysBetween(resolvedVersion, latestStable, releaseDates) {
|
|
2001
|
+
if (!resolvedVersion || !latestStable || !releaseDates) return null;
|
|
2002
|
+
const resolvedIso = releaseDates[resolvedVersion];
|
|
2003
|
+
const latestIso = releaseDates[latestStable];
|
|
2004
|
+
if (!resolvedIso || !latestIso) return null;
|
|
2005
|
+
const resolvedMs = Date.parse(resolvedIso);
|
|
2006
|
+
const latestMs = Date.parse(latestIso);
|
|
2007
|
+
if (Number.isNaN(resolvedMs) || Number.isNaN(latestMs)) return null;
|
|
2008
|
+
const days = (latestMs - resolvedMs) / MS_PER_DAY;
|
|
2009
|
+
return days > 0 ? days : 0;
|
|
2010
|
+
}
|
|
2011
|
+
function daysToLibyears(days) {
|
|
2012
|
+
if (days === null) return null;
|
|
2013
|
+
return days / DAYS_PER_YEAR;
|
|
2014
|
+
}
|
|
2015
|
+
function aggregateLibyears(values) {
|
|
2016
|
+
let total = 0;
|
|
2017
|
+
let max = 0;
|
|
2018
|
+
let measured = 0;
|
|
2019
|
+
for (const v of values) {
|
|
2020
|
+
if (v === null || v === void 0 || Number.isNaN(v)) continue;
|
|
2021
|
+
total += v;
|
|
2022
|
+
if (v > max) max = v;
|
|
2023
|
+
measured++;
|
|
2024
|
+
}
|
|
2025
|
+
if (measured === 0) return null;
|
|
2026
|
+
return { total, max, measured };
|
|
2027
|
+
}
|
|
2028
|
+
function freshnessScoreFromLibyears(agg) {
|
|
2029
|
+
if (!agg || agg.measured === 0) return null;
|
|
2030
|
+
const avg = agg.total / agg.measured;
|
|
2031
|
+
const avgPenalty = Math.min(avg * 20, 100);
|
|
2032
|
+
const maxPenalty = Math.min(agg.max * 10, 100);
|
|
2033
|
+
const score = 100 - (avgPenalty * 0.7 + maxPenalty * 0.3);
|
|
2034
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
2035
|
+
}
|
|
1998
2036
|
var DEFAULT_THRESHOLDS = {
|
|
1999
2037
|
failOnError: {
|
|
2000
2038
|
eolDays: 180,
|
|
@@ -2077,28 +2115,57 @@ function eolScore(projects) {
|
|
|
2077
2115
|
}
|
|
2078
2116
|
return score;
|
|
2079
2117
|
}
|
|
2118
|
+
function freshnessScore(projects) {
|
|
2119
|
+
const aggs = projects.map((p) => p.libyears).filter((a) => !!a);
|
|
2120
|
+
if (aggs.length === 0) return null;
|
|
2121
|
+
const total = aggs.reduce((s, a) => s + a.total, 0);
|
|
2122
|
+
const measured = aggs.reduce((s, a) => s + a.measured, 0);
|
|
2123
|
+
const max = aggs.reduce((m, a) => Math.max(m, a.max), 0);
|
|
2124
|
+
if (measured === 0) return null;
|
|
2125
|
+
return freshnessScoreFromLibyears({ total, max, measured });
|
|
2126
|
+
}
|
|
2127
|
+
function coverageConfidence(projects) {
|
|
2128
|
+
let known = 0;
|
|
2129
|
+
let unknown = 0;
|
|
2130
|
+
for (const p of projects) {
|
|
2131
|
+
known += p.dependencyAgeBuckets.current + p.dependencyAgeBuckets.oneBehind + p.dependencyAgeBuckets.twoPlusBehind;
|
|
2132
|
+
unknown += p.dependencyAgeBuckets.unknown;
|
|
2133
|
+
}
|
|
2134
|
+
const total = known + unknown;
|
|
2135
|
+
if (total === 0) return null;
|
|
2136
|
+
return Math.round(known / total * 100) / 100;
|
|
2137
|
+
}
|
|
2080
2138
|
function computeDriftScore(projects) {
|
|
2081
2139
|
const rs = runtimeScore(projects);
|
|
2082
2140
|
const fs8 = frameworkScore(projects);
|
|
2083
2141
|
const ds = dependencyScore(projects);
|
|
2084
2142
|
const es = eolScore(projects);
|
|
2143
|
+
const frs = freshnessScore(projects);
|
|
2085
2144
|
const components = [
|
|
2086
2145
|
{ score: rs, weight: 0.25 },
|
|
2087
2146
|
{ score: fs8, weight: 0.25 },
|
|
2088
2147
|
{ score: ds, weight: 0.3 },
|
|
2089
|
-
{ score: es, weight: 0.2 }
|
|
2148
|
+
{ score: es, weight: 0.2 },
|
|
2149
|
+
{ score: frs, weight: 0.15 }
|
|
2090
2150
|
];
|
|
2151
|
+
const confidence = coverageConfidence(projects) ?? void 0;
|
|
2152
|
+
const buildComponents = () => {
|
|
2153
|
+
const c = {
|
|
2154
|
+
runtimeScore: Math.round(rs ?? 100),
|
|
2155
|
+
frameworkScore: Math.round(fs8 ?? 100),
|
|
2156
|
+
dependencyScore: Math.round(ds ?? 100),
|
|
2157
|
+
eolScore: Math.round(es ?? 100)
|
|
2158
|
+
};
|
|
2159
|
+
if (frs !== null) c.freshnessScore = Math.round(frs);
|
|
2160
|
+
return c;
|
|
2161
|
+
};
|
|
2091
2162
|
const active = components.filter((c) => c.score !== null);
|
|
2092
2163
|
if (active.length === 0) {
|
|
2093
2164
|
return {
|
|
2094
2165
|
score: 100,
|
|
2095
2166
|
riskLevel: "low",
|
|
2096
|
-
components:
|
|
2097
|
-
|
|
2098
|
-
frameworkScore: Math.round(fs8 ?? 100),
|
|
2099
|
-
dependencyScore: Math.round(ds ?? 100),
|
|
2100
|
-
eolScore: Math.round(es ?? 100)
|
|
2101
|
-
}
|
|
2167
|
+
components: buildComponents(),
|
|
2168
|
+
...confidence !== void 0 ? { confidence } : {}
|
|
2102
2169
|
};
|
|
2103
2170
|
}
|
|
2104
2171
|
const totalActiveWeight = active.reduce((sum, c) => sum + c.weight, 0);
|
|
@@ -2116,16 +2183,13 @@ function computeDriftScore(projects) {
|
|
|
2116
2183
|
if (fs8 !== null) measured.push("framework");
|
|
2117
2184
|
if (ds !== null) measured.push("dependency");
|
|
2118
2185
|
if (es !== null) measured.push("eol");
|
|
2186
|
+
if (frs !== null) measured.push("freshness");
|
|
2119
2187
|
return {
|
|
2120
2188
|
score,
|
|
2121
2189
|
riskLevel,
|
|
2122
|
-
components:
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
dependencyScore: Math.round(ds ?? 100),
|
|
2126
|
-
eolScore: Math.round(es ?? 100)
|
|
2127
|
-
},
|
|
2128
|
-
measured
|
|
2190
|
+
components: buildComponents(),
|
|
2191
|
+
measured,
|
|
2192
|
+
...confidence !== void 0 ? { confidence } : {}
|
|
2129
2193
|
};
|
|
2130
2194
|
}
|
|
2131
2195
|
function generateFindings(projects, config) {
|
|
@@ -3059,25 +3123,53 @@ function maxStable(versions) {
|
|
|
3059
3123
|
if (stable.length === 0) return null;
|
|
3060
3124
|
return stable.sort(semver.rcompare)[0] ?? null;
|
|
3061
3125
|
}
|
|
3126
|
+
function parseReleaseDates(time) {
|
|
3127
|
+
if (!time || typeof time !== "object") return void 0;
|
|
3128
|
+
const out = {};
|
|
3129
|
+
for (const [key, value] of Object.entries(time)) {
|
|
3130
|
+
if (key === "created" || key === "modified") continue;
|
|
3131
|
+
if (typeof value === "string") out[key] = value;
|
|
3132
|
+
}
|
|
3133
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
3134
|
+
}
|
|
3062
3135
|
function parseNpmMetaPayload(data) {
|
|
3063
3136
|
let latest = null;
|
|
3064
3137
|
let versions = [];
|
|
3138
|
+
let license = null;
|
|
3139
|
+
let releaseDates;
|
|
3065
3140
|
if (data && typeof data === "object") {
|
|
3066
|
-
const
|
|
3067
|
-
const dtLatest =
|
|
3141
|
+
const record2 = data;
|
|
3142
|
+
const dtLatest = record2["dist-tags.latest"];
|
|
3068
3143
|
if (typeof dtLatest === "string") latest = dtLatest;
|
|
3069
|
-
const distTags =
|
|
3144
|
+
const distTags = record2["dist-tags"];
|
|
3070
3145
|
if (!latest && distTags && typeof distTags === "object" && typeof distTags.latest === "string") {
|
|
3071
3146
|
latest = distTags.latest;
|
|
3072
3147
|
}
|
|
3073
|
-
const v =
|
|
3148
|
+
const v = record2.versions;
|
|
3074
3149
|
if (Array.isArray(v)) versions = v.map(String);
|
|
3075
3150
|
else if (typeof v === "string") versions = [v];
|
|
3151
|
+
license = parseLicenseField(record2.license ?? record2.licenses);
|
|
3152
|
+
releaseDates = parseReleaseDates(record2.time);
|
|
3076
3153
|
}
|
|
3077
3154
|
const stable = stableOnly(versions);
|
|
3078
3155
|
const latestStableOverall = maxStable(stable);
|
|
3079
3156
|
if (!latest && latestStableOverall) latest = latestStableOverall;
|
|
3080
|
-
return { latest, stableVersions: stable, latestStableOverall };
|
|
3157
|
+
return { latest, stableVersions: stable, latestStableOverall, license, ...releaseDates ? { releaseDates } : {} };
|
|
3158
|
+
}
|
|
3159
|
+
function parseLicenseField(value) {
|
|
3160
|
+
if (!value) return null;
|
|
3161
|
+
if (typeof value === "string") return value.trim() || null;
|
|
3162
|
+
if (Array.isArray(value)) {
|
|
3163
|
+
const parts = value.map(parseLicenseField).filter((s) => Boolean(s));
|
|
3164
|
+
if (parts.length === 0) return null;
|
|
3165
|
+
return parts.length === 1 ? parts[0] : `(${parts.join(" OR ")})`;
|
|
3166
|
+
}
|
|
3167
|
+
if (typeof value === "object") {
|
|
3168
|
+
const obj = value;
|
|
3169
|
+
if (typeof obj.type === "string") return obj.type.trim() || null;
|
|
3170
|
+
if (typeof obj.spdx === "string") return obj.spdx.trim() || null;
|
|
3171
|
+
}
|
|
3172
|
+
return null;
|
|
3081
3173
|
}
|
|
3082
3174
|
var BATCH_SIZE = 24;
|
|
3083
3175
|
var WINDOWS_MAX_COMMAND_CHARS = 7e3;
|
|
@@ -3144,7 +3236,7 @@ var NpmCache = class {
|
|
|
3144
3236
|
if (pending.length === 0) return;
|
|
3145
3237
|
const batchPromise = this.sem.run(() => this.fetchBatch(pending));
|
|
3146
3238
|
for (const pkg2 of pending) {
|
|
3147
|
-
const promise = batchPromise.then((batch) => batch.get(pkg2) ?? { latest: null, stableVersions: [], latestStableOverall: null });
|
|
3239
|
+
const promise = batchPromise.then((batch) => batch.get(pkg2) ?? { latest: null, stableVersions: [], latestStableOverall: null, license: null });
|
|
3148
3240
|
this.meta.set(pkg2, promise);
|
|
3149
3241
|
}
|
|
3150
3242
|
await batchPromise;
|
|
@@ -3167,10 +3259,12 @@ var NpmCache = class {
|
|
|
3167
3259
|
out.set(pkg2, {
|
|
3168
3260
|
latest: manifestEntry.latest ?? latestStableOverall,
|
|
3169
3261
|
stableVersions: stable,
|
|
3170
|
-
latestStableOverall
|
|
3262
|
+
latestStableOverall,
|
|
3263
|
+
license: manifestEntry.license ?? null,
|
|
3264
|
+
...manifestEntry.releaseDates ? { releaseDates: manifestEntry.releaseDates } : {}
|
|
3171
3265
|
});
|
|
3172
3266
|
} else if (this.offline) {
|
|
3173
|
-
out.set(pkg2, { latest: null, stableVersions: [], latestStableOverall: null });
|
|
3267
|
+
out.set(pkg2, { latest: null, stableVersions: [], latestStableOverall: null, license: null });
|
|
3174
3268
|
} else {
|
|
3175
3269
|
remote.push(pkg2);
|
|
3176
3270
|
}
|
|
@@ -3191,22 +3285,22 @@ var NpmCache = class {
|
|
|
3191
3285
|
return out;
|
|
3192
3286
|
}
|
|
3193
3287
|
try {
|
|
3194
|
-
const data = await npmViewJson([...pkgs, "dist-tags.latest", "versions"], this.cwd);
|
|
3288
|
+
const data = await npmViewJson([...pkgs, "dist-tags.latest", "versions", "license", "licenses", "time"], this.cwd);
|
|
3195
3289
|
if (Array.isArray(data)) {
|
|
3196
3290
|
for (let i = 0; i < data.length && i < pkgs.length; i++) {
|
|
3197
3291
|
out.set(pkgs[i], parseNpmMetaPayload(data[i]));
|
|
3198
3292
|
}
|
|
3199
3293
|
} else if (data && typeof data === "object") {
|
|
3200
|
-
const
|
|
3294
|
+
const record2 = data;
|
|
3201
3295
|
let keyedByPkg = 0;
|
|
3202
3296
|
for (const pkg2 of pkgs) {
|
|
3203
|
-
if (pkg2 in
|
|
3204
|
-
out.set(pkg2, parseNpmMetaPayload(
|
|
3297
|
+
if (pkg2 in record2) {
|
|
3298
|
+
out.set(pkg2, parseNpmMetaPayload(record2[pkg2]));
|
|
3205
3299
|
keyedByPkg++;
|
|
3206
3300
|
}
|
|
3207
3301
|
}
|
|
3208
3302
|
if (keyedByPkg === 0 && pkgs.length === 1) {
|
|
3209
|
-
out.set(pkgs[0], parseNpmMetaPayload(
|
|
3303
|
+
out.set(pkgs[0], parseNpmMetaPayload(record2));
|
|
3210
3304
|
}
|
|
3211
3305
|
}
|
|
3212
3306
|
} catch {
|
|
@@ -3226,17 +3320,19 @@ var NpmCache = class {
|
|
|
3226
3320
|
return {
|
|
3227
3321
|
latest: manifestEntry.latest ?? latestStableOverall,
|
|
3228
3322
|
stableVersions: stable,
|
|
3229
|
-
latestStableOverall
|
|
3323
|
+
latestStableOverall,
|
|
3324
|
+
license: manifestEntry.license ?? null,
|
|
3325
|
+
...manifestEntry.releaseDates ? { releaseDates: manifestEntry.releaseDates } : {}
|
|
3230
3326
|
};
|
|
3231
3327
|
}
|
|
3232
3328
|
if (this.offline) {
|
|
3233
|
-
return { latest: null, stableVersions: [], latestStableOverall: null };
|
|
3329
|
+
return { latest: null, stableVersions: [], latestStableOverall: null, license: null };
|
|
3234
3330
|
}
|
|
3235
3331
|
return this.fetchOneRemote(pkg2);
|
|
3236
3332
|
}
|
|
3237
3333
|
async fetchOneRemote(pkg2) {
|
|
3238
3334
|
try {
|
|
3239
|
-
const data = await npmViewJson([pkg2, "dist-tags.latest", "versions"], this.cwd);
|
|
3335
|
+
const data = await npmViewJson([pkg2, "dist-tags.latest", "versions", "license", "licenses", "time"], this.cwd);
|
|
3240
3336
|
return parseNpmMetaPayload(data);
|
|
3241
3337
|
} catch {
|
|
3242
3338
|
let latest = null;
|
|
@@ -3257,7 +3353,7 @@ var NpmCache = class {
|
|
|
3257
3353
|
const stable = stableOnly(versions);
|
|
3258
3354
|
const latestStableOverall = maxStable(stable);
|
|
3259
3355
|
if (!latest && latestStableOverall) latest = latestStableOverall;
|
|
3260
|
-
return { latest, stableVersions: stable, latestStableOverall };
|
|
3356
|
+
return { latest, stableVersions: stable, latestStableOverall, license: null };
|
|
3261
3357
|
}
|
|
3262
3358
|
}
|
|
3263
3359
|
};
|
|
@@ -3292,6 +3388,449 @@ function isSemverSpec(spec) {
|
|
|
3292
3388
|
if (s === "*" || s.toLowerCase() === "latest") return true;
|
|
3293
3389
|
return semver.validRange(s) !== null;
|
|
3294
3390
|
}
|
|
3391
|
+
function riskForCategory(category) {
|
|
3392
|
+
switch (category) {
|
|
3393
|
+
case "public-domain":
|
|
3394
|
+
case "permissive":
|
|
3395
|
+
return "low";
|
|
3396
|
+
case "weak-copyleft":
|
|
3397
|
+
return "medium";
|
|
3398
|
+
case "copyleft":
|
|
3399
|
+
case "network-copyleft":
|
|
3400
|
+
case "proprietary":
|
|
3401
|
+
return "high";
|
|
3402
|
+
case "unknown":
|
|
3403
|
+
default:
|
|
3404
|
+
return "medium";
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
var NO_OBLIGATIONS = {
|
|
3408
|
+
attribution: false,
|
|
3409
|
+
copyleft: false,
|
|
3410
|
+
networkCopyleft: false,
|
|
3411
|
+
discloseSource: false,
|
|
3412
|
+
patentGrant: false,
|
|
3413
|
+
commercialRestriction: false
|
|
3414
|
+
};
|
|
3415
|
+
function record(seed) {
|
|
3416
|
+
return {
|
|
3417
|
+
spdxId: seed.id,
|
|
3418
|
+
name: seed.name,
|
|
3419
|
+
family: seed.family,
|
|
3420
|
+
category: seed.category,
|
|
3421
|
+
osiApproved: seed.osi ?? false,
|
|
3422
|
+
fsfLibre: seed.fsf ?? false,
|
|
3423
|
+
deprecated: seed.deprecated ?? false,
|
|
3424
|
+
referenceUrl: seed.url ?? `https://spdx.org/licenses/${seed.id}.html`,
|
|
3425
|
+
riskLevel: riskForCategory(seed.category),
|
|
3426
|
+
obligations: { ...NO_OBLIGATIONS, ...seed.obligations ?? {} }
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
var ATTRIB = { attribution: true };
|
|
3430
|
+
var ATTRIB_PATENT = { attribution: true, patentGrant: true };
|
|
3431
|
+
var WEAK = { attribution: true, copyleft: true, discloseSource: true };
|
|
3432
|
+
var STRONG = { attribution: true, copyleft: true, discloseSource: true };
|
|
3433
|
+
var NETWORK = {
|
|
3434
|
+
attribution: true,
|
|
3435
|
+
copyleft: true,
|
|
3436
|
+
networkCopyleft: true,
|
|
3437
|
+
discloseSource: true
|
|
3438
|
+
};
|
|
3439
|
+
var SEEDS = [
|
|
3440
|
+
// ── Public domain / unlicensed ──
|
|
3441
|
+
{ id: "CC0-1.0", name: "Creative Commons Zero v1.0 Universal", family: "CC", category: "public-domain", fsf: true },
|
|
3442
|
+
{ id: "Unlicense", name: "The Unlicense", family: "Public Domain", category: "public-domain", osi: true, fsf: true },
|
|
3443
|
+
{ id: "0BSD", name: "BSD Zero Clause License", family: "BSD", category: "permissive", osi: true },
|
|
3444
|
+
{ id: "WTFPL", name: "Do What The F*ck You Want To Public License", family: "WTFPL", category: "permissive", fsf: true },
|
|
3445
|
+
// ── Permissive ──
|
|
3446
|
+
{ id: "MIT", name: "MIT License", family: "MIT", category: "permissive", osi: true, fsf: true, obligations: ATTRIB },
|
|
3447
|
+
{ id: "MIT-0", name: "MIT No Attribution", family: "MIT", category: "permissive", osi: true },
|
|
3448
|
+
{ id: "ISC", name: "ISC License", family: "ISC", category: "permissive", osi: true, fsf: true, obligations: ATTRIB },
|
|
3449
|
+
{ id: "BSD-2-Clause", name: 'BSD 2-Clause "Simplified" License', family: "BSD", category: "permissive", osi: true, fsf: true, obligations: ATTRIB },
|
|
3450
|
+
{ id: "BSD-3-Clause", name: 'BSD 3-Clause "New" or "Revised" License', family: "BSD", category: "permissive", osi: true, fsf: true, obligations: ATTRIB },
|
|
3451
|
+
{ id: "BSD-3-Clause-Clear", name: "BSD 3-Clause Clear License", family: "BSD", category: "permissive", osi: true, obligations: ATTRIB },
|
|
3452
|
+
{ id: "Apache-2.0", name: "Apache License 2.0", family: "Apache", category: "permissive", osi: true, fsf: true, obligations: ATTRIB_PATENT },
|
|
3453
|
+
{ id: "Apache-1.1", name: "Apache Software License 1.1", family: "Apache", category: "permissive", osi: true, obligations: ATTRIB },
|
|
3454
|
+
{ id: "Zlib", name: "zlib License", family: "Zlib", category: "permissive", osi: true, fsf: true },
|
|
3455
|
+
{ id: "BSL-1.0", name: "Boost Software License 1.0", family: "Boost", category: "permissive", osi: true, fsf: true, obligations: ATTRIB },
|
|
3456
|
+
{ id: "PostgreSQL", name: "PostgreSQL License", family: "BSD", category: "permissive", osi: true, obligations: ATTRIB },
|
|
3457
|
+
{ id: "Python-2.0", name: "Python License 2.0", family: "Python", category: "permissive", osi: true, obligations: ATTRIB },
|
|
3458
|
+
{ id: "PSF-2.0", name: "Python Software Foundation License 2.0", family: "Python", category: "permissive", obligations: ATTRIB },
|
|
3459
|
+
{ id: "Artistic-2.0", name: "Artistic License 2.0", family: "Artistic", category: "permissive", osi: true, fsf: true, obligations: ATTRIB },
|
|
3460
|
+
{ id: "NCSA", name: "University of Illinois/NCSA Open Source License", family: "NCSA", category: "permissive", osi: true, fsf: true, obligations: ATTRIB },
|
|
3461
|
+
{ id: "Unicode-DFS-2016", name: "Unicode License Agreement - Data Files and Software (2016)", family: "Unicode", category: "permissive", osi: true, obligations: ATTRIB },
|
|
3462
|
+
{ id: "BlueOak-1.0.0", name: "Blue Oak Model License 1.0.0", family: "BlueOak", category: "permissive", obligations: ATTRIB },
|
|
3463
|
+
// ── Weak / file-level copyleft ──
|
|
3464
|
+
{ id: "LGPL-2.0-only", name: "GNU Library General Public License v2 only", family: "LGPL", category: "weak-copyleft", osi: true, fsf: true, obligations: WEAK },
|
|
3465
|
+
{ id: "LGPL-2.1-only", name: "GNU Lesser General Public License v2.1 only", family: "LGPL", category: "weak-copyleft", osi: true, fsf: true, obligations: WEAK },
|
|
3466
|
+
{ id: "LGPL-2.1-or-later", name: "GNU Lesser General Public License v2.1 or later", family: "LGPL", category: "weak-copyleft", osi: true, fsf: true, obligations: WEAK },
|
|
3467
|
+
{ id: "LGPL-3.0-only", name: "GNU Lesser General Public License v3.0 only", family: "LGPL", category: "weak-copyleft", osi: true, fsf: true, obligations: { ...WEAK, patentGrant: true } },
|
|
3468
|
+
{ id: "LGPL-3.0-or-later", name: "GNU Lesser General Public License v3.0 or later", family: "LGPL", category: "weak-copyleft", osi: true, fsf: true, obligations: { ...WEAK, patentGrant: true } },
|
|
3469
|
+
{ id: "MPL-2.0", name: "Mozilla Public License 2.0", family: "MPL", category: "weak-copyleft", osi: true, fsf: true, obligations: { ...WEAK, patentGrant: true } },
|
|
3470
|
+
{ id: "MPL-1.1", name: "Mozilla Public License 1.1", family: "MPL", category: "weak-copyleft", osi: true, fsf: true, obligations: WEAK },
|
|
3471
|
+
{ id: "EPL-1.0", name: "Eclipse Public License 1.0", family: "EPL", category: "weak-copyleft", osi: true, fsf: true, obligations: { ...WEAK, patentGrant: true } },
|
|
3472
|
+
{ id: "EPL-2.0", name: "Eclipse Public License 2.0", family: "EPL", category: "weak-copyleft", osi: true, fsf: true, obligations: { ...WEAK, patentGrant: true } },
|
|
3473
|
+
{ id: "CDDL-1.0", name: "Common Development and Distribution License 1.0", family: "CDDL", category: "weak-copyleft", osi: true, fsf: true, obligations: WEAK },
|
|
3474
|
+
{ id: "CDDL-1.1", name: "Common Development and Distribution License 1.1", family: "CDDL", category: "weak-copyleft", obligations: WEAK },
|
|
3475
|
+
{ id: "CPL-1.0", name: "Common Public License 1.0", family: "CPL", category: "weak-copyleft", osi: true, fsf: true, obligations: WEAK },
|
|
3476
|
+
{ id: "MS-PL", name: "Microsoft Public License", family: "Microsoft", category: "weak-copyleft", osi: true, fsf: true, obligations: ATTRIB },
|
|
3477
|
+
{ id: "MS-RL", name: "Microsoft Reciprocal License", family: "Microsoft", category: "weak-copyleft", osi: true, fsf: true, obligations: WEAK },
|
|
3478
|
+
// ── Strong copyleft ──
|
|
3479
|
+
{ id: "GPL-2.0-only", name: "GNU General Public License v2.0 only", family: "GPL", category: "copyleft", osi: true, fsf: true, obligations: STRONG },
|
|
3480
|
+
{ id: "GPL-2.0-or-later", name: "GNU General Public License v2.0 or later", family: "GPL", category: "copyleft", osi: true, fsf: true, obligations: STRONG },
|
|
3481
|
+
{ id: "GPL-3.0-only", name: "GNU General Public License v3.0 only", family: "GPL", category: "copyleft", osi: true, fsf: true, obligations: { ...STRONG, patentGrant: true } },
|
|
3482
|
+
{ id: "GPL-3.0-or-later", name: "GNU General Public License v3.0 or later", family: "GPL", category: "copyleft", osi: true, fsf: true, obligations: { ...STRONG, patentGrant: true } },
|
|
3483
|
+
{ id: "EUPL-1.1", name: "European Union Public License 1.1", family: "EUPL", category: "copyleft", osi: true, fsf: true, obligations: STRONG },
|
|
3484
|
+
{ id: "EUPL-1.2", name: "European Union Public License 1.2", family: "EUPL", category: "copyleft", osi: true, fsf: true, obligations: STRONG },
|
|
3485
|
+
{ id: "OSL-3.0", name: "Open Software License 3.0", family: "OSL", category: "copyleft", osi: true, fsf: true, obligations: { ...STRONG, patentGrant: true } },
|
|
3486
|
+
{ id: "CECILL-2.1", name: "CeCILL Free Software License Agreement v2.1", family: "CeCILL", category: "copyleft", osi: true, fsf: true, obligations: STRONG },
|
|
3487
|
+
{ id: "CC-BY-SA-4.0", name: "Creative Commons Attribution Share Alike 4.0 International", family: "CC", category: "copyleft", obligations: { attribution: true, copyleft: true } },
|
|
3488
|
+
// ── Network copyleft ──
|
|
3489
|
+
{ id: "AGPL-3.0-only", name: "GNU Affero General Public License v3.0 only", family: "AGPL", category: "network-copyleft", osi: true, fsf: true, obligations: { ...NETWORK, patentGrant: true } },
|
|
3490
|
+
{ id: "AGPL-3.0-or-later", name: "GNU Affero General Public License v3.0 or later", family: "AGPL", category: "network-copyleft", osi: true, fsf: true, obligations: { ...NETWORK, patentGrant: true } },
|
|
3491
|
+
// ── Source-available / proprietary (non-OSI) ──
|
|
3492
|
+
{ id: "SSPL-1.0", name: "Server Side Public License 1.0", family: "SSPL", category: "network-copyleft", obligations: NETWORK },
|
|
3493
|
+
{ id: "BUSL-1.1", name: "Business Source License 1.1", family: "BUSL", category: "proprietary", obligations: { commercialRestriction: true, discloseSource: true } },
|
|
3494
|
+
{ id: "Elastic-2.0", name: "Elastic License 2.0", family: "Elastic", category: "proprietary", obligations: { commercialRestriction: true } },
|
|
3495
|
+
{ id: "RPL-1.5", name: "Reciprocal Public License 1.5", family: "RPL", category: "network-copyleft", osi: true, obligations: NETWORK },
|
|
3496
|
+
{ id: "Commons-Clause", name: "Commons Clause License Condition v1.0", family: "Commons-Clause", category: "proprietary", obligations: { commercialRestriction: true } },
|
|
3497
|
+
{ id: "LicenseRef-Proprietary", name: "Proprietary / Commercial", family: "Proprietary", category: "proprietary", obligations: { commercialRestriction: true } },
|
|
3498
|
+
// ── Non-commercial Creative Commons (restrictive) ──
|
|
3499
|
+
{ id: "CC-BY-NC-4.0", name: "Creative Commons Attribution Non Commercial 4.0 International", family: "CC", category: "proprietary", obligations: { attribution: true, commercialRestriction: true } },
|
|
3500
|
+
{ id: "CC-BY-4.0", name: "Creative Commons Attribution 4.0 International", family: "CC", category: "permissive", obligations: ATTRIB }
|
|
3501
|
+
];
|
|
3502
|
+
var RECORDS = SEEDS.map(record);
|
|
3503
|
+
var BY_ID = /* @__PURE__ */ new Map();
|
|
3504
|
+
for (const rec of RECORDS) {
|
|
3505
|
+
BY_ID.set(rec.spdxId.toLowerCase(), rec);
|
|
3506
|
+
}
|
|
3507
|
+
function getLicenseRecord(spdxId) {
|
|
3508
|
+
return BY_ID.get(spdxId.trim().toLowerCase());
|
|
3509
|
+
}
|
|
3510
|
+
function unknownLicenseRecord(name = "Unknown") {
|
|
3511
|
+
return {
|
|
3512
|
+
spdxId: "NOASSERTION",
|
|
3513
|
+
name,
|
|
3514
|
+
family: "Unknown",
|
|
3515
|
+
category: "unknown",
|
|
3516
|
+
osiApproved: false,
|
|
3517
|
+
fsfLibre: false,
|
|
3518
|
+
deprecated: false,
|
|
3519
|
+
referenceUrl: "",
|
|
3520
|
+
riskLevel: "medium",
|
|
3521
|
+
obligations: { ...NO_OBLIGATIONS }
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
function normalizeAliasKey(raw) {
|
|
3525
|
+
return raw.trim().toLowerCase().replace(/^\(+|\)+$/g, "").replace(/[._]/g, "-").replace(/\s+/g, " ").replace(/\s*-\s*/g, "-").replace(/\bversion\b/g, "v").replace(/\blicen[sc]e\b/g, "").replace(/\s+/g, " ").trim();
|
|
3526
|
+
}
|
|
3527
|
+
var RAW_ALIASES = {
|
|
3528
|
+
// MIT
|
|
3529
|
+
"mit": "MIT",
|
|
3530
|
+
"mit license": "MIT",
|
|
3531
|
+
"the mit license": "MIT",
|
|
3532
|
+
"expat": "MIT",
|
|
3533
|
+
// ISC
|
|
3534
|
+
"isc": "ISC",
|
|
3535
|
+
// Apache
|
|
3536
|
+
"apache": "Apache-2.0",
|
|
3537
|
+
"apache 2": "Apache-2.0",
|
|
3538
|
+
"apache2": "Apache-2.0",
|
|
3539
|
+
"apache 2.0": "Apache-2.0",
|
|
3540
|
+
"apache-2": "Apache-2.0",
|
|
3541
|
+
"apache license 2.0": "Apache-2.0",
|
|
3542
|
+
"apache license version 2.0": "Apache-2.0",
|
|
3543
|
+
"asl 2.0": "Apache-2.0",
|
|
3544
|
+
"al2": "Apache-2.0",
|
|
3545
|
+
// BSD
|
|
3546
|
+
"bsd": "BSD-3-Clause",
|
|
3547
|
+
"bsd license": "BSD-3-Clause",
|
|
3548
|
+
"bsd-2": "BSD-2-Clause",
|
|
3549
|
+
"bsd 2-clause": "BSD-2-Clause",
|
|
3550
|
+
"simplified bsd": "BSD-2-Clause",
|
|
3551
|
+
"freebsd": "BSD-2-Clause",
|
|
3552
|
+
"bsd-3": "BSD-3-Clause",
|
|
3553
|
+
"bsd 3-clause": "BSD-3-Clause",
|
|
3554
|
+
"new bsd": "BSD-3-Clause",
|
|
3555
|
+
"modified bsd": "BSD-3-Clause",
|
|
3556
|
+
"revised bsd": "BSD-3-Clause",
|
|
3557
|
+
// GPL
|
|
3558
|
+
"gpl": "GPL-3.0-or-later",
|
|
3559
|
+
"gplv2": "GPL-2.0-only",
|
|
3560
|
+
"gpl2": "GPL-2.0-only",
|
|
3561
|
+
"gpl-2": "GPL-2.0-only",
|
|
3562
|
+
"gpl 2.0": "GPL-2.0-only",
|
|
3563
|
+
"gpl-2.0": "GPL-2.0-only",
|
|
3564
|
+
"gpl-2.0+": "GPL-2.0-or-later",
|
|
3565
|
+
"gnu gpl v2": "GPL-2.0-only",
|
|
3566
|
+
"gplv3": "GPL-3.0-only",
|
|
3567
|
+
"gpl3": "GPL-3.0-only",
|
|
3568
|
+
"gpl-3": "GPL-3.0-only",
|
|
3569
|
+
"gpl 3.0": "GPL-3.0-only",
|
|
3570
|
+
"gpl-3.0": "GPL-3.0-only",
|
|
3571
|
+
"gpl-3.0+": "GPL-3.0-or-later",
|
|
3572
|
+
"gnu gpl v3": "GPL-3.0-only",
|
|
3573
|
+
"gnu general public v3": "GPL-3.0-only",
|
|
3574
|
+
// LGPL
|
|
3575
|
+
"lgpl": "LGPL-3.0-or-later",
|
|
3576
|
+
"lgplv2.1": "LGPL-2.1-only",
|
|
3577
|
+
"lgpl-2.1": "LGPL-2.1-only",
|
|
3578
|
+
"lgpl 2.1": "LGPL-2.1-only",
|
|
3579
|
+
"lgplv3": "LGPL-3.0-only",
|
|
3580
|
+
"lgpl-3": "LGPL-3.0-only",
|
|
3581
|
+
"lgpl-3.0": "LGPL-3.0-only",
|
|
3582
|
+
"lgpl 3.0": "LGPL-3.0-only",
|
|
3583
|
+
// AGPL
|
|
3584
|
+
"agpl": "AGPL-3.0-or-later",
|
|
3585
|
+
"agplv3": "AGPL-3.0-only",
|
|
3586
|
+
"agpl-3": "AGPL-3.0-only",
|
|
3587
|
+
"agpl-3.0": "AGPL-3.0-only",
|
|
3588
|
+
"agpl 3.0": "AGPL-3.0-only",
|
|
3589
|
+
"gnu agpl v3": "AGPL-3.0-only",
|
|
3590
|
+
// MPL
|
|
3591
|
+
"mpl": "MPL-2.0",
|
|
3592
|
+
"mpl 2.0": "MPL-2.0",
|
|
3593
|
+
"mpl-2": "MPL-2.0",
|
|
3594
|
+
"mozilla public 2.0": "MPL-2.0",
|
|
3595
|
+
// EPL
|
|
3596
|
+
"epl": "EPL-2.0",
|
|
3597
|
+
"eclipse public 2.0": "EPL-2.0",
|
|
3598
|
+
"epl-2": "EPL-2.0",
|
|
3599
|
+
// Source-available / proprietary
|
|
3600
|
+
"sspl": "SSPL-1.0",
|
|
3601
|
+
"server side public": "SSPL-1.0",
|
|
3602
|
+
"busl": "BUSL-1.1",
|
|
3603
|
+
"business source": "BUSL-1.1",
|
|
3604
|
+
"elastic": "Elastic-2.0",
|
|
3605
|
+
"elastic-2": "Elastic-2.0",
|
|
3606
|
+
"commons clause": "Commons-Clause",
|
|
3607
|
+
"proprietary": "LicenseRef-Proprietary",
|
|
3608
|
+
"commercial": "LicenseRef-Proprietary",
|
|
3609
|
+
"see license in license": "LicenseRef-Proprietary",
|
|
3610
|
+
"unlicensed": "LicenseRef-Proprietary",
|
|
3611
|
+
"all rights reserved": "LicenseRef-Proprietary",
|
|
3612
|
+
// Public domain
|
|
3613
|
+
"cc0": "CC0-1.0",
|
|
3614
|
+
"cc0 1.0": "CC0-1.0",
|
|
3615
|
+
"public domain": "CC0-1.0",
|
|
3616
|
+
"the unlicense": "Unlicense",
|
|
3617
|
+
"wtfpl": "WTFPL",
|
|
3618
|
+
// Boost / zlib / others
|
|
3619
|
+
"boost": "BSL-1.0",
|
|
3620
|
+
"bsl": "BSL-1.0",
|
|
3621
|
+
"zlib/libpng": "Zlib",
|
|
3622
|
+
"python": "Python-2.0",
|
|
3623
|
+
"psf": "PSF-2.0",
|
|
3624
|
+
"artistic": "Artistic-2.0"
|
|
3625
|
+
};
|
|
3626
|
+
var ALIASES = /* @__PURE__ */ new Map();
|
|
3627
|
+
for (const [raw, spdx] of Object.entries(RAW_ALIASES)) {
|
|
3628
|
+
ALIASES.set(normalizeAliasKey(raw), spdx);
|
|
3629
|
+
}
|
|
3630
|
+
function resolveAlias(raw) {
|
|
3631
|
+
return ALIASES.get(normalizeAliasKey(raw));
|
|
3632
|
+
}
|
|
3633
|
+
var TOKEN_RE = /\(|\)|\bWITH\b|\bAND\b|\bOR\b|[^\s()]+/gi;
|
|
3634
|
+
function parseLicenseExpression(raw) {
|
|
3635
|
+
const input = (raw ?? "").trim();
|
|
3636
|
+
const licenseIds = [];
|
|
3637
|
+
const exceptions = [];
|
|
3638
|
+
let hasOr = false;
|
|
3639
|
+
let hasAnd = false;
|
|
3640
|
+
let orLater = false;
|
|
3641
|
+
let expectException = false;
|
|
3642
|
+
const tokens = input.match(TOKEN_RE) ?? [];
|
|
3643
|
+
for (const token of tokens) {
|
|
3644
|
+
const upper = token.toUpperCase();
|
|
3645
|
+
if (token === "(" || token === ")") continue;
|
|
3646
|
+
if (upper === "OR") {
|
|
3647
|
+
hasOr = true;
|
|
3648
|
+
expectException = false;
|
|
3649
|
+
continue;
|
|
3650
|
+
}
|
|
3651
|
+
if (upper === "AND") {
|
|
3652
|
+
hasAnd = true;
|
|
3653
|
+
expectException = false;
|
|
3654
|
+
continue;
|
|
3655
|
+
}
|
|
3656
|
+
if (upper === "WITH") {
|
|
3657
|
+
expectException = true;
|
|
3658
|
+
continue;
|
|
3659
|
+
}
|
|
3660
|
+
if (expectException) {
|
|
3661
|
+
if (!exceptions.includes(token)) exceptions.push(token);
|
|
3662
|
+
expectException = false;
|
|
3663
|
+
continue;
|
|
3664
|
+
}
|
|
3665
|
+
let id = token;
|
|
3666
|
+
if (id.endsWith("+")) {
|
|
3667
|
+
orLater = true;
|
|
3668
|
+
id = id.slice(0, -1);
|
|
3669
|
+
}
|
|
3670
|
+
if (id && !licenseIds.includes(id)) licenseIds.push(id);
|
|
3671
|
+
}
|
|
3672
|
+
const operator = hasOr ? "OR" : hasAnd ? "AND" : "SINGLE";
|
|
3673
|
+
return {
|
|
3674
|
+
licenseIds,
|
|
3675
|
+
exceptions,
|
|
3676
|
+
operator,
|
|
3677
|
+
orLater,
|
|
3678
|
+
normalized: serialise(licenseIds, exceptions, operator, orLater, input)
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
function serialise(ids, exceptions, operator, orLater, original) {
|
|
3682
|
+
if (ids.length === 0) return original.trim();
|
|
3683
|
+
if (ids.length === 1) {
|
|
3684
|
+
let out = ids[0];
|
|
3685
|
+
if (orLater && !out.endsWith("+")) out += "+";
|
|
3686
|
+
if (exceptions.length > 0) out += ` WITH ${exceptions.join(" WITH ")}`;
|
|
3687
|
+
return out;
|
|
3688
|
+
}
|
|
3689
|
+
const joiner = operator === "AND" ? " AND " : " OR ";
|
|
3690
|
+
return ids.join(joiner);
|
|
3691
|
+
}
|
|
3692
|
+
function isCompoundExpression(raw) {
|
|
3693
|
+
return /\b(?:AND|OR|WITH)\b|\+\s*$|\(/i.test(raw);
|
|
3694
|
+
}
|
|
3695
|
+
var CATEGORY_RESTRICTIVENESS = {
|
|
3696
|
+
"public-domain": 0,
|
|
3697
|
+
permissive: 1,
|
|
3698
|
+
"weak-copyleft": 2,
|
|
3699
|
+
copyleft: 3,
|
|
3700
|
+
"network-copyleft": 4,
|
|
3701
|
+
proprietary: 5,
|
|
3702
|
+
unknown: 2
|
|
3703
|
+
};
|
|
3704
|
+
function verdictFromRecord(rec, matchStatus, confidence, expression, components) {
|
|
3705
|
+
return {
|
|
3706
|
+
spdxId: rec.spdxId,
|
|
3707
|
+
name: rec.name,
|
|
3708
|
+
expression: expression ?? rec.spdxId,
|
|
3709
|
+
family: rec.family,
|
|
3710
|
+
category: rec.category,
|
|
3711
|
+
riskLevel: rec.riskLevel,
|
|
3712
|
+
osiApproved: rec.osiApproved,
|
|
3713
|
+
fsfLibre: rec.fsfLibre,
|
|
3714
|
+
obligations: rec.obligations,
|
|
3715
|
+
matchStatus,
|
|
3716
|
+
confidence,
|
|
3717
|
+
components: components ?? [rec.spdxId]
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3720
|
+
function fuzzyMatch(raw) {
|
|
3721
|
+
const s = raw.toLowerCase();
|
|
3722
|
+
if (/\bagpl/.test(s)) return getLicenseRecord("AGPL-3.0-or-later");
|
|
3723
|
+
if (/\bsspl/.test(s)) return getLicenseRecord("SSPL-1.0");
|
|
3724
|
+
if (/\bbusl|business source/.test(s)) return getLicenseRecord("BUSL-1.1");
|
|
3725
|
+
if (/\blgpl/.test(s)) return getLicenseRecord("LGPL-3.0-or-later");
|
|
3726
|
+
if (/\bgpl/.test(s)) return getLicenseRecord("GPL-3.0-or-later");
|
|
3727
|
+
if (/\bmpl|mozilla/.test(s)) return getLicenseRecord("MPL-2.0");
|
|
3728
|
+
if (/\bepl|eclipse/.test(s)) return getLicenseRecord("EPL-2.0");
|
|
3729
|
+
if (/\bapache/.test(s)) return getLicenseRecord("Apache-2.0");
|
|
3730
|
+
if (/\bbsd/.test(s)) return getLicenseRecord("BSD-3-Clause");
|
|
3731
|
+
if (/\bmit\b/.test(s)) return getLicenseRecord("MIT");
|
|
3732
|
+
if (/\bisc\b/.test(s)) return getLicenseRecord("ISC");
|
|
3733
|
+
if (/commons clause/.test(s)) return getLicenseRecord("Commons-Clause");
|
|
3734
|
+
if (/proprietary|commercial|all rights reserved/.test(s)) return getLicenseRecord("LicenseRef-Proprietary");
|
|
3735
|
+
if (/public domain|cc0/.test(s)) return getLicenseRecord("CC0-1.0");
|
|
3736
|
+
return void 0;
|
|
3737
|
+
}
|
|
3738
|
+
function normalizeLicense(raw) {
|
|
3739
|
+
const input = (raw ?? "").trim();
|
|
3740
|
+
if (!input || /^(unknown|noassertion|none|n\/a)$/i.test(input)) {
|
|
3741
|
+
return verdictFromRecord(unknownLicenseRecord(), "unknown", 0);
|
|
3742
|
+
}
|
|
3743
|
+
const exact = getLicenseRecord(input);
|
|
3744
|
+
if (exact) return verdictFromRecord(exact, "exact", 1);
|
|
3745
|
+
if (isCompoundExpression(input)) {
|
|
3746
|
+
return resolveExpression(input);
|
|
3747
|
+
}
|
|
3748
|
+
const aliasId = resolveAlias(input);
|
|
3749
|
+
if (aliasId) {
|
|
3750
|
+
const rec = getLicenseRecord(aliasId);
|
|
3751
|
+
if (rec) return verdictFromRecord(rec, "alias", 0.95);
|
|
3752
|
+
}
|
|
3753
|
+
const fuzzy = fuzzyMatch(input);
|
|
3754
|
+
if (fuzzy) {
|
|
3755
|
+
return { ...verdictFromRecord(fuzzy, "fuzzy", 0.6), name: input.slice(0, 120) };
|
|
3756
|
+
}
|
|
3757
|
+
return verdictFromRecord(unknownLicenseRecord(input.slice(0, 120)), "unknown", 0);
|
|
3758
|
+
}
|
|
3759
|
+
function resolveExpression(input) {
|
|
3760
|
+
const parsed = parseLicenseExpression(input);
|
|
3761
|
+
if (parsed.licenseIds.length === 0) {
|
|
3762
|
+
return verdictFromRecord(unknownLicenseRecord(input.slice(0, 120)), "unknown", 0);
|
|
3763
|
+
}
|
|
3764
|
+
const componentVerdicts = parsed.licenseIds.map((id) => {
|
|
3765
|
+
const exact = getLicenseRecord(id);
|
|
3766
|
+
if (exact) return verdictFromRecord(exact, "exact", 1);
|
|
3767
|
+
const aliasId = resolveAlias(id);
|
|
3768
|
+
if (aliasId) {
|
|
3769
|
+
const rec = getLicenseRecord(aliasId);
|
|
3770
|
+
if (rec) return verdictFromRecord(rec, "alias", 0.95);
|
|
3771
|
+
}
|
|
3772
|
+
const fuzzy = fuzzyMatch(id);
|
|
3773
|
+
if (fuzzy) return verdictFromRecord(fuzzy, "fuzzy", 0.6);
|
|
3774
|
+
return verdictFromRecord(unknownLicenseRecord(id), "unknown", 0);
|
|
3775
|
+
});
|
|
3776
|
+
const pickMostRestrictive = parsed.operator !== "OR";
|
|
3777
|
+
let chosen = componentVerdicts[0];
|
|
3778
|
+
for (const v of componentVerdicts) {
|
|
3779
|
+
const more = CATEGORY_RESTRICTIVENESS[v.category] > CATEGORY_RESTRICTIVENESS[chosen.category];
|
|
3780
|
+
if (pickMostRestrictive ? more : !more) chosen = v;
|
|
3781
|
+
}
|
|
3782
|
+
const obligations = unionObligations(
|
|
3783
|
+
pickMostRestrictive ? componentVerdicts : [chosen]
|
|
3784
|
+
);
|
|
3785
|
+
const components = componentVerdicts.map((v) => v.spdxId);
|
|
3786
|
+
const category = chosen.category;
|
|
3787
|
+
return {
|
|
3788
|
+
spdxId: chosen.spdxId,
|
|
3789
|
+
name: parsed.normalized,
|
|
3790
|
+
expression: parsed.normalized,
|
|
3791
|
+
family: chosen.family,
|
|
3792
|
+
category,
|
|
3793
|
+
riskLevel: riskForCategory(category),
|
|
3794
|
+
osiApproved: componentVerdicts.every((v) => v.osiApproved),
|
|
3795
|
+
fsfLibre: componentVerdicts.every((v) => v.fsfLibre),
|
|
3796
|
+
obligations,
|
|
3797
|
+
matchStatus: "expression",
|
|
3798
|
+
confidence: Math.min(...componentVerdicts.map((v) => v.confidence)) || 0.5,
|
|
3799
|
+
components
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
function unionObligations(verdicts) {
|
|
3803
|
+
const out = {
|
|
3804
|
+
attribution: false,
|
|
3805
|
+
copyleft: false,
|
|
3806
|
+
networkCopyleft: false,
|
|
3807
|
+
discloseSource: false,
|
|
3808
|
+
patentGrant: false,
|
|
3809
|
+
commercialRestriction: false
|
|
3810
|
+
};
|
|
3811
|
+
for (const v of verdicts) {
|
|
3812
|
+
out.attribution ||= v.obligations.attribution;
|
|
3813
|
+
out.copyleft ||= v.obligations.copyleft;
|
|
3814
|
+
out.networkCopyleft ||= v.obligations.networkCopyleft;
|
|
3815
|
+
out.discloseSource ||= v.obligations.discloseSource;
|
|
3816
|
+
out.patentGrant ||= v.obligations.patentGrant;
|
|
3817
|
+
out.commercialRestriction ||= v.obligations.commercialRestriction;
|
|
3818
|
+
}
|
|
3819
|
+
return out;
|
|
3820
|
+
}
|
|
3821
|
+
function buildDependencyLicense(raw, source) {
|
|
3822
|
+
const trimmed = (raw ?? "").trim();
|
|
3823
|
+
if (!trimmed) {
|
|
3824
|
+
return { raw: null, spdxId: null, source: "none", confidence: 0 };
|
|
3825
|
+
}
|
|
3826
|
+
const verdict = normalizeLicense(trimmed);
|
|
3827
|
+
return {
|
|
3828
|
+
raw: trimmed.slice(0, 200),
|
|
3829
|
+
spdxId: verdict.matchStatus === "unknown" ? null : verdict.spdxId,
|
|
3830
|
+
source,
|
|
3831
|
+
confidence: verdict.confidence
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3295
3834
|
var KNOWN_FRAMEWORKS = {
|
|
3296
3835
|
// ── Frontend ──
|
|
3297
3836
|
"react": "React",
|
|
@@ -3544,6 +4083,8 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
|
|
|
3544
4083
|
} else {
|
|
3545
4084
|
buckets.unknown++;
|
|
3546
4085
|
}
|
|
4086
|
+
const ageDays = ageDaysBetween(resolvedVersion, latestStable, meta.releaseDates);
|
|
4087
|
+
const libyears = daysToLibyears(ageDays);
|
|
3547
4088
|
dependencies.push({
|
|
3548
4089
|
package: pkg2,
|
|
3549
4090
|
section,
|
|
@@ -3551,7 +4092,10 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
|
|
|
3551
4092
|
resolvedVersion,
|
|
3552
4093
|
latestStable,
|
|
3553
4094
|
majorsBehind,
|
|
3554
|
-
drift
|
|
4095
|
+
drift,
|
|
4096
|
+
license: buildDependencyLicense(meta.license, "registry"),
|
|
4097
|
+
ageDays,
|
|
4098
|
+
libyears
|
|
3555
4099
|
});
|
|
3556
4100
|
if (pkg2 in KNOWN_FRAMEWORKS) {
|
|
3557
4101
|
frameworks.push({
|
|
@@ -3573,7 +4117,8 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
|
|
|
3573
4117
|
resolvedVersion: null,
|
|
3574
4118
|
latestStable: null,
|
|
3575
4119
|
majorsBehind: null,
|
|
3576
|
-
drift: "unknown"
|
|
4120
|
+
drift: "unknown",
|
|
4121
|
+
license: { raw: null, spdxId: null, source: "none", confidence: 0 }
|
|
3577
4122
|
});
|
|
3578
4123
|
buckets.unknown++;
|
|
3579
4124
|
}
|
|
@@ -3599,6 +4144,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
|
|
|
3599
4144
|
frameworks,
|
|
3600
4145
|
dependencies,
|
|
3601
4146
|
dependencyAgeBuckets: buckets,
|
|
4147
|
+
libyears: aggregateLibyears(dependencies.map((d) => d.libyears)) ?? void 0,
|
|
3602
4148
|
fileCount
|
|
3603
4149
|
};
|
|
3604
4150
|
}
|
|
@@ -11139,6 +11685,19 @@ function detectDbBrand(raw) {
|
|
|
11139
11685
|
if (value.includes("cosmosdb") || value.includes("@azure/cosmos") || value.includes("cosmos")) return { kind: "nosql", brand: "CosmosDB", version: null, evidence: raw };
|
|
11140
11686
|
if (value.includes("dynamodb")) return { kind: "nosql", brand: "DynamoDB", version: null, evidence: raw };
|
|
11141
11687
|
if (value.includes("neo4j")) return { kind: "nosql", brand: "Neo4j", version: null, evidence: raw };
|
|
11688
|
+
if (value.includes("clickhouse")) return { kind: "sql", brand: "ClickHouse", version: null, evidence: raw };
|
|
11689
|
+
if (value.includes("snowflake")) return { kind: "sql", brand: "Snowflake", version: null, evidence: raw };
|
|
11690
|
+
if (value.includes("bigquery")) return { kind: "sql", brand: "BigQuery", version: null, evidence: raw };
|
|
11691
|
+
if (value.includes("firestore")) return { kind: "nosql", brand: "Firestore", version: null, evidence: raw };
|
|
11692
|
+
if (value.includes("cockroach")) return { kind: "sql", brand: "CockroachDB", version: null, evidence: raw };
|
|
11693
|
+
if (value.includes("planetscale")) return { kind: "sql", brand: "PlanetScale", version: null, evidence: raw };
|
|
11694
|
+
if (value.includes("supabase")) return { kind: "sql", brand: "Supabase", version: null, evidence: raw };
|
|
11695
|
+
if (value.includes("tidb")) return { kind: "sql", brand: "TiDB", version: null, evidence: raw };
|
|
11696
|
+
if (value.includes("couchdb")) return { kind: "nosql", brand: "CouchDB", version: null, evidence: raw };
|
|
11697
|
+
if (value.includes("elasticsearch") || value.includes("opensearch")) return { kind: "nosql", brand: "Elasticsearch", version: null, evidence: raw };
|
|
11698
|
+
if (value.includes("influxdb")) return { kind: "nosql", brand: "InfluxDB", version: null, evidence: raw };
|
|
11699
|
+
if (value.includes("memcached")) return { kind: "nosql", brand: "Memcached", version: null, evidence: raw };
|
|
11700
|
+
if (value.includes("cockroachdb")) return { kind: "sql", brand: "CockroachDB", version: null, evidence: raw };
|
|
11142
11701
|
return null;
|
|
11143
11702
|
}
|
|
11144
11703
|
async function scanTextFiles(rootDir, fileCache) {
|
|
@@ -11215,12 +11774,12 @@ async function scanDataStores(rootDir, fileCache) {
|
|
|
11215
11774
|
otherServices: []
|
|
11216
11775
|
};
|
|
11217
11776
|
for (const file of files) {
|
|
11218
|
-
const connStrings = extract(file.content, /\b(?:postgres(?:ql)?:\/\/[^\s'"`]+|mysql:\/\/[^\s'"`]+|mongodb(?:\+srv)?:\/\/[^\s'"`]+|redis:\/\/[^\s'"`]+)\b/gi, file.relPath);
|
|
11777
|
+
const connStrings = extract(file.content, /\b(?:postgres(?:ql)?:\/\/[^\s'"`]+|mysql:\/\/[^\s'"`]+|mongodb(?:\+srv)?:\/\/[^\s'"`]+|redis:\/\/[^\s'"`]+|clickhouse:\/\/[^\s'"`]+|cockroachdb:\/\/[^\s'"`]+|snowflake:\/\/[^\s'"`]+)\b/gi, file.relPath);
|
|
11219
11778
|
result.connectionStrings.push(...connStrings);
|
|
11220
11779
|
const isLockFile = LOCKFILE_NAMES.has(path27.basename(file.relPath));
|
|
11221
11780
|
if (!isLockFile) {
|
|
11222
11781
|
const dbEvidence = [
|
|
11223
|
-
...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|cosmosdb|cosmos|dynamodb|neo4j)\b/gi, file.relPath),
|
|
11782
|
+
...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|cosmosdb|cosmos|dynamodb|neo4j|clickhouse|snowflake|bigquery|firestore|cockroach|cockroachdb|planetscale|supabase|tidb|couchdb|elasticsearch|opensearch|influxdb|memcached)\b/gi, file.relPath),
|
|
11224
11783
|
...connStrings
|
|
11225
11784
|
];
|
|
11226
11785
|
for (const evidence of dbEvidence) {
|
|
@@ -11506,7 +12065,9 @@ async function scanOssGovernance(rootDir, fileCache) {
|
|
|
11506
12065
|
});
|
|
11507
12066
|
}
|
|
11508
12067
|
vulns.push(...extract(file.content, /\b(?:CVE-\d{4}-\d{4,}|critical vulnerability|high severity)\b[^\n]*/gi, file.relPath));
|
|
11509
|
-
|
|
12068
|
+
if (/package(-lock)?\.json$|pnpm-lock\.yaml$|yarn\.lock$|poetry\.lock$|pom\.xml$|build\.gradle(\.kts)?$|Cargo\.(toml|lock)$|composer\.(json|lock)$|requirements\.txt$|\.csproj$|\.nuspec$/i.test(file.relPath)) {
|
|
12069
|
+
licenseRisks.push(...extract(file.content, /\b(?:GPL-3\.0|AGPL|SSPL|BUSL|source[- ]available)\b[^\n]*/gi, file.relPath));
|
|
12070
|
+
}
|
|
11510
12071
|
}
|
|
11511
12072
|
return {
|
|
11512
12073
|
directDependencies: directDeps.size,
|
|
@@ -13227,19 +13788,19 @@ async function loadScanHistory(rootDir) {
|
|
|
13227
13788
|
return null;
|
|
13228
13789
|
}
|
|
13229
13790
|
}
|
|
13230
|
-
async function saveScanHistory(rootDir,
|
|
13791
|
+
async function saveScanHistory(rootDir, record2) {
|
|
13231
13792
|
const dir = path32.join(rootDir, ".vibgrate");
|
|
13232
13793
|
const filePath = path32.join(dir, HISTORY_FILENAME);
|
|
13233
13794
|
let history;
|
|
13234
13795
|
const existing = await loadScanHistory(rootDir);
|
|
13235
13796
|
if (existing) {
|
|
13236
13797
|
history = existing;
|
|
13237
|
-
history.records.push(
|
|
13798
|
+
history.records.push(record2);
|
|
13238
13799
|
if (history.records.length > MAX_RECORDS) {
|
|
13239
13800
|
history.records = history.records.slice(-MAX_RECORDS);
|
|
13240
13801
|
}
|
|
13241
13802
|
} else {
|
|
13242
|
-
history = { version: 1, records: [
|
|
13803
|
+
history = { version: 1, records: [record2] };
|
|
13243
13804
|
}
|
|
13244
13805
|
try {
|
|
13245
13806
|
await fs7.mkdir(dir, { recursive: true });
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
pathExists,
|
|
7
7
|
readJsonFile,
|
|
8
8
|
writeTextFile
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7CIAP6ZD.js";
|
|
10
10
|
import {
|
|
11
11
|
computeRepoFingerprint,
|
|
12
12
|
detectVcs,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
resolveRepositoryName,
|
|
17
17
|
runScan,
|
|
18
18
|
writeDefaultConfig
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-7J3LXQEA.js";
|
|
20
20
|
import {
|
|
21
21
|
require_semver
|
|
22
22
|
} from "./chunk-74ZJFYEM.js";
|
|
@@ -48,7 +48,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
48
48
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
49
49
|
}
|
|
50
50
|
if (opts.baseline) {
|
|
51
|
-
const { runBaseline } = await import("./baseline-
|
|
51
|
+
const { runBaseline } = await import("./baseline-IGEWCPFI.js");
|
|
52
52
|
await runBaseline(rootDir);
|
|
53
53
|
}
|
|
54
54
|
console.log("");
|
|
@@ -74,10 +74,38 @@ import * as crypto from "crypto";
|
|
|
74
74
|
import * as path2 from "path";
|
|
75
75
|
import { Command as Command2 } from "commander";
|
|
76
76
|
import chalk2 from "chalk";
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
|
|
78
|
+
// src/regions.ts
|
|
79
|
+
var REGIONS = [
|
|
80
|
+
{
|
|
81
|
+
id: "us",
|
|
82
|
+
label: "United States",
|
|
83
|
+
ingestHost: "us.ingest.vibgrate.com",
|
|
84
|
+
dashHost: "dash.vibgrate.com",
|
|
85
|
+
available: true
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "eu",
|
|
89
|
+
label: "European Union",
|
|
90
|
+
ingestHost: "eu.ingest.vibgrate.com",
|
|
91
|
+
dashHost: "dash.vibgrate.eu",
|
|
92
|
+
available: true
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "apac",
|
|
96
|
+
label: "Asia-Pacific (coming soon)",
|
|
97
|
+
ingestHost: "apac.ingest.vibgrate.com",
|
|
98
|
+
dashHost: "dash.vibgrate.com",
|
|
99
|
+
available: false
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
var DEFAULT_REGION = "us";
|
|
103
|
+
function availableRegionIds() {
|
|
104
|
+
return REGIONS.filter((r) => r.available).map((r) => r.id);
|
|
105
|
+
}
|
|
106
|
+
function findRegion(id) {
|
|
107
|
+
return REGIONS.find((r) => r.id === id);
|
|
108
|
+
}
|
|
81
109
|
function resolveIngestHost(region, ingest) {
|
|
82
110
|
if (ingest) {
|
|
83
111
|
try {
|
|
@@ -86,14 +114,23 @@ function resolveIngestHost(region, ingest) {
|
|
|
86
114
|
throw new Error(`Invalid ingest URL: ${ingest}`);
|
|
87
115
|
}
|
|
88
116
|
}
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
92
|
-
throw new Error(`Unknown region "${
|
|
117
|
+
const id = (region ?? DEFAULT_REGION).toLowerCase();
|
|
118
|
+
const match = findRegion(id);
|
|
119
|
+
if (!match) {
|
|
120
|
+
throw new Error(`Unknown region "${id}". Supported: ${availableRegionIds().join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
if (!match.available) {
|
|
123
|
+
throw new Error(`Region "${id}" (${match.label}) is not yet available. Supported: ${availableRegionIds().join(", ")}`);
|
|
93
124
|
}
|
|
94
|
-
return
|
|
125
|
+
return match.ingestHost;
|
|
126
|
+
}
|
|
127
|
+
function dashHostForIngestHost(ingestHost) {
|
|
128
|
+
const match = REGIONS.find((r) => r.ingestHost === ingestHost);
|
|
129
|
+
return match?.dashHost ?? "dash.vibgrate.com";
|
|
95
130
|
}
|
|
96
|
-
|
|
131
|
+
|
|
132
|
+
// src/commands/dsn.ts
|
|
133
|
+
async function provisionDsn(keyId, secret, workspaceId, ingestHost, region) {
|
|
97
134
|
const url = `https://${ingestHost}/v1/provision`;
|
|
98
135
|
try {
|
|
99
136
|
const response = await fetch(url, {
|
|
@@ -103,7 +140,10 @@ async function provisionDsn(keyId, secret, workspaceId, ingestHost) {
|
|
|
103
140
|
"Connection": "close"
|
|
104
141
|
// Prevent keep-alive delays on exit
|
|
105
142
|
},
|
|
106
|
-
|
|
143
|
+
// Pin the workspace to the selected region. The API rejects the request
|
|
144
|
+
// if `region` doesn't match the endpoint host (residency guard). For a
|
|
145
|
+
// custom --ingest host we omit it and let the API derive it from the host.
|
|
146
|
+
body: JSON.stringify(region ? { keyId, secret, workspaceId, region } : { keyId, secret, workspaceId })
|
|
107
147
|
});
|
|
108
148
|
if (!response.ok) {
|
|
109
149
|
const result = await response.json();
|
|
@@ -115,7 +155,7 @@ async function provisionDsn(keyId, secret, workspaceId, ingestHost) {
|
|
|
115
155
|
}
|
|
116
156
|
}
|
|
117
157
|
var dsnCommand = new Command2("dsn").description("Manage DSN tokens");
|
|
118
|
-
dsnCommand.command("create").description("Create a new DSN token").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>",
|
|
158
|
+
dsnCommand.command("create").description("Create a new DSN token").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", `Data residency region (${availableRegionIds().join(", ")})`, "us").requiredOption("--workspace <id>", 'Workspace ID (use "new" to auto-generate)').option("--write <path>", "Write DSN to file").action(async (opts) => {
|
|
119
159
|
const keyId = crypto.randomBytes(12).toString("hex");
|
|
120
160
|
const secret = crypto.randomBytes(32).toString("hex");
|
|
121
161
|
let ingestHost;
|
|
@@ -131,8 +171,9 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
131
171
|
workspaceId = crypto.randomBytes(8).toString("hex");
|
|
132
172
|
console.log(chalk2.dim(`Provisioning new workspace ${workspaceId}...`));
|
|
133
173
|
}
|
|
174
|
+
const region = opts.ingest ? void 0 : opts.region.toLowerCase();
|
|
134
175
|
if (isNewWorkspace) {
|
|
135
|
-
const result = await provisionDsn(keyId, secret, workspaceId, ingestHost);
|
|
176
|
+
const result = await provisionDsn(keyId, secret, workspaceId, ingestHost, region);
|
|
136
177
|
if (!result.success) {
|
|
137
178
|
console.error(chalk2.red(`Failed to provision DSN: ${result.error}`));
|
|
138
179
|
process.exit(1);
|
|
@@ -141,6 +182,9 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
141
182
|
const dsn = `vibgrate+https://${keyId}:${secret}@${ingestHost}/${workspaceId}`;
|
|
142
183
|
console.log(chalk2.green("\u2714") + " DSN created");
|
|
143
184
|
console.log("");
|
|
185
|
+
console.log(chalk2.bold("Region:"));
|
|
186
|
+
console.log(` ${region ?? "custom"} (${ingestHost})`);
|
|
187
|
+
console.log("");
|
|
144
188
|
console.log(chalk2.bold("DSN:"));
|
|
145
189
|
console.log(` ${dsn}`);
|
|
146
190
|
console.log("");
|
|
@@ -1379,7 +1423,7 @@ function parseDsn2(dsn) {
|
|
|
1379
1423
|
function computeHmac(body, secret) {
|
|
1380
1424
|
return crypto2.createHmac("sha256", secret).update(body).digest("base64");
|
|
1381
1425
|
}
|
|
1382
|
-
var pushCommand = new Command5("push").description("Push scan results to Vibgrate API").option("--dsn <dsn>", "DSN token (or use VIBGRATE_DSN env)").option("--region <region>",
|
|
1426
|
+
var pushCommand = new Command5("push").description("Push scan results to Vibgrate API").option("--dsn <dsn>", "DSN token (or use VIBGRATE_DSN env)").option("--region <region>", `Override data residency region (${availableRegionIds().join(", ")})`).option("--file <file>", "Scan artifact file", ".vibgrate/scan_result.json").option("--strict", "Fail on upload errors").action(async (opts) => {
|
|
1383
1427
|
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
1384
1428
|
if (!dsn) {
|
|
1385
1429
|
console.error(chalk6.red("No DSN provided."));
|
|
@@ -1442,7 +1486,7 @@ var pushCommand = new Command5("push").description("Push scan results to Vibgrat
|
|
|
1442
1486
|
console.log(chalk6.dim("Processing continues in the background. Results available shortly."));
|
|
1443
1487
|
console.log();
|
|
1444
1488
|
if (result.ingestId) {
|
|
1445
|
-
const dashHost = host
|
|
1489
|
+
const dashHost = dashHostForIngestHost(host);
|
|
1446
1490
|
const reportUrl = `https://${dashHost}/${parsed.workspaceId}/scan/${result.ingestId}`;
|
|
1447
1491
|
console.log(chalk6.dim("View report: ") + chalk6.underline(reportUrl));
|
|
1448
1492
|
}
|
package/dist/index.js
CHANGED