feat(开发脚本): dev:all 支持 --clear-database 并提示本地库 schema 冲突
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m18s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m19s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m55s
Project CI / Backend tests (pull_request) Successful in 9m11s
Project CI / Frontend tests (pull_request) Successful in 3m37s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m46s
Project CI / Native shell tests (pull_request) Successful in 8m35s
Project CI / Repository checks (pull_request) Successful in 7m12s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m18s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m19s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m55s
Project CI / Backend tests (pull_request) Successful in 9m11s
Project CI / Frontend tests (pull_request) Successful in 3m37s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m46s
Project CI / Native shell tests (pull_request) Successful in 8m35s
Project CI / Repository checks (pull_request) Successful in 7m12s
默认仍写死 --preserve-database(不改默认行为);显式 --clear-database 时不再传它、逐字透传 dev.mjs 既有语义;根栈因 manual migration / Reordering table 失败时补一段可操作提示(打印库名与 data dir、两条出路、清库会丢数据的警告),提示文案抽成纯函数并补单测;文档补一句
This commit is contained in:
@@ -100,7 +100,7 @@ npm run dev
|
||||
npm run dev:all
|
||||
```
|
||||
|
||||
`npm run dev:all` 保持 `npm run dev` 的主站完整栈语义不变,使用 AGC 当前配套后端的数据库 `genarrative-game-creator-dev` 与 data dir `server-rs/.spacetimedb/ai-game-creator/data` 启动一份 SpacetimeDB、BgFilter worker、api-server、主站 Vite 和后台 Vite;待这五个服务就绪后,再启动 AGC Vite 与 Tauri。AGC 通过匹配的 `.app/dev-stack.json` 复用这份后端,且设置 `AGC_DEV_ADMIN_WEB=0`,因此一键入口只保留一份管理后台。该入口不接受覆盖 database 或 SpacetimeDB data dir 的参数;需要独立数据库时分别使用 `npm run dev` / `npm run agc`。
|
||||
`npm run dev:all` 保持 `npm run dev` 的主站完整栈语义不变,使用 AGC 当前配套后端的数据库 `genarrative-game-creator-dev` 与 data dir `server-rs/.spacetimedb/ai-game-creator/data` 启动一份 SpacetimeDB、BgFilter worker、api-server、主站 Vite 和后台 Vite;待这五个服务就绪后,再启动 AGC Vite 与 Tauri。AGC 通过匹配的 `.app/dev-stack.json` 复用这份后端,且设置 `AGC_DEV_ADMIN_WEB=0`,因此一键入口只保留一份管理后台。该入口不接受覆盖 database 或 SpacetimeDB data dir 的参数;需要独立数据库时分别使用 `npm run dev` / `npm run agc`。默认会**保留**这个本地库(等价于给根栈传 `--preserve-database`),因此当库内 schema 与源码不一致(SpacetimeDB 报 `needs manual migration` / `Reordering table …`)时 publish 会被拒、根栈随之退出——此时终端会补一段可操作提示(说明是本地 schema 冲突、打印目标库与 data dir),按提示用 `npm run dev:all -- --clear-database` 清掉该本地库重建,或先跑一次不带 `--preserve-database` 的 `npm run dev -- --database genarrative-game-creator-dev --spacetime-data-dir server-rs/.spacetimedb/ai-game-creator/data`;两种做法都会丢弃该本地库里的数据(仅本地开发数据)。
|
||||
|
||||
一键入口由自身负责收束根 dev 栈与 AGC 客户端的进程树。AGC Vite marker 可访问后,终端会打印包含 SpacetimeDB、BgFilter worker、api-server、主站、后台、AGC Vite 和 Tauri 窗口的实际地址表;任一子进程异常退出都应停止另一侧并返回非零退出码。端口漂移和运行态地址仍以启动日志及 `.app/dev-stack.json` 为准。
|
||||
|
||||
|
||||
+66
-9
@@ -15,6 +15,14 @@ const agcDataDir = resolve(
|
||||
);
|
||||
const agcDataDirArgument = 'server-rs/.spacetimedb/ai-game-creator/data';
|
||||
const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json');
|
||||
// AGC 配套本地库的 schema 冲突识别:SpacetimeDB 拒绝自动迁移时会在服务端日志里留下这两类特征串
|
||||
// (`… needs manual migration` / `Reordering table <name> requires a manual migration`)。
|
||||
const localSchemaConflictPattern =
|
||||
/manual migration|Reordering table/i;
|
||||
// 唯一允许「冲突即清库」的显式开关:dev:all 默认写死 --preserve-database(不删库),
|
||||
// 只有调用方显式传 --clear-database(dev.mjs 既有语义:preserve=false → -c=on-conflict)才清库。
|
||||
const clearDatabaseOption = '--clear-database';
|
||||
const spacetimeStartLogName = 'logs/dev-spacetime-start.log';
|
||||
const rootReadyTimeoutMs = 660_000;
|
||||
const rootReadyPollMs = 500;
|
||||
const childGracefulTimeoutMs = 2_500;
|
||||
@@ -75,8 +83,47 @@ function validateDevAllArgs(argv = []) {
|
||||
}
|
||||
}
|
||||
|
||||
/// 判定根栈失败是否由「本地库 schema 与源码不一致」造成(纯函数,供单测钉住)。
|
||||
export function isLocalSchemaConflictOutput(output = '') {
|
||||
return localSchemaConflictPattern.test(String(output ?? ''));
|
||||
}
|
||||
|
||||
/// 读 SpacetimeDB 启动日志尾部(失败诊断用):文件不存在或读取失败都返回空串,绝不抛。
|
||||
function readSpacetimeStartLogTail(dataDir = agcDataDir, maxBytes = 64 * 1024) {
|
||||
const logPath = resolve(dataDir, spacetimeStartLogName);
|
||||
if (!existsSync(logPath)) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const text = readFileSync(logPath, 'utf8');
|
||||
return text.length > maxBytes ? text.slice(-maxBytes) : text;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成可操作提示(纯函数):库名与 data dir 打实际值,给出两条出路,并明确清库会丢本地数据。
|
||||
export function buildLocalSchemaConflictHint({
|
||||
database = agcDatabase,
|
||||
dataDir = agcDataDirArgument,
|
||||
} = {}) {
|
||||
return [
|
||||
'[dev:all] 本地 SpacetimeDB schema 与源码不一致:本次 publish 需要手动迁移而被拒(不是代码问题)。',
|
||||
` 目标库:${database}`,
|
||||
` 数据目录:${dataDir}`,
|
||||
' 两条出路:',
|
||||
` ① 清库重建后重跑:npm run dev:all -- ${clearDatabaseOption}`,
|
||||
` ② 只想先起根栈自愈:npm run dev -- --database ${database} --spacetime-data-dir ${dataDir}(不带 --preserve-database)`,
|
||||
' ⚠ ① 与 ② 都会丢弃该本地库里的数据(仅本地开发数据,生产库不受影响)。',
|
||||
' ⚠ 默认不加开关时 dev:all 会保留该库,冲突不会自愈——这是有意为之,避免误清库。',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildRootDevArgs(argv = []) {
|
||||
validateDevAllArgs(argv);
|
||||
// 默认继续写死 --preserve-database(不改默认,避免误清库);
|
||||
// 只有调用方显式传 --clear-database 时才交给根栈按 dev.mjs 既有语义(preserve=false → -c=on-conflict)清库。
|
||||
const wantsClearDatabase = argv.includes(clearDatabaseOption);
|
||||
return [
|
||||
'run',
|
||||
'dev',
|
||||
@@ -85,7 +132,7 @@ function buildRootDevArgs(argv = []) {
|
||||
agcDatabase,
|
||||
'--spacetime-data-dir',
|
||||
agcDataDirArgument,
|
||||
'--preserve-database',
|
||||
...(wantsClearDatabase ? [] : ['--preserve-database']),
|
||||
'--no-interactive',
|
||||
...argv,
|
||||
];
|
||||
@@ -538,14 +585,24 @@ async function runDevAll(argv = process.argv.slice(2), dependencies = {}) {
|
||||
);
|
||||
rootChild = spawnProcess(npmCommand, rootArgs, env);
|
||||
const rootExit = waitForChildExit(rootChild);
|
||||
const rootState = await waitForRoot(rootChild, {
|
||||
readState,
|
||||
expected: {
|
||||
expectedRepoRoot: repoRoot,
|
||||
expectedDatabase: agcDatabase,
|
||||
expectedDataDir: agcDataDir,
|
||||
},
|
||||
});
|
||||
let rootState;
|
||||
try {
|
||||
rootState = await waitForRoot(rootChild, {
|
||||
readState,
|
||||
expected: {
|
||||
expectedRepoRoot: repoRoot,
|
||||
expectedDatabase: agcDatabase,
|
||||
expectedDataDir: agcDataDir,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// 根栈失败原因常常只在 SpacetimeDB 启动日志里(子进程 stdio 是 inherit,dev:all 抓不到它的 stdout):
|
||||
// 命中「本地库 schema 需手动迁移」时补一段可操作提示,否则原样抛出。
|
||||
if (isLocalSchemaConflictOutput(readSpacetimeStartLogTail())) {
|
||||
console.error(buildLocalSchemaConflictHint());
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (rootChild.exitCode !== null || rootChild.signalCode !== null) {
|
||||
throw new Error('根开发栈已退出,不能启动 AGC');
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
agcDataDir,
|
||||
buildDevAllBaseEnvironment,
|
||||
buildDevAllEnvironment,
|
||||
buildLocalSchemaConflictHint,
|
||||
buildManagedSpawnOptions,
|
||||
buildRootDevArgs,
|
||||
buildStartupSummary,
|
||||
isHttpReady,
|
||||
isLocalSchemaConflictOutput,
|
||||
isSharedRootStackReady,
|
||||
renderStartupTable,
|
||||
runDevAll,
|
||||
@@ -249,4 +251,58 @@ describe('dev:all 参数和共享开发栈合同', () => {
|
||||
expect(children).toHaveLength(2);
|
||||
expect(stopCalls).toEqual([children[1], children[0]]);
|
||||
});
|
||||
|
||||
test('默认仍固定保留 AGC 本地库(不改变默认行为)', () => {
|
||||
const args = buildRootDevArgs(['--watch']);
|
||||
expect(args).toContain('--preserve-database');
|
||||
expect(args).not.toContain('--clear-database');
|
||||
});
|
||||
|
||||
test('显式 --clear-database 时不再固定 --preserve-database,开关逐字透传给根栈', () => {
|
||||
const args = buildRootDevArgs(['--clear-database']);
|
||||
expect(args).not.toContain('--preserve-database');
|
||||
expect(args.filter((argument) => argument === '--clear-database')).toHaveLength(
|
||||
1,
|
||||
);
|
||||
expect(args).toEqual([
|
||||
'run',
|
||||
'dev',
|
||||
'--',
|
||||
'--database',
|
||||
agcDatabase,
|
||||
'--spacetime-data-dir',
|
||||
'server-rs/.spacetimedb/ai-game-creator/data',
|
||||
'--no-interactive',
|
||||
'--clear-database',
|
||||
]);
|
||||
});
|
||||
|
||||
test('识别本地库 schema 冲突的服务端输出', () => {
|
||||
expect(isLocalSchemaConflictOutput('… needs manual migration')).toBe(true);
|
||||
expect(
|
||||
isLocalSchemaConflictOutput(
|
||||
'Reordering table game_distribution_game requires a manual migration',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isLocalSchemaConflictOutput(
|
||||
'Aborting publish due to required manual migration.',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(isLocalSchemaConflictOutput('Finished `dev` profile [unoptimized]')).toBe(
|
||||
false,
|
||||
);
|
||||
expect(isLocalSchemaConflictOutput(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
test('本地库 schema 冲突提示含库名/data dir/两条出路/丢数据警告', () => {
|
||||
const hint = buildLocalSchemaConflictHint();
|
||||
expect(hint).toContain(agcDatabase);
|
||||
expect(hint).toContain('server-rs/.spacetimedb/ai-game-creator/data');
|
||||
expect(hint).toContain('npm run dev:all -- --clear-database');
|
||||
expect(hint).toContain('不带 --preserve-database');
|
||||
expect(hint).toContain('都会丢弃该本地库里的数据');
|
||||
expect(hint).toContain('不是代码问题');
|
||||
expect(hint).not.toContain('undefined');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user