收口原生壳锁文件版本

移动壳配置检查锁定package-lock实际解析版本

桌面壳配置检查锁定package-lock与Cargo.lock解析版本

宿主壳方案文档补充锁文件版本门禁

共享决策日志记录原生壳锁文件依赖边界
This commit is contained in:
2026-06-18 14:23:48 +08:00
parent b2b0f4514b
commit 673c6dbbb7
4 changed files with 160 additions and 4 deletions
+117
View File
@@ -6,6 +6,8 @@ const packagePath = new URL('../package.json', import.meta.url);
const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const rootPackagePath = new URL('../../../package.json', import.meta.url);
const rootPackageConfig = JSON.parse(fs.readFileSync(rootPackagePath, 'utf8'));
const rootPackageLockPath = new URL('../../../package-lock.json', import.meta.url);
const rootPackageLock = JSON.parse(fs.readFileSync(rootPackageLockPath, 'utf8'));
const capabilityPath = new URL(
'../src-tauri/capabilities/main.json',
import.meta.url,
@@ -15,6 +17,8 @@ const buildScriptPath = new URL('../src-tauri/build.rs', import.meta.url);
const buildScript = fs.readFileSync(buildScriptPath, 'utf8');
const cargoManifestPath = new URL('../src-tauri/Cargo.toml', import.meta.url);
const cargoManifest = fs.readFileSync(cargoManifestPath, 'utf8');
const cargoLockPath = new URL('../src-tauri/Cargo.lock', import.meta.url);
const cargoLock = fs.readFileSync(cargoLockPath, 'utf8');
const iconDirPath = new URL('../src-tauri/icons/', import.meta.url);
const generatedPermissionDir = new URL(
'../src-tauri/permissions/autogenerated/',
@@ -140,6 +144,22 @@ function extractCargoDependencyLine(source, sectionName, dependencyName) {
throw new Error(`Cargo ${sectionName}.${dependencyName} is missing`);
}
function extractCargoLockPackages(source) {
return source
.split('\n[[package]]\n')
.map((block) => block.trim())
.filter((block) => block.includes('name = '))
.map((block) => {
const name = block.match(/^name = "([^"]+)"/m)?.[1];
const version = block.match(/^version = "([^"]+)"/m)?.[1];
const dependenciesMatch = block.match(/^dependencies = \[\n([\s\S]*?)\n\]/m);
const dependencies = dependenciesMatch
? [...dependenciesMatch[1].matchAll(/ "([^"]+)"/g)].map((entry) => entry[1])
: [];
return { block, dependencies, name, version };
});
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
@@ -242,6 +262,15 @@ function assertPackageDependencyVersion(
}
}
function assertPackageLockVersion(dependency, expected) {
const actual = rootPackageLock.packages?.[`node_modules/${dependency}`]?.version;
if (actual !== expected) {
throw new Error(
`root package-lock ${dependency} resolved version drifted: expected ${expected} but got ${actual}`,
);
}
}
function assertCargoDependencyLine(sectionName, dependencyName, expectedLine) {
const actualLine = extractCargoDependencyLine(
cargoManifest,
@@ -255,6 +284,52 @@ function assertCargoDependencyLine(sectionName, dependencyName, expectedLine) {
}
}
const cargoLockPackages = extractCargoLockPackages(cargoLock);
function findCargoLockPackage(packageName, expectedVersion) {
return cargoLockPackages.find(
(entry) => entry.name === packageName && entry.version === expectedVersion,
);
}
function assertCargoLockPackageVersion(packageName, expectedVersion) {
if (!findCargoLockPackage(packageName, expectedVersion)) {
const actualVersions = cargoLockPackages
.filter((entry) => entry.name === packageName)
.map((entry) => entry.version)
.join(', ');
throw new Error(
`Cargo.lock ${packageName} resolved version drifted: expected ${expectedVersion} but got ${actualVersions || 'missing'}`,
);
}
}
function assertCargoLockDirectDependency(
parentPackageName,
parentVersion,
dependencyName,
expectedVersion,
) {
const parentPackage = findCargoLockPackage(parentPackageName, parentVersion);
if (!parentPackage) {
throw new Error(
`Cargo.lock ${parentPackageName} ${parentVersion} is missing`,
);
}
const dependencyToken = parentPackage.dependencies.find((dependency) => {
const [name, version] = dependency.split(' ');
return name === dependencyName && (!version || version === expectedVersion);
});
if (!dependencyToken) {
throw new Error(
`Cargo.lock ${parentPackageName} ${parentVersion} dependency ${dependencyName} drifted: expected ${expectedVersion}`,
);
}
assertCargoLockPackageVersion(dependencyName, expectedVersion);
}
function collectProductionSourceFiles(entry) {
const stats = fs.statSync(entry);
if (stats.isDirectory()) {
@@ -349,6 +424,13 @@ for (const [dependency, expected] of Object.entries({
);
}
for (const [dependency, expected] of Object.entries({
'@tauri-apps/cli': '2.11.2',
typescript: '5.8.3',
})) {
assertPackageLockVersion(dependency, expected);
}
for (const [sectionName, dependencyName, expectedLine] of [
['build-dependencies', 'tauri-build', 'tauri-build = { version = "2.6.2", features = [] }'],
['dependencies', 'base64', 'base64 = "0.22"'],
@@ -384,6 +466,41 @@ for (const [sectionName, dependencyName, expectedLine] of [
assertCargoDependencyLine(sectionName, dependencyName, expectedLine);
}
for (const [packageName, expectedVersion] of [
['tauri-build', '2.6.2'],
['base64', '0.22.1'],
['serde', '1.0.228'],
['serde_json', '1.0.150'],
['tauri', '2.11.2'],
['tauri-plugin-clipboard-manager', '2.3.2'],
['tauri-plugin-dialog', '2.7.1'],
['tauri-plugin-notification', '2.3.3'],
['tauri-plugin-opener', '2.5.4'],
['tauri-plugin-single-instance', '2.4.2'],
]) {
assertCargoLockPackageVersion(packageName, expectedVersion);
}
for (const [dependencyName, expectedVersion] of [
['tauri-build', '2.6.2'],
['base64', '0.22.1'],
['serde', '1.0.228'],
['serde_json', '1.0.150'],
['tauri', '2.11.2'],
['tauri-plugin-clipboard-manager', '2.3.2'],
['tauri-plugin-dialog', '2.7.1'],
['tauri-plugin-notification', '2.3.3'],
['tauri-plugin-opener', '2.5.4'],
['tauri-plugin-single-instance', '2.4.2'],
]) {
assertCargoLockDirectDependency(
'genarrative-desktop-shell',
config.version,
dependencyName,
expectedVersion,
);
}
function readPngSize(file) {
const buffer = fs.readFileSync(file);
if (