Files
gongxue-base/scripts/taro-supply-chain-audit-test.js
2026-07-12 19:26:57 +08:00

140 lines
5.9 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import {
allowedInvalidEdges,
npmAuditArgs,
securedBundleDependencies,
validateAuditPayload,
validateNpmLsPayload,
} from './taro-supply-chain-audit.js';
import {
enforceTaroH5RuntimePatches,
taroButtonLoadingPatch,
taroH5RuntimePatchDefinition,
taroInputWatcherPatch,
} from './taro-components-h5-runtime-patch.js';
const repoRoot = process.cwd();
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
const packageLock = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package-lock.json'), 'utf8'));
const taroPackage = JSON.parse(fs.readFileSync(path.join(repoRoot, 'apps', 'taro', 'package.json'), 'utf8'));
assert.equal(taroPackage.devDependencies?.['@tarojs/components'], taroH5RuntimePatchDefinition.packageVersion);
assert.equal(taroPackage.scripts?.postinstall, taroH5RuntimePatchDefinition.postinstallCommand);
assert.equal(packageLock.packages?.['apps/taro']?.hasInstallScript, true);
const patchState = enforceTaroH5RuntimePatches({ root: repoRoot, mode: 'check' });
assert.equal(patchState.status, 'pass');
assert.equal(patchState.patches.inputWatcher.installedSha256, taroInputWatcherPatch.patchedSha256);
assert.equal(patchState.patches.buttonLoading.installedSha256, taroButtonLoadingPatch.patchedSha256);
for (const [name, expected] of Object.entries(securedBundleDependencies)) {
assert.equal(packageJson.overrides?.[name], expected.version, `${name} override must stay exact`);
assert.equal(packageLock.packages?.[`node_modules/${name}`]?.version, expected.version, `${name} lock version must stay exact`);
assert.equal(packageLock.packages?.[`node_modules/${name}`]?.integrity, expected.integrity, `${name} lock integrity must stay exact`);
}
const dependencyNode = (name, parents) => ({
version: securedBundleDependencies[name].version,
invalid: parents.map(parent => `"${parent.declared}" from node_modules/${parent.parent}`).join(', '),
problems: [`invalid: ${name}@${securedBundleDependencies[name].version} /repo/node_modules/${name}`],
});
const edgesFor = dependency => allowedInvalidEdges.filter(edge => edge.dependency === dependency);
const validLs = {
error: { code: 'ELSPROBLEMS' },
problems: Object.entries(securedBundleDependencies).map(([name, expected]) => `invalid: ${name}@${expected.version} /repo/node_modules/${name}`),
dependencies: {
'@tiku-saas/taro': {
dependencies: {
'@tarojs/components': {
dependencies: {
swiper: dependencyNode('swiper', edgesFor('swiper').filter(edge => edge.parent === '@tarojs/components')),
},
},
'@tarojs/plugin-platform-h5': {
dependencies: {
'@tarojs/components-react': {
dependencies: {
swiper: dependencyNode('swiper', edgesFor('swiper').filter(edge => edge.parent === '@tarojs/components-react')),
},
},
'@tarojs/taro-h5': {
dependencies: {
'lodash-es': dependencyNode('lodash-es', edgesFor('lodash-es').filter(edge => edge.parent === '@tarojs/taro-h5')),
},
},
'lodash-es': dependencyNode('lodash-es', edgesFor('lodash-es').filter(edge => edge.parent === '@tarojs/plugin-platform-h5')),
},
},
},
},
},
};
const lsSummary = validateNpmLsPayload(validLs);
assert.equal(lsSummary.edges.length, 4);
assert.throws(
() => validateNpmLsPayload({ ...validLs, problems: [...validLs.problems, 'extraneous: unsafe@1.0.0 /repo/node_modules/unsafe'] }),
/unexpected problem|unapproved problem/,
'new npm ls problems must fail closed',
);
const missingEdgeLs = structuredClone(validLs);
delete missingEdgeLs.dependencies['@tiku-saas/taro'].dependencies['@tarojs/plugin-platform-h5'].dependencies['@tarojs/components-react'];
assert.throws(() => validateNpmLsPayload(missingEdgeLs), /once per reviewed invalid edge|invalid-edge set changed/, 'the exception set must not silently shrink or change');
const auditFixture = {
vulnerabilities: {
'@tarojs/cli': { severity: 'high', isDirect: true },
download: { severity: 'critical', isDirect: false, via: [] },
'git-clone': { severity: 'high', isDirect: false, via: [{ source: 1093404, severity: 'high' }] },
esbuild: { severity: 'moderate', isDirect: false },
},
metadata: {
vulnerabilities: { info: 0, low: 0, moderate: 1, high: 2, critical: 1, total: 4 },
},
};
const auditSummary = validateAuditPayload(auditFixture);
assert.equal(auditSummary.reviewedHighCritical.length, 3);
assert.deepEqual(auditSummary.reviewedAdvisories, [1093404]);
assert.throws(
() => validateAuditPayload({
...auditFixture,
vulnerabilities: { ...auditFixture.vulnerabilities, swiper: { severity: 'critical', isDirect: false } },
}),
/still reports swiper/,
'bundle dependency advisories must fail the gate',
);
assert.throws(
() => validateAuditPayload({
...auditFixture,
vulnerabilities: { ...auditFixture.vulnerabilities, 'new-build-risk': { severity: 'high', isDirect: false } },
}),
/new unreviewed high/,
'new high or critical toolchain findings must require review',
);
assert.throws(
() => validateAuditPayload({
...auditFixture,
vulnerabilities: {
...auditFixture.vulnerabilities,
'git-clone': { severity: 'high', isDirect: false, via: [{ source: 9999999, severity: 'high' }] },
},
}),
/new unreviewed high Taro advisory/,
'new advisories on an already allowlisted package must require review',
);
const auditArgs = npmAuditArgs('https://registry.npmjs.org/');
assert.ok(auditArgs.includes('--workspace'));
assert.ok(auditArgs.includes('@tiku-saas/taro'));
assert.equal(auditArgs.some(arg => arg === '--omit=dev' || arg.startsWith('--omit=')), false, 'Taro audit must include dependencies that are marked dev but bundled into H5');
console.log('[PASS] Taro supply-chain audit contract');