This commit is contained in:
2026-04-22 20:14:15 +08:00
parent 0773a0d0ca
commit 0e9c286a57
205 changed files with 25790 additions and 1623 deletions

View File

@@ -1,11 +1,12 @@
import net from 'node:net';
import path from 'node:path';
import {spawn} from 'node:child_process';
import {existsSync, readFileSync} from 'node:fs';
import net from 'node:net';
import path from 'node:path';
import {fileURLToPath, pathToFileURL} from 'node:url';
const repoRoot = fileURLToPath(new URL('../', import.meta.url));
const serverRoot = fileURLToPath(new URL('../server-node/', import.meta.url));
const serverRsRoot = fileURLToPath(new URL('../server-rs/', import.meta.url));
const viteCliPath = fileURLToPath(new URL('./vite-cli.mjs', import.meta.url));
const serverTsxCliPath = fileURLToPath(
new URL('../server-node/node_modules/tsx/dist/cli.mjs', import.meta.url),
@@ -16,6 +17,8 @@ const serverTsxLoaderPath = fileURLToPath(
const serverTsxLoaderUrl = pathToFileURL(serverTsxLoaderPath).href;
const envExamplePath = fileURLToPath(new URL('../.env.example', import.meta.url));
const envLocalPath = fileURLToPath(new URL('../.env.local', import.meta.url));
const spacetimeConfigPath = fileURLToPath(new URL('../spacetime.json', import.meta.url));
const spacetimeLocalConfigPath = fileURLToPath(new URL('../spacetime.local.json', import.meta.url));
const bundledNodePath = fileURLToPath(
new URL('../.tools/node-v22.22.2-win-x64/node.exe', import.meta.url),
);
@@ -25,6 +28,11 @@ const bundledNpmCliPath = fileURLToPath(
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const DEFAULT_DEV_DATABASE_URL = 'postgresql://postgres:postgres@127.0.0.1:5432/genarrative';
const DEV_MEMORY_DATABASE_URL = 'pg-mem://genarrative-dev';
const DEFAULT_RUST_API_HOST = '127.0.0.1';
const DEFAULT_RUST_API_PORT = '3100';
const DEFAULT_SPACETIME_SERVER_URL = 'http://127.0.0.1:3001';
const DEFAULT_SPACETIME_DATABASE = 'genarrative-dev';
const DEFAULT_INTERNAL_API_SECRET = 'genarrative-dev-internal-bridge';
function parseEnvContents(contents) {
return contents
@@ -63,6 +71,18 @@ function readEnvFile(filePath) {
return parseEnvContents(readFileSync(filePath, 'utf8'));
}
function readJsonFile(filePath) {
if (!existsSync(filePath)) {
return null;
}
try {
return JSON.parse(readFileSync(filePath, 'utf8'));
} catch {
return null;
}
}
function resolveDatabaseProbeTarget(databaseUrl) {
const trimmed = databaseUrl.trim();
if (!trimmed || !/^postgres(?:ql)?:\/\//u.test(trimmed)) {
@@ -177,6 +197,8 @@ function prependEnvPath(envMap, nextEntry) {
const exampleEnv = readEnvFile(envExamplePath);
const localEnv = readEnvFile(envLocalPath);
const spacetimeConfig = readJsonFile(spacetimeConfigPath);
const spacetimeLocalConfig = readJsonFile(spacetimeLocalConfigPath);
const mergedEnv = {
...exampleEnv,
@@ -196,6 +218,22 @@ mergedEnv.PROJECT_ROOT = mergedEnv.PROJECT_ROOT || repoRoot;
mergedEnv.NODE_SERVER_ADDR = mergedEnv.NODE_SERVER_ADDR || ':8081';
mergedEnv.NODE_SERVER_TARGET =
mergedEnv.NODE_SERVER_TARGET || resolveServerTarget(mergedEnv.NODE_SERVER_ADDR);
mergedEnv.GENARRATIVE_API_HOST =
mergedEnv.GENARRATIVE_API_HOST || DEFAULT_RUST_API_HOST;
mergedEnv.GENARRATIVE_API_PORT =
mergedEnv.GENARRATIVE_API_PORT || DEFAULT_RUST_API_PORT;
mergedEnv.GENARRATIVE_API_TARGET =
mergedEnv.GENARRATIVE_API_TARGET ||
`http://${mergedEnv.GENARRATIVE_API_HOST}:${mergedEnv.GENARRATIVE_API_PORT}`;
mergedEnv.GENARRATIVE_INTERNAL_API_SECRET =
mergedEnv.GENARRATIVE_INTERNAL_API_SECRET || DEFAULT_INTERNAL_API_SECRET;
mergedEnv.GENARRATIVE_SPACETIME_SERVER_URL =
mergedEnv.GENARRATIVE_SPACETIME_SERVER_URL || DEFAULT_SPACETIME_SERVER_URL;
mergedEnv.GENARRATIVE_SPACETIME_DATABASE =
mergedEnv.GENARRATIVE_SPACETIME_DATABASE ||
spacetimeLocalConfig?.database ||
spacetimeConfig?.database ||
DEFAULT_SPACETIME_DATABASE;
mergedEnv.DATABASE_URL =
mergedEnv.DATABASE_URL || DEFAULT_DEV_DATABASE_URL;
mergedEnv.VITE_DEV_HOST = mergedEnv.VITE_DEV_HOST || '127.0.0.1';
@@ -205,9 +243,23 @@ mergedEnv.npm_config_scripts_prepend_node_path = 'true';
const exampleDatabaseUrl = `${exampleEnv.DATABASE_URL || ''}`.trim();
const localDatabaseUrl = `${localEnv.DATABASE_URL || ''}`.trim();
const processDatabaseUrl = `${process.env.DATABASE_URL || ''}`.trim();
const exampleSpacetimeDatabase = `${exampleEnv.GENARRATIVE_SPACETIME_DATABASE || ''}`.trim();
const localSpacetimeDatabase = `${localEnv.GENARRATIVE_SPACETIME_DATABASE || ''}`.trim();
const processSpacetimeDatabase = `${process.env.GENARRATIVE_SPACETIME_DATABASE || ''}`.trim();
const hasExplicitDatabaseUrl =
Boolean(processDatabaseUrl) ||
(Boolean(localDatabaseUrl) && localDatabaseUrl !== exampleDatabaseUrl);
const hasExplicitSpacetimeDatabase =
Boolean(processSpacetimeDatabase) ||
(Boolean(localSpacetimeDatabase) && localSpacetimeDatabase !== exampleSpacetimeDatabase);
// 本地开发默认跟随仓库当前的 Spacetime 数据库名,只有显式覆盖时才尊重环境变量。
if (!hasExplicitSpacetimeDatabase) {
mergedEnv.GENARRATIVE_SPACETIME_DATABASE =
spacetimeLocalConfig?.database ||
spacetimeConfig?.database ||
DEFAULT_SPACETIME_DATABASE;
}
if (!hasExplicitDatabaseUrl) {
const databaseProbeTarget = resolveDatabaseProbeTarget(mergedEnv.DATABASE_URL);
@@ -228,6 +280,14 @@ if (!hasExplicitDatabaseUrl) {
console.log(`[dev:node] PROJECT_ROOT=${mergedEnv.PROJECT_ROOT}`);
console.log(`[dev:node] NODE_SERVER_ADDR=${mergedEnv.NODE_SERVER_ADDR}`);
console.log(`[dev:node] NODE_SERVER_TARGET=${mergedEnv.NODE_SERVER_TARGET}`);
console.log(`[dev:node] GENARRATIVE_API_TARGET=${mergedEnv.GENARRATIVE_API_TARGET}`);
console.log('[dev:node] GENARRATIVE_INTERNAL_API_SECRET=[configured]');
console.log(
`[dev:node] GENARRATIVE_SPACETIME_SERVER_URL=${mergedEnv.GENARRATIVE_SPACETIME_SERVER_URL}`,
);
console.log(
`[dev:node] GENARRATIVE_SPACETIME_DATABASE=${mergedEnv.GENARRATIVE_SPACETIME_DATABASE}`,
);
console.log(`[dev:node] DATABASE_URL=${redactDatabaseUrl(mergedEnv.DATABASE_URL)}`);
console.log(`[dev:node] VITE_DEV_HOST=${mergedEnv.VITE_DEV_HOST}`);
console.log(`[dev:node] NODE_RUNTIME=${runtimeNodePath}`);
@@ -329,6 +389,38 @@ const serverProcess = existsSync(serverTsxLoaderPath)
stdio: 'inherit',
});
const rustApiProcess = process.platform === 'win32'
? spawn(
'powershell',
[
'-NoProfile',
'-ExecutionPolicy',
'Bypass',
'-File',
path.join(serverRsRoot, 'scripts', 'dev.ps1'),
'-ApiHost',
mergedEnv.GENARRATIVE_API_HOST,
'-Port',
mergedEnv.GENARRATIVE_API_PORT,
],
{
cwd: repoRoot,
env: mergedEnv,
stdio: 'inherit',
},
)
: spawn(
'bash',
[
path.join(serverRsRoot, 'scripts', 'dev.sh'),
],
{
cwd: repoRoot,
env: mergedEnv,
stdio: 'inherit',
},
);
const viteProcess = spawn(
runtimeNodePath,
[viteCliPath, '--port=3000', `--host=${mergedEnv.VITE_DEV_HOST}`],
@@ -340,6 +432,7 @@ const viteProcess = spawn(
);
registerChild('node server', serverProcess, () => viteProcess);
registerChild('rust api-server', rustApiProcess, () => viteProcess);
registerChild('vite dev server', viteProcess, () => serverProcess);
process.on('SIGINT', () => {