File size: 2,667 Bytes
704e84f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/bin/env node

import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

const REPO_ROOT = fileURLToPath(new URL('..', import.meta.url));

function readJson(path) {
    return JSON.parse(readFileSync(join(REPO_ROOT, path), 'utf8'));
}

function extractPackageName(lockfilePath) {
    const packagePath = lockfilePath.slice(lockfilePath.lastIndexOf('node_modules/') + 'node_modules/'.length);
    if (/^(?:@[^/]+\/)?[^/]+$/.test(packagePath)) return packagePath;
    return undefined;
}

export function collectInstallScriptIdentities(packageLock) {
    const identities = [];
    for (const [lockfilePath, manifest] of Object.entries(packageLock.packages ?? {})) {
        if (!lockfilePath.includes('node_modules/') || manifest?.hasInstallScript !== true) continue;

        const name = extractPackageName(lockfilePath);
        if (!name || typeof manifest.version !== 'string' || manifest.version.length === 0) {
            throw new Error(`Invalid install-script package-lock entry: ${lockfilePath}`);
        }
        identities.push(`${name}@${manifest.version}`);
    }
    return [...new Set(identities)].sort();
}

export function validateInstallScriptPolicy(packageLock, packageJson) {
    const installScriptIdentities = collectInstallScriptIdentities(packageLock);
    const policy = packageJson.allowScripts;
    const entries = policy && typeof policy === 'object' && !Array.isArray(policy) ? Object.entries(policy) : [];
    const invalid = entries
        .filter(([, value]) => typeof value !== 'boolean')
        .map(([identity]) => identity)
        .sort();
    const approved = entries
        .filter(([, value]) => value === true)
        .map(([identity]) => identity)
        .sort();
    const denied = entries
        .filter(([, value]) => value === false)
        .map(([identity]) => identity)
        .sort();
    const configured = entries.map(([identity]) => identity);
    const missing = installScriptIdentities.filter((identity) => !approved.includes(identity));
    const stale = configured.filter((identity) => !installScriptIdentities.includes(identity)).sort();

    return {
        ok: missing.length === 0 && stale.length === 0 && invalid.length === 0,
        installScriptIdentities,
        approved,
        denied,
        missing,
        stale,
        invalid
    };
}

function main() {
    const result = validateInstallScriptPolicy(readJson('package-lock.json'), readJson('package.json'));
    console.log(JSON.stringify(result, null, 2));
    if (!result.ok) process.exit(1);
}

if (process.argv[1] === fileURLToPath(import.meta.url)) main();