Files
kdletters c146f7f99c
Project CI / Repository checks (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
建立 AGC 稳定版生命周期基础合同 (#348)
## 变更内容

这是基于 PR #346 的稳定版生命周期基础切换,承接已完成的 HTTP body timeout、Runner blocking worker、最近项目逐项刷新和首页跨页防重:

- 新增统一 `ClientOperation` 合同,固定 operationId、requestId、phase、deadline、scope、cancellable 和 stale 判定;
- 认证 refresh 投影 `auth-refresh` operation;登录、401 refresh、退出共用平台 session generation,并由 `auth-transition` 投影 Runner phase/成功/失败/不确定状态;
- 首页自动创建记录 draft、startMode、phase 和建项后的 project scope,页面卸载不会释放 operation;
- `.app/dev-stack.json` 增加 instanceId、repoRoot、服务级 dataDir/instanceId,AGC Vite marker 增加 repoRoot/processId/port;缺身份的旧状态拒绝 AGC 后端复用;
- 新增稳定版生命周期主规范、开发运维口径和 shared pitfalls。

## 验证

- `npm --prefix apps/ai-game-creator-shell run typecheck`
- `npm --prefix apps/ai-game-creator-shell exec -- vitest run tests/clientOperation.test.ts tests/clientApi.test.ts tests/clientHttp.test.ts tests/recentProjectsModel.test.ts tests/start-dev-stack.test.ts tests/dev-port.test.ts --reporter=dot`(61 passed,2 skipped)
- `npm --prefix apps/ai-game-creator-shell exec -- vitest run tests/appSurface.test.ts --reporter=dot`(423/423 passed)
- `node --check scripts/dev.mjs`
- `node --check apps/ai-game-creator-shell/scripts/start-dev-stack.mjs`
- `npm run check:doc-index`
- `npm run check:encoding`
- `git diff --check`

`scripts/dev.test.ts` 全量仍有 1 个 Windows 文件权限相关既有失败:bootstrap secret 测试在 Windows 上无法通过 chmod 模拟 0600;本变更未触及该逻辑。

Reviewed-on: #348
2026-09-14 13:56:56 +08:00

172 lines
4.5 KiB
TypeScript

import { existsSync, readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
const appRoot = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(appRoot, '../..');
const appRequire = createRequire(resolve(appRoot, 'package.json'));
const reactFileManagerPackageRoot = dirname(
appRequire.resolve('@cubone/react-file-manager/package.json'),
);
function resolveDevApiTarget() {
const statePath = resolve(repoRoot, '.app/dev-stack.json');
if (!existsSync(statePath)) {
return 'http://127.0.0.1:8082';
}
try {
const state = JSON.parse(readFileSync(statePath, 'utf8')) as {
services?: {
'api-server'?: {
status?: string;
url?: string;
};
};
};
const apiServer = state.services?.['api-server'];
if (
apiServer &&
['running', 'reused', 'starting'].includes(apiServer.status ?? '') &&
apiServer.url
) {
return apiServer.url;
}
return 'http://127.0.0.1:8082';
} catch {
return 'http://127.0.0.1:8082';
}
}
const apiTarget = resolveDevApiTarget();
export default defineConfig({
root: appRoot,
plugins: [
tailwindcss(),
react(),
{
name: 'genarrative-ai-game-creator-dev-marker',
configureServer(server) {
server.middlewares.use(
'/__agc_dev_server.json',
(_request, response) => {
response.setHeader(
'Content-Type',
'application/json; charset=utf-8',
);
response.end(
JSON.stringify({
schemaVersion: 1,
app: 'ai-game-creator-shell',
repoRoot,
processId: process.pid,
port: server.config.server.port,
apiTarget,
}),
);
},
);
},
},
],
resolve: {
alias: [
// v1.35.0 publishes this bundle but its package metadata points Vite at
// an incomplete source entrypoint. Resolve only the bare module to the
// published ESM artifact; its stylesheet remains a valid subpath import.
{
find: /^@cubone\/react-file-manager$/,
replacement: resolve(
reactFileManagerPackageRoot,
'dist/react-file-manager.es.js',
),
},
{
find: '@genarrative/image-canvas-core',
replacement: resolve(
repoRoot,
'packages/image-canvas-core/src/index.ts',
),
},
{
find: '@genarrative/image-canvas-react/styles.css',
replacement: resolve(
repoRoot,
'packages/image-canvas-react/src/styles.css',
),
},
{
find: '@genarrative/image-canvas-react',
replacement: resolve(
repoRoot,
'packages/image-canvas-react/src/index.ts',
),
},
{
find: '@genarrative/shared/styles.css',
replacement: resolve(
repoRoot,
'packages/shared/src/components/styles.css',
),
},
{
find: '@genarrative/shared/theme.css',
replacement: resolve(repoRoot, 'packages/shared/src/theme.css'),
},
{
find: '@genarrative/shared/components/ui',
replacement: resolve(
repoRoot,
'packages/shared/src/components/ui/index.ts',
),
},
{
find: '@genarrative/shared/lib',
replacement: resolve(repoRoot, 'packages/shared/src/lib'),
},
{
find: '@genarrative/shared/components/account',
replacement: resolve(
repoRoot,
'packages/shared/src/components/account.ts',
),
},
{
find: '@genarrative/shared/components',
replacement: resolve(
repoRoot,
'packages/shared/src/components/index.ts',
),
},
{
find: '@genarrative/shared',
replacement: resolve(repoRoot, 'packages/shared/src/index.ts'),
},
],
dedupe: ['react', 'react-dom'],
},
server: {
host: '127.0.0.1',
port: 3080,
strictPort: true,
fs: {
allow: [repoRoot],
},
proxy: {
'/api': {
target: apiTarget,
changeOrigin: true,
},
},
},
build: {
outDir: resolve(appRoot, 'dist'),
emptyOutDir: true,
},
});