@wipcomputer/wip-license-hook 1.0.0

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.
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Scanner — detects dependencies and their licenses across package managers.
3
+ * Supports: npm, pip, cargo, go modules. Works offline.
4
+ */
5
+ import { readFileSync, existsSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { execSync } from "node:child_process";
8
+ import { detectLicenseFromText, normalizeSpdx } from "./detector.js";
9
+ import { readLedger, writeLedger, upsertEntry, findEntry, addAlert, archiveSnapshot, hasLicenseChanged, } from "./ledger.js";
10
+ // ─── License file discovery ───
11
+ const LICENSE_NAMES = ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING", "COPYING.md"];
12
+ export function findLicenseFile(dir) {
13
+ for (const name of LICENSE_NAMES) {
14
+ const p = join(dir, name);
15
+ if (existsSync(p))
16
+ return p;
17
+ }
18
+ return null;
19
+ }
20
+ export function readLicenseFromDir(dir) {
21
+ const p = findLicenseFile(dir);
22
+ if (!p)
23
+ return null;
24
+ const text = readFileSync(p, "utf-8");
25
+ return { license: detectLicenseFromText(text), text };
26
+ }
27
+ // ─── Package manager detection ───
28
+ function detectPackageManagers(repoRoot) {
29
+ const types = [];
30
+ if (existsSync(join(repoRoot, "package.json")))
31
+ types.push("npm");
32
+ if (existsSync(join(repoRoot, "requirements.txt")) || existsSync(join(repoRoot, "Pipfile")) || existsSync(join(repoRoot, "pyproject.toml")))
33
+ types.push("pip");
34
+ if (existsSync(join(repoRoot, "Cargo.toml")))
35
+ types.push("cargo");
36
+ if (existsSync(join(repoRoot, "go.mod")))
37
+ types.push("go");
38
+ return types;
39
+ }
40
+ // ─── npm scanning ───
41
+ function scanNpmDeps(repoRoot, offline) {
42
+ const results = [];
43
+ const pkgPath = join(repoRoot, "package.json");
44
+ if (!existsSync(pkgPath))
45
+ return results;
46
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
47
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
48
+ for (const [name, _version] of Object.entries(allDeps)) {
49
+ let detectedLicense = "UNKNOWN";
50
+ let licenseText;
51
+ // Check node_modules first (works offline)
52
+ const modDir = join(repoRoot, "node_modules", name);
53
+ if (existsSync(modDir)) {
54
+ const fromFile = readLicenseFromDir(modDir);
55
+ if (fromFile) {
56
+ detectedLicense = fromFile.license;
57
+ licenseText = fromFile.text;
58
+ }
59
+ // Also check package.json license field
60
+ const modPkg = join(modDir, "package.json");
61
+ if (existsSync(modPkg) && detectedLicense === "UNKNOWN") {
62
+ try {
63
+ const modMeta = JSON.parse(readFileSync(modPkg, "utf-8"));
64
+ if (modMeta.license)
65
+ detectedLicense = normalizeSpdx(modMeta.license);
66
+ }
67
+ catch { /* skip */ }
68
+ }
69
+ }
70
+ // Try npm view if online and still unknown
71
+ if (detectedLicense === "UNKNOWN" && !offline) {
72
+ try {
73
+ const out = execSync(`npm view ${name} license 2>/dev/null`, { encoding: "utf-8", timeout: 10000 }).trim();
74
+ if (out)
75
+ detectedLicense = normalizeSpdx(out);
76
+ }
77
+ catch { /* offline or not found */ }
78
+ }
79
+ results.push({
80
+ name,
81
+ source: `npm:${name}`,
82
+ type: "npm",
83
+ detectedLicense,
84
+ licenseText,
85
+ wasChanged: false,
86
+ isNew: true,
87
+ });
88
+ }
89
+ return results;
90
+ }
91
+ // ─── pip scanning ───
92
+ function parsePipDeps(repoRoot) {
93
+ const names = [];
94
+ const reqPath = join(repoRoot, "requirements.txt");
95
+ if (existsSync(reqPath)) {
96
+ const lines = readFileSync(reqPath, "utf-8").split("\n");
97
+ for (const line of lines) {
98
+ const trimmed = line.trim();
99
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-"))
100
+ continue;
101
+ const name = trimmed.split(/[=<>!~\[]/)[0].trim();
102
+ if (name)
103
+ names.push(name);
104
+ }
105
+ }
106
+ return names;
107
+ }
108
+ function scanPipDeps(repoRoot, offline) {
109
+ const deps = parsePipDeps(repoRoot);
110
+ const results = [];
111
+ for (const name of deps) {
112
+ let detectedLicense = "UNKNOWN";
113
+ if (!offline) {
114
+ try {
115
+ const out = execSync(`pip show ${name} 2>/dev/null`, { encoding: "utf-8", timeout: 10000 });
116
+ const match = out.match(/^License:\s*(.+)$/m);
117
+ if (match)
118
+ detectedLicense = normalizeSpdx(match[1]);
119
+ }
120
+ catch { /* skip */ }
121
+ }
122
+ results.push({
123
+ name,
124
+ source: `pip:${name}`,
125
+ type: "pip",
126
+ detectedLicense,
127
+ wasChanged: false,
128
+ isNew: true,
129
+ });
130
+ }
131
+ return results;
132
+ }
133
+ // ─── cargo scanning ───
134
+ function scanCargoDeps(repoRoot, offline) {
135
+ const results = [];
136
+ const cargoPath = join(repoRoot, "Cargo.toml");
137
+ if (!existsSync(cargoPath))
138
+ return results;
139
+ const content = readFileSync(cargoPath, "utf-8");
140
+ const depSection = content.match(/\[dependencies\]([\s\S]*?)(\[|$)/);
141
+ if (!depSection)
142
+ return results;
143
+ const lines = depSection[1].split("\n");
144
+ for (const line of lines) {
145
+ const match = line.match(/^(\S+)\s*=/);
146
+ if (!match)
147
+ continue;
148
+ const name = match[1];
149
+ let detectedLicense = "UNKNOWN";
150
+ if (!offline) {
151
+ try {
152
+ const out = execSync(`cargo info ${name} 2>/dev/null`, { encoding: "utf-8", timeout: 10000 });
153
+ const lMatch = out.match(/license:\s*(.+)/i);
154
+ if (lMatch)
155
+ detectedLicense = normalizeSpdx(lMatch[1]);
156
+ }
157
+ catch { /* skip */ }
158
+ }
159
+ results.push({
160
+ name,
161
+ source: `cargo:${name}`,
162
+ type: "cargo",
163
+ detectedLicense,
164
+ wasChanged: false,
165
+ isNew: true,
166
+ });
167
+ }
168
+ return results;
169
+ }
170
+ // ─── go scanning ───
171
+ function scanGoDeps(repoRoot, _offline) {
172
+ const results = [];
173
+ const goModPath = join(repoRoot, "go.mod");
174
+ if (!existsSync(goModPath))
175
+ return results;
176
+ const content = readFileSync(goModPath, "utf-8");
177
+ const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/);
178
+ const lines = requireBlock ? requireBlock[1].split("\n") : [];
179
+ // Also handle single-line requires
180
+ const singleRequires = content.match(/^require\s+(\S+)\s+/gm) ?? [];
181
+ for (const sr of singleRequires) {
182
+ const m = sr.match(/^require\s+(\S+)/);
183
+ if (m)
184
+ lines.push(m[1]);
185
+ }
186
+ for (const line of lines) {
187
+ const trimmed = line.trim();
188
+ if (!trimmed || trimmed.startsWith("//"))
189
+ continue;
190
+ const parts = trimmed.split(/\s+/);
191
+ const name = parts[0];
192
+ if (!name || name.startsWith(")"))
193
+ continue;
194
+ results.push({
195
+ name,
196
+ source: `go:${name}`,
197
+ type: "go",
198
+ detectedLicense: "UNKNOWN", // Go modules need network for license check
199
+ wasChanged: false,
200
+ isNew: true,
201
+ });
202
+ }
203
+ return results;
204
+ }
205
+ // ─── Fork/upstream scanning ───
206
+ function scanUpstreamLicense(repoRoot, offline) {
207
+ // Check if there's an upstream remote
208
+ try {
209
+ const remote = execSync("git remote get-url upstream 2>/dev/null", {
210
+ cwd: repoRoot, encoding: "utf-8", timeout: 5000
211
+ }).trim();
212
+ if (!remote)
213
+ return null;
214
+ // Fetch upstream (if online)
215
+ if (!offline) {
216
+ try {
217
+ execSync("git fetch upstream --quiet 2>/dev/null", { cwd: repoRoot, timeout: 30000 });
218
+ }
219
+ catch { /* offline, use cached */ }
220
+ }
221
+ // Try to read LICENSE from upstream/main or upstream/master
222
+ for (const branch of ["upstream/main", "upstream/master"]) {
223
+ try {
224
+ const text = execSync(`git show ${branch}:LICENSE 2>/dev/null`, {
225
+ cwd: repoRoot, encoding: "utf-8", timeout: 5000
226
+ });
227
+ if (text) {
228
+ const license = detectLicenseFromText(text);
229
+ return {
230
+ name: "upstream",
231
+ source: remote,
232
+ type: "fork",
233
+ detectedLicense: license,
234
+ licenseText: text,
235
+ wasChanged: false,
236
+ isNew: true,
237
+ };
238
+ }
239
+ }
240
+ catch { /* try next branch */ }
241
+ }
242
+ }
243
+ catch { /* no upstream remote */ }
244
+ return null;
245
+ }
246
+ // ─── Main scan ───
247
+ export function scanAll(opts) {
248
+ const { repoRoot, offline = false } = opts;
249
+ const managers = detectPackageManagers(repoRoot);
250
+ const results = [];
251
+ // Always check upstream fork
252
+ const upstream = scanUpstreamLicense(repoRoot, offline);
253
+ if (upstream)
254
+ results.push(upstream);
255
+ if (managers.includes("npm"))
256
+ results.push(...scanNpmDeps(repoRoot, offline));
257
+ if (managers.includes("pip"))
258
+ results.push(...scanPipDeps(repoRoot, offline));
259
+ if (managers.includes("cargo"))
260
+ results.push(...scanCargoDeps(repoRoot, offline));
261
+ if (managers.includes("go"))
262
+ results.push(...scanGoDeps(repoRoot, offline));
263
+ return results;
264
+ }
265
+ /**
266
+ * Run a full scan and update the ledger. Returns results with change detection.
267
+ */
268
+ export function scanAndUpdate(opts) {
269
+ const { repoRoot } = opts;
270
+ const ledger = readLedger(repoRoot);
271
+ const results = scanAll(opts);
272
+ const today = new Date().toISOString().slice(0, 10);
273
+ for (const result of results) {
274
+ const existing = findEntry(ledger, result.name);
275
+ if (existing) {
276
+ result.isNew = false;
277
+ existing.license_current = result.detectedLicense;
278
+ existing.last_checked = today;
279
+ if (hasLicenseChanged(existing)) {
280
+ existing.status = "changed";
281
+ result.wasChanged = true;
282
+ addAlert(ledger, result.name, existing.license_at_adoption, result.detectedLicense);
283
+ }
284
+ else {
285
+ existing.status = "clean";
286
+ }
287
+ upsertEntry(ledger, existing);
288
+ }
289
+ else {
290
+ // New dependency
291
+ const entry = {
292
+ name: result.name,
293
+ source: result.source,
294
+ type: result.type,
295
+ license_at_adoption: result.detectedLicense,
296
+ license_current: result.detectedLicense,
297
+ adopted_date: today,
298
+ last_checked: today,
299
+ status: "clean",
300
+ };
301
+ upsertEntry(ledger, entry);
302
+ }
303
+ // Archive license text if available
304
+ if (result.licenseText) {
305
+ archiveSnapshot(repoRoot, result.name, result.licenseText, today);
306
+ }
307
+ }
308
+ ledger.last_full_scan = new Date().toISOString();
309
+ writeLedger(repoRoot, ledger);
310
+ return results;
311
+ }
312
+ /**
313
+ * Gate check — returns true if safe to proceed, false if blocked.
314
+ */
315
+ export function gateCheck(repoRoot, offline) {
316
+ const results = scanAndUpdate({ repoRoot, offline });
317
+ const changed = results.filter((r) => r.wasChanged);
318
+ const alerts = changed.map((r) => `🚫 LICENSE CHANGED: ${r.name} — was adopted under different terms, now detected as ${r.detectedLicense}`);
319
+ return {
320
+ safe: changed.length === 0,
321
+ results,
322
+ alerts,
323
+ };
324
+ }
325
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # wip-license-hook — Pre-pull hook (hard gate)
3
+ # Blocks pull/merge if upstream license has changed.
4
+ #
5
+ # Install: cp hooks/pre-pull.sh .git/hooks/pre-merge-commit && chmod +x .git/hooks/pre-merge-commit
6
+ # Or: wip-license-hook install
7
+
8
+ set -euo pipefail
9
+
10
+ REPO_ROOT="$(git rev-parse --show-toplevel)"
11
+
12
+ # Check if wip-license-hook is available
13
+ if command -v wip-license-hook &>/dev/null; then
14
+ HOOK_CMD="wip-license-hook"
15
+ elif [ -f "$REPO_ROOT/node_modules/.bin/wip-license-hook" ]; then
16
+ HOOK_CMD="$REPO_ROOT/node_modules/.bin/wip-license-hook"
17
+ elif command -v npx &>/dev/null; then
18
+ HOOK_CMD="npx @wipcomputer/wip-license-hook"
19
+ else
20
+ echo ""
21
+ echo "╔══════════════════════════════════════════════════╗"
22
+ echo "║ ⚠️ wip-license-hook not found ║"
23
+ echo "║ Install: npm i -g @wipcomputer/wip-license-hook ║"
24
+ echo "║ Pull proceeding WITHOUT license check. ║"
25
+ echo "╚══════════════════════════════════════════════════╝"
26
+ echo ""
27
+ exit 0
28
+ fi
29
+
30
+ echo ""
31
+ echo "🔒 wip-license-hook: Checking upstream licenses..."
32
+ echo ""
33
+
34
+ # Run the gate check — exits non-zero if license changed
35
+ cd "$REPO_ROOT"
36
+ $HOOK_CMD gate
37
+
38
+ EXIT_CODE=$?
39
+
40
+ if [ $EXIT_CODE -ne 0 ]; then
41
+ echo ""
42
+ echo "╔══════════════════════════════════════════════════╗"
43
+ echo "║ 🚫 MERGE BLOCKED — License change detected! ║"
44
+ echo "║ ║"
45
+ echo "║ Review the changes above. If you accept the ║"
46
+ echo "║ new license, update the ledger: ║"
47
+ echo "║ wip-license-hook scan ║"
48
+ echo "║ ║"
49
+ echo "║ Then retry the pull/merge. ║"
50
+ echo "╚══════════════════════════════════════════════════╝"
51
+ echo ""
52
+ exit 1
53
+ fi
54
+
55
+ exit 0
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # wip-license-hook — Pre-push hook (advisory alert)
3
+ # Warns if upstream license has drifted. Does NOT block push.
4
+ #
5
+ # Install: cp hooks/pre-push.sh .git/hooks/pre-push && chmod +x .git/hooks/pre-push
6
+ # Or: wip-license-hook install
7
+
8
+ set -uo pipefail
9
+
10
+ REPO_ROOT="$(git rev-parse --show-toplevel)"
11
+
12
+ # Check if wip-license-hook is available
13
+ if command -v wip-license-hook &>/dev/null; then
14
+ HOOK_CMD="wip-license-hook"
15
+ elif [ -f "$REPO_ROOT/node_modules/.bin/wip-license-hook" ]; then
16
+ HOOK_CMD="$REPO_ROOT/node_modules/.bin/wip-license-hook"
17
+ elif command -v npx &>/dev/null; then
18
+ HOOK_CMD="npx @wipcomputer/wip-license-hook"
19
+ else
20
+ # No tool available — push proceeds silently
21
+ exit 0
22
+ fi
23
+
24
+ echo ""
25
+ echo "🔒 wip-license-hook: Checking license status before push..."
26
+ echo ""
27
+
28
+ cd "$REPO_ROOT"
29
+
30
+ # Run gate in advisory mode — capture output but NEVER block push
31
+ OUTPUT=$($HOOK_CMD gate 2>&1) || true
32
+ EXIT_CODE=$?
33
+
34
+ if [ $EXIT_CODE -ne 0 ]; then
35
+ echo ""
36
+ echo "╔══════════════════════════════════════════════════╗"
37
+ echo "║ ⚠️ LICENSE DRIFT DETECTED ║"
38
+ echo "║ ║"
39
+ echo "║ Upstream license may have changed. ║"
40
+ echo "║ Your push will proceed (it's your code). ║"
41
+ echo "║ ║"
42
+ echo "║ Run: wip-license-hook scan --verbose ║"
43
+ echo "║ to review the changes. ║"
44
+ echo "╚══════════════════════════════════════════════════╝"
45
+ echo ""
46
+ echo "$OUTPUT"
47
+ echo ""
48
+ fi
49
+
50
+ # ALWAYS exit 0 — pre-push is advisory only
51
+ exit 0
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@wipcomputer/wip-license-hook",
3
+ "version": "1.0.0",
4
+ "description": "License rug-pull detection and dependency license compliance for open source projects",
5
+ "type": "module",
6
+ "main": "dist/cli/index.js",
7
+ "bin": {
8
+ "wip-license-hook": "dist/cli/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/cli/index.js",
14
+ "lint": "tsc --noEmit"
15
+ },
16
+ "keywords": ["license", "compliance", "git-hook", "rug-pull", "dependency", "scanner"],
17
+ "author": "WIP Computer",
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.3.0",
24
+ "@types/node": "^20.0.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "files": [
30
+ "dist/",
31
+ "hooks/",
32
+ "dashboard/",
33
+ "README.md",
34
+ "LICENSE"
35
+ ]
36
+ }