校验原生壳能力声明一致性
移动壳配置检查校验声明能力来自共享 HostBridge 白名单 桌面壳配置检查校验 runtime 能力与 URL hostCapabilities 一致 文档补充新增 native capability 后必须运行双壳检查 共享决策记录补充壳能力防漂移约束
This commit is contained in:
@@ -8,11 +8,68 @@ const appPath = new URL('../App.tsx', import.meta.url);
|
||||
const appSource = fs.readFileSync(appPath, 'utf8');
|
||||
const bridgePath = new URL('../src/mobileHostBridge.ts', import.meta.url);
|
||||
const bridgeSource = fs.readFileSync(bridgePath, 'utf8');
|
||||
const sharedContractPath = new URL(
|
||||
'../../../packages/shared/src/contracts/hostBridge.ts',
|
||||
import.meta.url,
|
||||
);
|
||||
const sharedContractSource = fs.readFileSync(sharedContractPath, 'utf8');
|
||||
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));
|
||||
|
||||
function extractStringArrayExport(source, exportName) {
|
||||
const match = source.match(
|
||||
new RegExp(`export const ${exportName}[^=]*= \\[([\\s\\S]*?)\\](?: as const)?;`),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read ${exportName}`);
|
||||
}
|
||||
|
||||
const entries = [...match[1].matchAll(/'([^']+)'/g)].map(
|
||||
(entry) => entry[1],
|
||||
);
|
||||
if (match[1].includes('...HOST_BRIDGE_METHODS')) {
|
||||
return [
|
||||
...extractStringArrayExport(source, 'HOST_BRIDGE_METHODS'),
|
||||
...entries,
|
||||
];
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
const sharedCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_CAPABILITIES',
|
||||
);
|
||||
const mobileCapabilities = extractStringArrayExport(
|
||||
bridgeSource,
|
||||
'MOBILE_HOST_CAPABILITIES',
|
||||
);
|
||||
const mobileCapabilitySet = new Set(mobileCapabilities);
|
||||
const unknownMobileCapabilities = mobileCapabilities.filter(
|
||||
(capability) => !sharedCapabilities.includes(capability),
|
||||
);
|
||||
if (unknownMobileCapabilities.length > 0) {
|
||||
throw new Error(
|
||||
`mobile shell declares unknown HostBridge capabilities: ${unknownMobileCapabilities.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const capability of mobileCapabilities) {
|
||||
const switchCase = `case '${capability}':`;
|
||||
if (
|
||||
capability !== 'host.events' &&
|
||||
capability !== 'navigation.canGoBack' &&
|
||||
!bridgeSource.includes(switchCase)
|
||||
) {
|
||||
throw new Error(
|
||||
`mobile shell declares ${capability} but does not handle it in HostBridge`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (appConfig.scheme !== 'genarrative') {
|
||||
throw new Error('mobile shell scheme must be genarrative');
|
||||
}
|
||||
@@ -68,3 +125,24 @@ for (const snippet of [
|
||||
throw new Error(`mobile shell HostBridge missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
|
||||
const capabilityQuerySnippet = "capabilities: MOBILE_HOST_CAPABILITIES";
|
||||
if (!appSource.includes(capabilityQuerySnippet)) {
|
||||
throw new Error('mobile shell URL must use MOBILE_HOST_CAPABILITIES');
|
||||
}
|
||||
|
||||
for (const capability of [
|
||||
'host.getRuntime',
|
||||
'share.open',
|
||||
'share.setTarget',
|
||||
'navigation.openNativePage',
|
||||
'navigation.canGoBack',
|
||||
'app.openExternalUrl',
|
||||
'clipboard.writeText',
|
||||
'file.exportText',
|
||||
'haptics.impact',
|
||||
]) {
|
||||
if (!mobileCapabilitySet.has(capability)) {
|
||||
throw new Error(`mobile shell capabilities missing ${capability}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user