支持App Run并行开发并修复资源画布
新增App Run独立开发profile并隔离端口、数据库、Tauri身份与AppData 常驻资源排列切换并避免布局状态挤压操作入口 显式扩展资源画布横向滚动范围并收敛依赖图可见连线 补充启动器、资源布局、界面回归测试与项目文档
This commit is contained in:
@@ -8,17 +8,56 @@ import { fileURLToPath } from 'node:url';
|
||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
const repoRoot = resolve(appRoot, '../..');
|
||||
const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json');
|
||||
const viteHost = '127.0.0.1';
|
||||
const vitePort = 3080;
|
||||
const devProfileName = process.env.GENARRATIVE_AGC_DEV_PROFILE || 'default';
|
||||
|
||||
function resolveDevStackProfile(name = 'default') {
|
||||
switch (name) {
|
||||
case 'default':
|
||||
return {
|
||||
name,
|
||||
viteHost: '127.0.0.1',
|
||||
vitePort: 3080,
|
||||
apiPort: 8082,
|
||||
bgfilterWorkerPort: 8083,
|
||||
spacetimePort: 3101,
|
||||
backendDatabase: 'genarrative-game-creator-dev',
|
||||
backendSpacetimeDataDir: resolve(
|
||||
repoRoot,
|
||||
'server-rs/.spacetimedb/ai-game-creator/data',
|
||||
),
|
||||
};
|
||||
case 'app-run':
|
||||
return {
|
||||
name,
|
||||
viteHost: '127.0.0.1',
|
||||
vitePort: 3081,
|
||||
apiPort: 8084,
|
||||
bgfilterWorkerPort: 8085,
|
||||
spacetimePort: 3103,
|
||||
backendDatabase: 'genarrative-game-creator-app-run-dev',
|
||||
backendSpacetimeDataDir: resolve(
|
||||
repoRoot,
|
||||
'server-rs/.spacetimedb/ai-game-creator-app-run/data',
|
||||
),
|
||||
};
|
||||
default:
|
||||
throw new Error(`未知 AI 游戏创作开发 profile: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const devProfile = resolveDevStackProfile(devProfileName);
|
||||
const {
|
||||
viteHost,
|
||||
vitePort,
|
||||
apiPort,
|
||||
bgfilterWorkerPort,
|
||||
spacetimePort,
|
||||
backendDatabase,
|
||||
backendSpacetimeDataDir,
|
||||
} = devProfile;
|
||||
const viteUrl = `http://${viteHost}:${vitePort}/`;
|
||||
const viteMarkerUrl = `${viteUrl}__agc_dev_server.json`;
|
||||
const defaultApiTarget =
|
||||
process.env.RUST_SERVER_TARGET || 'http://127.0.0.1:8082';
|
||||
const backendDatabase = 'genarrative-game-creator-dev';
|
||||
const backendSpacetimeDataDir = resolve(
|
||||
repoRoot,
|
||||
'server-rs/.spacetimedb/ai-game-creator/data',
|
||||
);
|
||||
process.env.RUST_SERVER_TARGET || `http://127.0.0.1:${apiPort}`;
|
||||
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
const childLifecycles = new WeakMap();
|
||||
|
||||
@@ -72,6 +111,7 @@ function resolveBackendTargetsFromState(
|
||||
expectedDatabase = backendDatabase,
|
||||
expectedSpacetimeDataDir = backendSpacetimeDataDir,
|
||||
fallbackApiTarget = defaultApiTarget,
|
||||
fallbackSpacetimeTarget = `http://127.0.0.1:${spacetimePort}`,
|
||||
} = {},
|
||||
) {
|
||||
const apiServer = state?.services?.['api-server'];
|
||||
@@ -100,7 +140,7 @@ function resolveBackendTargetsFromState(
|
||||
? spacetime.url
|
||||
: requireAgcBackend
|
||||
? ''
|
||||
: 'http://127.0.0.1:3101';
|
||||
: fallbackSpacetimeTarget;
|
||||
return {
|
||||
apiUrl,
|
||||
spacetimeUrl,
|
||||
@@ -131,13 +171,24 @@ async function isBackendReady() {
|
||||
);
|
||||
}
|
||||
|
||||
async function readExistingViteServer() {
|
||||
return httpGetText(viteUrl);
|
||||
function resolveViteProfileUrls(profile = devProfile) {
|
||||
const profileViteUrl = `http://${profile.viteHost}:${profile.vitePort}/`;
|
||||
return {
|
||||
viteUrl: profileViteUrl,
|
||||
viteMarkerUrl: `${profileViteUrl}__agc_dev_server.json`,
|
||||
};
|
||||
}
|
||||
|
||||
function isVitePortListening() {
|
||||
async function readExistingViteServer(profile = devProfile) {
|
||||
return httpGetText(resolveViteProfileUrls(profile).viteUrl);
|
||||
}
|
||||
|
||||
function isVitePortListening(profile = devProfile) {
|
||||
return new Promise((resolveRequest) => {
|
||||
const socket = net.connect({ host: viteHost, port: vitePort });
|
||||
const socket = net.connect({
|
||||
host: profile.viteHost,
|
||||
port: profile.vitePort,
|
||||
});
|
||||
socket.once('connect', () => {
|
||||
socket.destroy();
|
||||
resolveRequest(true);
|
||||
@@ -160,8 +211,11 @@ function isAiGameCreatorServer(response) {
|
||||
);
|
||||
}
|
||||
|
||||
async function readExistingViteMarker() {
|
||||
const response = await httpGetText(viteMarkerUrl, 2000);
|
||||
async function readExistingViteMarker(profile = devProfile) {
|
||||
const response = await httpGetText(
|
||||
resolveViteProfileUrls(profile).viteMarkerUrl,
|
||||
2000,
|
||||
);
|
||||
if (!response || response.statusCode !== 200) {
|
||||
return null;
|
||||
}
|
||||
@@ -173,15 +227,17 @@ async function readExistingViteMarker() {
|
||||
}
|
||||
|
||||
async function preflightExistingVite({
|
||||
readServer = readExistingViteServer,
|
||||
portListening = isVitePortListening,
|
||||
readMarker = readExistingViteMarker,
|
||||
profile = devProfile,
|
||||
readServer = () => readExistingViteServer(profile),
|
||||
portListening = () => isVitePortListening(profile),
|
||||
readMarker = () => readExistingViteMarker(profile),
|
||||
} = {}) {
|
||||
const { viteUrl: profileViteUrl } = resolveViteProfileUrls(profile);
|
||||
const existing = await readServer();
|
||||
if (!existing) {
|
||||
if (await portListening()) {
|
||||
throw new Error(
|
||||
`${viteUrl} is already in use by a non-HTTP or unrecognized server. Stop it before starting Tauri dev.`,
|
||||
`${profileViteUrl} is already in use by a non-HTTP or unrecognized server. Stop it before starting Tauri dev.`,
|
||||
);
|
||||
}
|
||||
return { status: 'available', apiTarget: '' };
|
||||
@@ -189,7 +245,7 @@ async function preflightExistingVite({
|
||||
|
||||
if (!isAiGameCreatorServer(existing)) {
|
||||
throw new Error(
|
||||
`${viteUrl} is already in use by another server. Stop it before starting Tauri dev.`,
|
||||
`${profileViteUrl} is already in use by another server. Stop it before starting Tauri dev.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -202,7 +258,7 @@ async function preflightExistingVite({
|
||||
: '';
|
||||
const actualTarget = markerApiTarget || 'unknown';
|
||||
throw new Error(
|
||||
`${viteUrl} is already running with API target ${actualTarget}. Its owning worktree cannot be proven, so it will not be reused. Stop that Vite dev server before starting Tauri dev.`,
|
||||
`${profileViteUrl} is already running with API target ${actualTarget}. Its owning worktree cannot be proven, so it will not be reused. Stop that Vite dev server before starting Tauri dev.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -507,27 +563,50 @@ async function waitForBackendReady(backendChild, timeoutMs = 600_000) {
|
||||
throw new Error('等待配套后端和数据库启动超时');
|
||||
}
|
||||
|
||||
function buildBackendStartArguments(profile = devProfile) {
|
||||
return [
|
||||
'--prefix',
|
||||
'../..',
|
||||
'run',
|
||||
'agc:backend',
|
||||
'--',
|
||||
'--database',
|
||||
profile.backendDatabase,
|
||||
'--spacetime-data-dir',
|
||||
profile.backendSpacetimeDataDir,
|
||||
'--api-port',
|
||||
String(profile.apiPort),
|
||||
'--bgfilter-worker-port',
|
||||
String(profile.bgfilterWorkerPort),
|
||||
'--spacetime-port',
|
||||
String(profile.spacetimePort),
|
||||
'--no-interactive',
|
||||
];
|
||||
}
|
||||
|
||||
function buildViteStartArguments(profile = devProfile) {
|
||||
return [
|
||||
'--prefix',
|
||||
'../..',
|
||||
'exec',
|
||||
'vite',
|
||||
'--',
|
||||
'--config',
|
||||
'vite.config.ts',
|
||||
'--host',
|
||||
profile.viteHost,
|
||||
'--port',
|
||||
String(profile.vitePort),
|
||||
'--strictPort',
|
||||
];
|
||||
}
|
||||
|
||||
async function ensureBackend({
|
||||
onBackendChild = () => {},
|
||||
checkBackendReady = isBackendReady,
|
||||
resolveTargets = readBackendTargets,
|
||||
spawnBackend = () =>
|
||||
spawnChild(
|
||||
npm,
|
||||
[
|
||||
'--prefix',
|
||||
'../..',
|
||||
'run',
|
||||
'agc:backend',
|
||||
'--',
|
||||
'--database',
|
||||
backendDatabase,
|
||||
'--spacetime-data-dir',
|
||||
backendSpacetimeDataDir,
|
||||
'--no-interactive',
|
||||
],
|
||||
{ cwd: appRoot },
|
||||
),
|
||||
spawnChild(npm, buildBackendStartArguments(), { cwd: appRoot }),
|
||||
waitUntilReady = waitForBackendReady,
|
||||
} = {}) {
|
||||
if (await checkBackendReady()) {
|
||||
@@ -569,11 +648,7 @@ async function startVite(apiTarget) {
|
||||
);
|
||||
}
|
||||
|
||||
return spawnChild(
|
||||
npm,
|
||||
['--prefix', '../..', 'exec', 'vite', '--', '--config', 'vite.config.ts'],
|
||||
{ cwd: appRoot },
|
||||
);
|
||||
return spawnChild(npm, buildViteStartArguments(), { cwd: appRoot });
|
||||
}
|
||||
|
||||
async function main() {
|
||||
@@ -593,6 +668,9 @@ async function main() {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[ai-game-creator-shell] dev profile ${devProfile.name}: vite=${viteUrl} api=${apiPort} spacetime=${spacetimePort}`,
|
||||
);
|
||||
await preflightExistingVite();
|
||||
const backend = await ensureBackend({
|
||||
onBackendChild(child) {
|
||||
@@ -650,6 +728,8 @@ function isDirectModuleExecution() {
|
||||
}
|
||||
|
||||
export {
|
||||
buildBackendStartArguments,
|
||||
buildViteStartArguments,
|
||||
ensureBackend,
|
||||
formatChildFailure,
|
||||
isDirectModuleExecution,
|
||||
@@ -657,6 +737,7 @@ export {
|
||||
preflightExistingVite,
|
||||
readChildFailure,
|
||||
readLinuxProcessGroupAlive,
|
||||
resolveDevStackProfile,
|
||||
resolveBackendTargetsFromState,
|
||||
runWindowsTaskkill,
|
||||
spawnChild,
|
||||
|
||||
Reference in New Issue
Block a user