修正 Web 预检错误分流
Project CI / AI game creator shell Rust smoke (push) Successful in 1m46s
Project CI / AI game creator shell Rust crates (push) Successful in 1m10s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (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 / Backend tests (push) Successful in 4m33s
Project CI / Native shell tests (push) Successful in 6m15s
Project CI / Frontend tests (push) Successful in 2m3s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 10m31s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 11m48s
Project CI / AI game creator shell web tests (push) Successful in 2m3s
Project CI / Repository checks (push) Successful in 3m31s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m46s
Project CI / AI game creator shell Rust crates (push) Successful in 1m10s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (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 / Backend tests (push) Successful in 4m33s
Project CI / Native shell tests (push) Successful in 6m15s
Project CI / Frontend tests (push) Successful in 2m3s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 10m31s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 11m48s
Project CI / AI game creator shell web tests (push) Successful in 2m3s
Project CI / Repository checks (push) Successful in 3m31s
This commit is contained in:
@@ -4,19 +4,63 @@ import type { HomeCreationType } from '../../view/home/useHomeDraftStore';
|
||||
export const HOME_WEB_PREFLIGHT_FAILURE =
|
||||
'Web 游戏环境预检未通过,请检查 Node/npm 或浏览器后重试;尚未开始生成。';
|
||||
|
||||
export const HOME_WEB_PREFLIGHT_IPC_FAILURE =
|
||||
'Web 游戏环境预检调用失败,客户端连接异常;请重启客户端后重试,尚未开始生成。';
|
||||
|
||||
type HomeWebPreflightReport = {
|
||||
status?: string;
|
||||
code?: string;
|
||||
};
|
||||
|
||||
function knownPreflightCode(value: unknown): string | undefined {
|
||||
const text = value instanceof Error ? value.message : value;
|
||||
if (typeof text !== 'string') return undefined;
|
||||
const match = text.match(
|
||||
/(?:预检未通过:|预检未通过(|code[=:])([a-z0-9-]+)/i,
|
||||
);
|
||||
return match?.[1];
|
||||
}
|
||||
|
||||
export function homeWebPreflightErrorMessage(value: unknown): string {
|
||||
if (value instanceof Error && value.message === HOME_WEB_PREFLIGHT_FAILURE) {
|
||||
return HOME_WEB_PREFLIGHT_FAILURE;
|
||||
}
|
||||
const code = knownPreflightCode(value);
|
||||
if (code) return `Web 游戏环境预检未通过(${code});尚未开始生成。`;
|
||||
return HOME_WEB_PREFLIGHT_IPC_FAILURE;
|
||||
}
|
||||
|
||||
function isHostPreflightError(value: unknown): boolean {
|
||||
if (!(value instanceof Error)) return false;
|
||||
return value.message === HOME_WEB_PREFLIGHT_FAILURE || Boolean(knownPreflightCode(value.message));
|
||||
}
|
||||
|
||||
export async function ensureHomeWebCreationEnvironment(
|
||||
invoke: TauriInvoke,
|
||||
creationType: HomeCreationType,
|
||||
startMode: ProjectStartMode,
|
||||
): Promise<void> {
|
||||
if (creationType !== 'game' || startMode !== 'direct-build') return;
|
||||
try {
|
||||
const report = await invoke<{ status: string }>(
|
||||
'preflight_web_game_creation',
|
||||
);
|
||||
if (report?.status !== 'ready') throw new Error(HOME_WEB_PREFLIGHT_FAILURE);
|
||||
} catch {
|
||||
// 不把子进程、Provider 或宿主路径的原始错误注入首页状态栏。
|
||||
throw new Error(HOME_WEB_PREFLIGHT_FAILURE);
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
try {
|
||||
const report = await invoke<HomeWebPreflightReport>(
|
||||
'preflight_web_game_creation',
|
||||
);
|
||||
if (report?.status === 'ready') return;
|
||||
throw new Error(
|
||||
report?.code
|
||||
? `Web 游戏环境预检未通过:${report.code}`
|
||||
: HOME_WEB_PREFLIGHT_FAILURE,
|
||||
);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
const code = knownPreflightCode(error);
|
||||
if (isHostPreflightError(error) || code || attempt === 1) {
|
||||
throw new Error(homeWebPreflightErrorMessage(error));
|
||||
}
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
throw new Error(homeWebPreflightErrorMessage(lastError));
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ import { importDesignFiles } from '../project-workspace/importDesignFiles';
|
||||
import { clearAclElevationDenials } from './aclElevation';
|
||||
import {
|
||||
ensureHomeWebCreationEnvironment,
|
||||
HOME_WEB_PREFLIGHT_FAILURE,
|
||||
homeWebPreflightErrorMessage,
|
||||
} from './homeWebPreflight';
|
||||
import type { ShowTransientNotice } from './model';
|
||||
import { resolveSessionPreviewOnProjectOpen } from './sessionPreview';
|
||||
@@ -458,9 +458,10 @@ export function useHomeProjectCreation({
|
||||
}
|
||||
try {
|
||||
await ensureHomeWebCreationEnvironment(invoke, creationType, startMode);
|
||||
} catch {
|
||||
setStatus(HOME_WEB_PREFLIGHT_FAILURE);
|
||||
return HOME_WEB_PREFLIGHT_FAILURE;
|
||||
} catch (error) {
|
||||
const message = homeWebPreflightErrorMessage(error);
|
||||
setStatus(message);
|
||||
return message;
|
||||
}
|
||||
const result = await invoke<InitLocalProjectResult>(
|
||||
'init_local_game_project',
|
||||
@@ -894,7 +895,8 @@ export function useHomeProjectCreation({
|
||||
draft.creationType,
|
||||
startMode,
|
||||
);
|
||||
} catch {
|
||||
} catch (error) {
|
||||
const message = homeWebPreflightErrorMessage(error);
|
||||
setHomeCreationOperation(
|
||||
transitionClientOperation(
|
||||
operation,
|
||||
@@ -902,7 +904,7 @@ export function useHomeProjectCreation({
|
||||
operationScope(),
|
||||
),
|
||||
);
|
||||
return HOME_WEB_PREFLIGHT_FAILURE;
|
||||
return message;
|
||||
}
|
||||
const suggestedName = options.suggestName
|
||||
? await suggestAutomaticProjectName(invoke, draft)
|
||||
|
||||
@@ -3,7 +3,10 @@ import { act, cleanup, renderHook } from '@testing-library/react';
|
||||
import { afterEach, expect, it, vi } from 'vitest';
|
||||
|
||||
import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import { HOME_WEB_PREFLIGHT_FAILURE } from '../src/features/app-shell/homeWebPreflight';
|
||||
import {
|
||||
HOME_WEB_PREFLIGHT_FAILURE,
|
||||
HOME_WEB_PREFLIGHT_IPC_FAILURE,
|
||||
} from '../src/features/app-shell/homeWebPreflight';
|
||||
import { useHomeProjectCreation } from '../src/features/app-shell/useHomeProjectCreation';
|
||||
|
||||
afterEach(() => {
|
||||
@@ -67,26 +70,51 @@ it('awaits the real home preflight before naming or creating a game', async () =
|
||||
expect(result.current.currentProjectContext?.creationType).toBe('game');
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
'blocks naming, creation, and first generation when preflight fails (throws=%s)',
|
||||
async (throws) => {
|
||||
const { result, calls } = mount(async () => {
|
||||
if (throws) throw new Error('private host path');
|
||||
return { status: 'blocked' };
|
||||
});
|
||||
let status = '';
|
||||
await act(async () => {
|
||||
status = await result.current.createHomeDraftAutomatically(
|
||||
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
||||
'direct-build',
|
||||
);
|
||||
});
|
||||
expect(status).toBe(HOME_WEB_PREFLIGHT_FAILURE);
|
||||
expect(calls).toEqual(['preflight_web_game_creation']);
|
||||
expect(result.current.currentProjectContext).toBeNull();
|
||||
expect(status).not.toContain('private');
|
||||
},
|
||||
);
|
||||
it('blocks naming, creation, and first generation with the host error code', async () => {
|
||||
const { result, calls } = mount(async () => {
|
||||
throw new Error('Web 游戏环境预检未通过:web-preflight-browser-failed;尚未启动生成');
|
||||
});
|
||||
let status = '';
|
||||
await act(async () => {
|
||||
status = await result.current.createHomeDraftAutomatically(
|
||||
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
||||
'direct-build',
|
||||
);
|
||||
});
|
||||
expect(status).toBe('Web 游戏环境预检未通过(web-preflight-browser-failed);尚未开始生成。');
|
||||
expect(calls).toEqual(['preflight_web_game_creation']);
|
||||
expect(result.current.currentProjectContext).toBeNull();
|
||||
});
|
||||
|
||||
it('distinguishes an IPC failure from a host preflight failure', async () => {
|
||||
const { result, calls } = mount(async () => {
|
||||
throw new Error('Failed to fetch');
|
||||
});
|
||||
let status = '';
|
||||
await act(async () => {
|
||||
status = await result.current.createHomeDraftAutomatically(
|
||||
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
||||
'direct-build',
|
||||
);
|
||||
});
|
||||
expect(status).toBe(HOME_WEB_PREFLIGHT_IPC_FAILURE);
|
||||
expect(calls).toEqual(['preflight_web_game_creation', 'preflight_web_game_creation']);
|
||||
expect(result.current.currentProjectContext).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the legacy fallback for a blocked report without a code', async () => {
|
||||
const { result, calls } = mount(async () => ({ status: 'blocked' }));
|
||||
let status = '';
|
||||
await act(async () => {
|
||||
status = await result.current.createHomeDraftAutomatically(
|
||||
{ creationType: 'game', prompt: '做一个小游戏', attachments: [] },
|
||||
'direct-build',
|
||||
);
|
||||
});
|
||||
expect(status).toBe(HOME_WEB_PREFLIGHT_FAILURE);
|
||||
expect(calls).toEqual(['preflight_web_game_creation']);
|
||||
expect(result.current.currentProjectContext).toBeNull();
|
||||
});
|
||||
|
||||
it.each(['game', 'doc'] as const)(
|
||||
'does not require the Web preflight for %s planning',
|
||||
|
||||
Reference in New Issue
Block a user