forked from wangziqi/gongxue-base
140 lines
5.9 KiB
JavaScript
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');
|