禁止壳生产代码保留临时替身

移动壳配置检查扫描生产源码脚手架词

桌面壳配置检查扫描生产源码脚手架词

保留测试文件使用 mock 的边界

更新原生壳方案和团队决策记录
This commit is contained in:
2026-06-18 08:42:18 +08:00
parent 64c5c65b20
commit 01a346a9b0
4 changed files with 145 additions and 0 deletions
@@ -19,6 +19,24 @@ const packagePath = new URL('../package.json', import.meta.url);
const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const iconPath = new URL('../assets/icon.png', import.meta.url);
const icon = PNG.sync.read(fs.readFileSync(iconPath));
const productionSourceRoots = [
new URL('../App.tsx', import.meta.url),
new URL('../app.json', import.meta.url),
new URL('../package.json', import.meta.url),
new URL('../src/', import.meta.url),
];
const productionFileExtensions = new Set(['.json', '.mjs', '.ts', '.tsx']);
const devScaffoldTerms = [
'mo' + 'ck',
'fa' + 'ke',
'place' + 'holder',
'st' + 'ub',
'TO' + 'DO',
'FIX' + 'ME',
'占' + '位',
'模' + '拟',
'伪' + '造',
];
function extractStringArrayExport(source, exportName) {
const match = source.match(
@@ -41,6 +59,58 @@ function extractStringArrayExport(source, exportName) {
return entries;
}
function collectProductionSourceFiles(entry) {
const stats = fs.statSync(entry);
if (stats.isDirectory()) {
const directory = entry.href.endsWith('/') ? entry : new URL(`${entry.href}/`);
return fs
.readdirSync(entry, { withFileTypes: true })
.flatMap((child) =>
collectProductionSourceFiles(
new URL(`${child.name}${child.isDirectory() ? '/' : ''}`, directory),
),
);
}
const path = entry.pathname;
const extension = path.match(/\.[^.]+$/)?.[0] ?? '';
if (!productionFileExtensions.has(extension)) {
return [];
}
if (path.includes('.test.') || path.endsWith('/scripts/check-config.mjs')) {
return [];
}
return [entry];
}
function assertNoDevScaffoldTerms(files) {
for (const file of files) {
const source = fs.readFileSync(file, 'utf8');
const lineStarts = [0];
for (let index = 0; index < source.length; index += 1) {
if (source[index] === '\n') {
lineStarts.push(index + 1);
}
}
for (const term of devScaffoldTerms) {
const matchIndex = source.toLowerCase().indexOf(term.toLowerCase());
if (matchIndex === -1) {
continue;
}
const line = lineStarts.filter((start) => start <= matchIndex).length;
throw new Error(
`mobile shell production source must not include ${term}: ${file.pathname}:${line}`,
);
}
}
}
assertNoDevScaffoldTerms(
productionSourceRoots.flatMap((root) => collectProductionSourceFiles(root)),
);
const sharedCapabilities = extractStringArrayExport(
sharedContractSource,
'HOST_BRIDGE_CAPABILITIES',