Merge branch 'master' into opt/win-ssache
Project CI / AI game creator shell Rust tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
This commit is contained in:
+238
-51
@@ -79,6 +79,72 @@ const aiGameCreatorViteConfigSource = fs.readFileSync(
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// 按门禁组运行:默认跑全部分组(本地语义不变),CI 用 `--groups=` 把互不依赖的
|
||||
// 分组拆成独立 job。每个步骤和静态断言必须属于且只属于一个分组,分组名同时是
|
||||
// 根 `check:native-shells:<group>` 脚本和 workflow job 的拆分口径。
|
||||
// - contract:纯源码 / 契约 / 清单断言,不需要任何构建产物。
|
||||
// - shells:微信壳、Expo 移动壳、Tauri 桌面壳与 H5 HostBridge 的现役运行时门禁。
|
||||
// - agc-web:AI 游戏创作壳的前端门禁(typecheck 与壳内测试,不触碰 Cargo)。
|
||||
// - agc-rust:AI 游戏创作壳的 Rust 门禁(共享 / 平台 crate 测试、串行壳测试和
|
||||
// 会用 `src-tauri/Cargo.toml` spawn `cargo` 的 agent-run smoke)。
|
||||
// - release:发布构建 smoke 和依赖发布产物的落盘断言。
|
||||
const nativeShellGateGroups = [
|
||||
'contract',
|
||||
'shells',
|
||||
'agc-web',
|
||||
'agc-rust',
|
||||
'release',
|
||||
];
|
||||
const requestedNativeShellGroups = readRequestedNativeShellGroups(
|
||||
process.argv.slice(2),
|
||||
);
|
||||
|
||||
function readRequestedNativeShellGroups(argv) {
|
||||
const groupsFlag = argv.find((argument) => argument.startsWith('--groups='));
|
||||
if (groupsFlag === undefined) {
|
||||
return nativeShellGateGroups;
|
||||
}
|
||||
|
||||
const requested = groupsFlag
|
||||
.slice('--groups='.length)
|
||||
.split(',')
|
||||
.map((group) => group.trim())
|
||||
.filter(Boolean);
|
||||
if (requested.length === 0) {
|
||||
throw new Error(
|
||||
`--groups requires at least one of: ${nativeShellGateGroups.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const unknownGroups = requested.filter(
|
||||
(group) => !nativeShellGateGroups.includes(group),
|
||||
);
|
||||
if (unknownGroups.length > 0) {
|
||||
throw new Error(
|
||||
`unknown native shell gate group(s): ${unknownGroups.join(', ')}; expected ${nativeShellGateGroups.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
return nativeShellGateGroups.filter((group) => requested.includes(group));
|
||||
}
|
||||
|
||||
function runsNativeShellGateGroup(group) {
|
||||
if (!nativeShellGateGroups.includes(group)) {
|
||||
throw new Error(`unknown native shell gate group: ${group}`);
|
||||
}
|
||||
|
||||
return requestedNativeShellGroups.includes(group);
|
||||
}
|
||||
|
||||
function runNativeShellGate(group, label, gate) {
|
||||
if (!runsNativeShellGateGroup(group)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[check:native-shells] ${label}`);
|
||||
gate();
|
||||
}
|
||||
|
||||
const productionShellScanRoots = [
|
||||
'apps/mobile-shell',
|
||||
'apps/desktop-shell',
|
||||
@@ -161,7 +227,11 @@ function assertRootNativeShellCheckScripts() {
|
||||
}
|
||||
}
|
||||
|
||||
assertRootNativeShellCheckScripts();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'root-native-shell-check-scripts',
|
||||
assertRootNativeShellCheckScripts,
|
||||
);
|
||||
function assertNativeShellDependencyVersionGuardrails() {
|
||||
for (const snippet of [
|
||||
"const rootPackageLockPath = new URL('../../../package-lock.json', import.meta.url)",
|
||||
@@ -209,7 +279,11 @@ function assertNativeShellDependencyVersionGuardrails() {
|
||||
}
|
||||
}
|
||||
|
||||
assertNativeShellDependencyVersionGuardrails();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'native-shell-dependency-version-guardrails',
|
||||
assertNativeShellDependencyVersionGuardrails,
|
||||
);
|
||||
const h5HostBridgeCallChainWrapperFiles = [
|
||||
'src/hooks/useHostNavigationCanGoBack.ts',
|
||||
'src/components/platform-entry/platformProfileHostClipboard.ts',
|
||||
@@ -2156,6 +2230,7 @@ const h5HostBridgeTests = [
|
||||
const h5NativeAppRouteFlowTestSteps = h5NativeAppRouteFlowContracts.flatMap(
|
||||
(contract) =>
|
||||
(contract.targetedTests ?? []).map((test) => ({
|
||||
group: 'shells',
|
||||
label: `h5-native-app-route-${contract.route}`,
|
||||
command: npmCommand,
|
||||
args: ['run', 'test', '--', test.filePath, '-t', test.name],
|
||||
@@ -2176,67 +2251,98 @@ const wechatShellTests = [
|
||||
|
||||
const steps = [
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'h5-host-bridge-tests',
|
||||
command: npmCommand,
|
||||
args: ['run', 'test', '--', ...h5HostBridgeTests],
|
||||
},
|
||||
...h5NativeAppRouteFlowTestSteps,
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'wechat-shell-tests',
|
||||
command: npmCommand,
|
||||
args: ['run', 'test', '--', ...wechatShellTests],
|
||||
},
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'mobile-shell-typecheck',
|
||||
command: npmCommand,
|
||||
args: ['run', 'mobile-shell:typecheck'],
|
||||
},
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'mobile-shell-test',
|
||||
command: npmCommand,
|
||||
args: ['run', 'mobile-shell:test'],
|
||||
},
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'mobile-shell-eas-build-config-smoke',
|
||||
command: npmCommand,
|
||||
args: ['run', 'mobile-shell:build-config'],
|
||||
},
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'mobile-shell-expo-config-smoke',
|
||||
command: npmCommand,
|
||||
args: ['run', 'mobile-shell:config'],
|
||||
},
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'mobile-shell-expo-export-smoke',
|
||||
command: npmCommand,
|
||||
args: ['run', 'mobile-shell:export'],
|
||||
},
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'desktop-shell-test',
|
||||
command: npmCommand,
|
||||
args: ['run', 'desktop-shell:test'],
|
||||
},
|
||||
{
|
||||
group: 'shells',
|
||||
label: 'desktop-shell-typecheck',
|
||||
command: npmCommand,
|
||||
args: ['run', 'desktop-shell:typecheck'],
|
||||
},
|
||||
// AI 游戏创作壳原先一步串完 typecheck、壳内测试、共享 / 平台 crate 测试和
|
||||
// 串行壳测试,CI 因此只有一条 10 分钟以上的长尾。这里按同一组命令切成
|
||||
// web 与 rust 两段,顺序与 `npm run ai-game-creator-shell:check` 完全一致,
|
||||
// 但允许 CI 并行执行;本地全量运行仍然是 web -> rust -> smoke 原顺序。
|
||||
{
|
||||
label: 'ai-game-creator-shell-check',
|
||||
group: 'agc-web',
|
||||
label: 'ai-game-creator-shell-check-web',
|
||||
command: npmCommand,
|
||||
args: ['run', 'ai-game-creator-shell:check'],
|
||||
args: ['run', 'ai-game-creator-shell:check:web'],
|
||||
},
|
||||
{
|
||||
group: 'agc-rust',
|
||||
label: 'ai-game-creator-shell-check-rust',
|
||||
command: npmCommand,
|
||||
args: ['run', 'ai-game-creator-shell:check:rust'],
|
||||
},
|
||||
// agent-run smoke 会用 `src-tauri/Cargo.toml` spawn `cargo`,因此与 Rust 段同组,
|
||||
// 保证它落在已经预热 AGC Cargo 依赖的 job 里。
|
||||
{
|
||||
group: 'agc-rust',
|
||||
label: 'ai-game-creator-shell-agent-run-smoke',
|
||||
command: npmCommand,
|
||||
args: ['run', 'ai-game-creator-shell:agent-run:smoke'],
|
||||
},
|
||||
{
|
||||
group: 'release',
|
||||
label: 'ai-game-creator-shell-release-build-smoke',
|
||||
command: npmCommand,
|
||||
args: ['run', 'ai-game-creator-shell:build', '--', '--no-bundle'],
|
||||
},
|
||||
{
|
||||
group: 'release',
|
||||
label: 'desktop-shell-release-build-smoke',
|
||||
command: npmCommand,
|
||||
args: ['run', 'desktop-shell:build', '--', '--no-bundle'],
|
||||
},
|
||||
{
|
||||
group: 'release',
|
||||
label: 'desktop-shell-stage-release-binary',
|
||||
command: npmCommand,
|
||||
args: ['run', 'desktop-shell:stage-release-binary'],
|
||||
@@ -2664,6 +2770,12 @@ function normalizeModulePath(modulePath) {
|
||||
.replace(/\.(jsx?|tsx?)$/, '');
|
||||
}
|
||||
|
||||
// 调用链扫描用 POSIX 相对路径做集合成员比较,但 `collectFiles` 在 Windows 上返回
|
||||
// 反斜杠路径。这里只统一分隔符、保留扩展名,Linux 上与原行为完全一致。
|
||||
function normalizeScannedFilePath(filePath) {
|
||||
return filePath.split(path.sep).join('/');
|
||||
}
|
||||
|
||||
function importedModulePath(fromFile, specifier) {
|
||||
if (specifier === '@') {
|
||||
return '.';
|
||||
@@ -2758,12 +2870,12 @@ function collectH5HostBridgeCallChainFiles() {
|
||||
importsScannedFacadeCapability ||
|
||||
imports.some((specifier) => wrapperModules.has(specifier))
|
||||
) {
|
||||
scannedFiles.add(file);
|
||||
scannedFiles.add(normalizeScannedFilePath(file));
|
||||
}
|
||||
}
|
||||
|
||||
for (const wrapperFile of h5HostBridgeCallChainWrapperFiles) {
|
||||
scannedFiles.add(wrapperFile);
|
||||
scannedFiles.add(normalizeScannedFilePath(wrapperFile));
|
||||
}
|
||||
|
||||
const missingRequiredFiles = h5HostBridgeRequiredCallChainFiles.filter(
|
||||
@@ -5021,6 +5133,10 @@ function assertDesktopReleaseBinaryArtifact() {
|
||||
}
|
||||
|
||||
for (const step of steps) {
|
||||
if (!runsNativeShellGateGroup(step.group)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[check:native-shells] ${step.label}`);
|
||||
const result = spawnSync(step.command, step.args, {
|
||||
cwd: process.cwd(),
|
||||
@@ -5046,71 +5162,142 @@ for (const step of steps) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[check:native-shells] desktop-release-binary-artifact');
|
||||
assertDesktopReleaseBinaryArtifact();
|
||||
runNativeShellGate(
|
||||
'release',
|
||||
'desktop-release-binary-artifact',
|
||||
assertDesktopReleaseBinaryArtifact,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] host-bridge-layer-layout');
|
||||
assertHostBridgeLayerLayout();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'host-bridge-layer-layout',
|
||||
assertHostBridgeLayerLayout,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] native-shell-capability-plan');
|
||||
assertNativeShellCapabilityPlan();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'native-shell-capability-plan',
|
||||
assertNativeShellCapabilityPlan,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] external-url-protocol-parity');
|
||||
assertExternalUrlProtocolParity();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'external-url-protocol-parity',
|
||||
assertExternalUrlProtocolParity,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] wechat-mini-program-route-parity');
|
||||
assertWechatMiniProgramRouteParity();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'wechat-mini-program-route-parity',
|
||||
assertWechatMiniProgramRouteParity,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] wechat-mini-program-capability-flows');
|
||||
assertWechatMiniProgramCapabilityFlows();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'wechat-mini-program-capability-flows',
|
||||
assertWechatMiniProgramCapabilityFlows,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] expo-mobile-capability-flows');
|
||||
assertExpoMobileCapabilityFlows();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'expo-mobile-capability-flows',
|
||||
assertExpoMobileCapabilityFlows,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] tauri-desktop-capability-flows');
|
||||
assertTauriDesktopCapabilityFlows();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'tauri-desktop-capability-flows',
|
||||
assertTauriDesktopCapabilityFlows,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] h5-native-app-route-flows');
|
||||
assertH5NativeAppRouteFlows();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'h5-native-app-route-flows',
|
||||
assertH5NativeAppRouteFlows,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] wechat-payment-result-boundaries');
|
||||
assertWechatPaymentResultBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'wechat-payment-result-boundaries',
|
||||
assertWechatPaymentResultBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] wechat-auth-failure-boundaries');
|
||||
assertWechatAuthFailureBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'wechat-auth-failure-boundaries',
|
||||
assertWechatAuthFailureBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] wechat-web-view-page-event-boundaries');
|
||||
assertWechatWebViewPageEventBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'wechat-web-view-page-event-boundaries',
|
||||
assertWechatWebViewPageEventBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] wechat-share-grid-failure-boundaries');
|
||||
assertWechatShareGridFailureBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'wechat-share-grid-failure-boundaries',
|
||||
assertWechatShareGridFailureBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] desktop-navigation-event-boundaries');
|
||||
assertDesktopNavigationEventBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'desktop-navigation-event-boundaries',
|
||||
assertDesktopNavigationEventBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] h5-host-bridge-event-subscription-gates');
|
||||
assertH5HostBridgeEventSubscriptionGates();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'h5-host-bridge-event-subscription-gates',
|
||||
assertH5HostBridgeEventSubscriptionGates,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] h5-host-bridge-payload-boundaries');
|
||||
assertH5HostBridgePayloadBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'h5-host-bridge-payload-boundaries',
|
||||
assertH5HostBridgePayloadBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] h5-native-app-transport-timeout-boundaries');
|
||||
assertH5NativeAppTransportTimeoutBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'h5-native-app-transport-timeout-boundaries',
|
||||
assertH5NativeAppTransportTimeoutBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] h5-native-app-message-source-boundaries');
|
||||
assertH5NativeAppMessageSourceBoundaries();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'h5-native-app-message-source-boundaries',
|
||||
assertH5NativeAppMessageSourceBoundaries,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] h5-native-app-transport-facade-boundary');
|
||||
assertH5NativeAppTransportFacadeBoundary();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'h5-native-app-transport-facade-boundary',
|
||||
assertH5NativeAppTransportFacadeBoundary,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] generated-native-shell-artifact-boundary');
|
||||
assertNoTrackedGeneratedNativeShellArtifacts();
|
||||
assertGeneratedNativeShellArtifactsAreIgnored();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'generated-native-shell-artifact-boundary',
|
||||
() => {
|
||||
assertNoTrackedGeneratedNativeShellArtifacts();
|
||||
assertGeneratedNativeShellArtifactsAreIgnored();
|
||||
},
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] ai-game-creator-shell-user-dev-boundary');
|
||||
assertAiGameCreatorShellUserDevBoundary();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'ai-game-creator-shell-user-dev-boundary',
|
||||
assertAiGameCreatorShellUserDevBoundary,
|
||||
);
|
||||
|
||||
console.log('[check:native-shells] production-shell-dev-scaffold-scan');
|
||||
assertNoProductionShellDevScaffoldTerms();
|
||||
runNativeShellGate(
|
||||
'contract',
|
||||
'production-shell-dev-scaffold-scan',
|
||||
assertNoProductionShellDevScaffoldTerms,
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[check:native-shells] groups=${requestedNativeShellGroups.join(',')}`,
|
||||
);
|
||||
console.log('[check:native-shells] OK');
|
||||
|
||||
@@ -40,8 +40,28 @@ const jobNames = [
|
||||
'frontend-tests',
|
||||
'backend-tests',
|
||||
'native-shell-tests',
|
||||
'ai-game-creator-shell-web-tests',
|
||||
'ai-game-creator-shell-rust-tests',
|
||||
] as const;
|
||||
|
||||
const rootPackageJson = JSON.parse(
|
||||
readFileSync(resolve(process.cwd(), 'package.json'), 'utf8'),
|
||||
) as { scripts?: Record<string, string> };
|
||||
const nativeShellGateScript = readFileSync(
|
||||
resolve(process.cwd(), 'scripts/check-native-shells.mjs'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// 客户端门禁拆分口径:门禁脚本里的每个分组都由一个根 npm 脚本暴露,并在 workflow
|
||||
// 的某个 job 里被恰好调用一次。新增分组时必须同步这三处,否则拆分就会静默漏跑门禁。
|
||||
const nativeShellGateGroupScripts = {
|
||||
contract: 'npm run check:native-shells:contract',
|
||||
shells: 'npm run check:native-shells:shells',
|
||||
'agc-web': 'npm run check:native-shells:agc-web',
|
||||
'agc-rust': 'npm run check:native-shells:agc-rust',
|
||||
release: 'npm run check:native-shells:release',
|
||||
} as const;
|
||||
|
||||
function jobSection(jobName: (typeof jobNames)[number]) {
|
||||
const jobStart = workflow.indexOf(` ${jobName}:`);
|
||||
expect(jobStart).toBeGreaterThanOrEqual(0);
|
||||
@@ -67,6 +87,10 @@ function stepSection(jobName: (typeof jobNames)[number], stepName: string) {
|
||||
);
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
||||
}
|
||||
|
||||
function backendStepIndex(stepName: string) {
|
||||
const backendJobStart = workflow.indexOf(' backend-tests:');
|
||||
const nativeShellJobStart = workflow.indexOf(' native-shell-tests:');
|
||||
@@ -87,7 +111,9 @@ describe('project CI workflow', () => {
|
||||
});
|
||||
|
||||
it('keeps every job on the isolated preinstalled CI image boundary', () => {
|
||||
expect(workflow.match(/^ {4}runs-on: genarrative-ci$/gm)).toHaveLength(4);
|
||||
expect(workflow.match(/^ {4}runs-on: genarrative-ci$/gm)).toHaveLength(
|
||||
jobNames.length,
|
||||
);
|
||||
expect(workflow).not.toContain('actions/checkout');
|
||||
expect(workflow).not.toContain('actions/setup-node');
|
||||
expect(workflow).not.toMatch(/^\s+run: .*\b(?:apt|rustup)\b/m);
|
||||
@@ -314,17 +340,77 @@ describe('project CI workflow', () => {
|
||||
expect(frontendJob).toContain('run: npm run check:production-api-deploy');
|
||||
|
||||
const nativeJob = jobSection('native-shell-tests');
|
||||
expect(nativeJob).toContain('run: npm run check:native-shells');
|
||||
expect(nativeJob).toContain('run: npm run check:native-shells:contract');
|
||||
expect(nativeJob).toContain('run: npm run check:native-shells:shells');
|
||||
expect(nativeJob).toContain('run: npm run check:native-shells:release');
|
||||
expect(nativeJob).toContain(
|
||||
'git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock apps/ai-game-creator-shell/src-tauri/Cargo.lock',
|
||||
);
|
||||
expect(nativeJob).toContain('server-rs/Cargo.toml');
|
||||
expect(nativeJob).toContain('apps/desktop-shell/src-tauri/Cargo.toml');
|
||||
expect(nativeJob).toContain(
|
||||
'apps/ai-game-creator-shell/src-tauri/Cargo.toml',
|
||||
);
|
||||
expect(nativeJob).toContain('cargo fetch --locked');
|
||||
});
|
||||
|
||||
it('prefetches the excluded standalone Rust crates before the native shell gates', () => {
|
||||
it('runs every native shell gate group exactly once across the split jobs', () => {
|
||||
for (const [group, script] of Object.entries(nativeShellGateGroupScripts)) {
|
||||
expect(rootPackageJson.scripts?.[`check:native-shells:${group}`]).toBe(
|
||||
`node scripts/check-native-shells.mjs --groups=${group}`,
|
||||
);
|
||||
expect(
|
||||
workflow.match(new RegExp(`^ {8}run: ${escapeRegExp(script)}$`, 'mu')),
|
||||
).toHaveLength(1);
|
||||
}
|
||||
|
||||
const declaredGroups = [
|
||||
...nativeShellGateScript
|
||||
.slice(
|
||||
nativeShellGateScript.indexOf('const nativeShellGateGroups = ['),
|
||||
nativeShellGateScript.indexOf(
|
||||
'];',
|
||||
nativeShellGateScript.indexOf('const nativeShellGateGroups = ['),
|
||||
),
|
||||
)
|
||||
.matchAll(/'([a-z-]+)'/gu),
|
||||
].map((match) => match[1]);
|
||||
expect(declaredGroups).toEqual(Object.keys(nativeShellGateGroupScripts));
|
||||
|
||||
// 全量分组脚本只允许本地使用:CI 必须走拆分后的分组脚本,避免整套门禁再被
|
||||
// 串行跑一遍。
|
||||
expect(workflow).not.toMatch(/^ {8}run: npm run check:native-shells$/mu);
|
||||
});
|
||||
|
||||
it('splits the AI game creator shell gates into web and Rust jobs', () => {
|
||||
const webJob = jobSection('ai-game-creator-shell-web-tests');
|
||||
expect(webJob).toContain('run: npm run check:native-shells:agc-web');
|
||||
expect(webJob).not.toContain('cargo fetch');
|
||||
|
||||
const rustJob = jobSection('ai-game-creator-shell-rust-tests');
|
||||
expect(rustJob).toContain('run: npm run check:native-shells:agc-rust');
|
||||
expect(rustJob).toContain('server-rs/Cargo.toml');
|
||||
expect(rustJob).toContain(
|
||||
'apps/ai-game-creator-shell/src-tauri/Cargo.toml',
|
||||
);
|
||||
expect(rustJob).toContain('cargo fetch --locked');
|
||||
|
||||
// 拆开的 web / rust 两段必须还是原 `ai-game-creator-shell:check` 的同一条命令序列。
|
||||
expect(rootPackageJson.scripts?.['ai-game-creator-shell:check']).toBe(
|
||||
'npm run ai-game-creator-shell:check:web && npm run ai-game-creator-shell:check:rust && npm run ai-game-creator-shell:agent-run:smoke',
|
||||
);
|
||||
expect(rootPackageJson.scripts?.['ai-game-creator-shell:check:web']).toBe(
|
||||
'npm run ai-game-creator-shell:typecheck && npm run test -- apps/ai-game-creator-shell/tests',
|
||||
);
|
||||
expect(
|
||||
rootPackageJson.scripts?.['ai-game-creator-shell:check:rust'],
|
||||
).toContain(
|
||||
'cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --test-threads=1',
|
||||
);
|
||||
});
|
||||
|
||||
it('prefetches the excluded standalone Rust crates before the AI game creator shell Rust gates', () => {
|
||||
const standaloneStep = stepSection(
|
||||
'native-shell-tests',
|
||||
'ai-game-creator-shell-rust-tests',
|
||||
'Prepare standalone Rust crate dependencies',
|
||||
);
|
||||
for (const manifest of [
|
||||
@@ -338,9 +424,11 @@ describe('project CI workflow', () => {
|
||||
expect(standaloneStep).toContain('cargo fetch \\');
|
||||
expect(standaloneStep).not.toContain('cargo fetch --locked');
|
||||
|
||||
const nativeJob = jobSection('native-shell-tests');
|
||||
const rustJob = jobSection('ai-game-creator-shell-rust-tests');
|
||||
expect(
|
||||
nativeJob.indexOf('Prepare standalone Rust crate dependencies'),
|
||||
).toBeLessThan(nativeJob.indexOf('run: npm run check:native-shells'));
|
||||
rustJob.indexOf('Prepare standalone Rust crate dependencies'),
|
||||
).toBeLessThan(
|
||||
rustJob.indexOf('run: npm run check:native-shells:agc-rust'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user