合并最新master分支
合入 origin/master 最新画布、外部接口与后端模块化改动。 按新队列语义解决画布测试和原生壳检查冲突。 把编辑器 Agent 迁移到模块化实现并适配 LlmRunRequest。 删除已废弃的旧编辑器 Agent 单文件与个人 Claude 本地配置。
This commit is contained in:
@@ -20,7 +20,7 @@ const repoRoot = path.resolve(
|
||||
'..',
|
||||
);
|
||||
const database = 'admin-account-smoke';
|
||||
const expectedSpacetimeVersion = '2.6.0';
|
||||
const expectedSpacetimeVersion = '2.6.1';
|
||||
const commandTimeoutMs = 5 * 60 * 1000;
|
||||
|
||||
function assert(condition, message) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1411,47 +1411,7 @@ const desktopCapabilityFlowContracts = [
|
||||
],
|
||||
},
|
||||
];
|
||||
const h5NativeAppRouteFlowContracts = [
|
||||
{
|
||||
route: '/child-motion-demo',
|
||||
label: 'child motion demo',
|
||||
files: [
|
||||
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
|
||||
'src/routing/appRoutes.tsx',
|
||||
'src/routing/appRoutes.test.ts',
|
||||
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
|
||||
],
|
||||
snippets: [
|
||||
[
|
||||
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
|
||||
"navigateHostNativePage('/child-motion-demo')",
|
||||
],
|
||||
[
|
||||
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
|
||||
"window.location.assign('/child-motion-demo')",
|
||||
],
|
||||
['src/routing/appRoutes.tsx', "normalizedPath === '/child-motion-demo'"],
|
||||
['src/routing/appRoutes.test.ts', "matchAppRoute('/child-motion-demo')"],
|
||||
[
|
||||
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
|
||||
"request.method === 'navigation.openNativePage'",
|
||||
],
|
||||
[
|
||||
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
|
||||
"url === '/child-motion-demo'",
|
||||
],
|
||||
],
|
||||
tests: [
|
||||
'src/routing/appRoutes.test.ts',
|
||||
],
|
||||
targetedTests: [
|
||||
{
|
||||
filePath: 'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
|
||||
name: 'native app opens child motion demo through host navigation bridge',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const h5NativeAppRouteFlowContracts = [];
|
||||
const expectedWechatHostBridgeFiles = [
|
||||
'dispatch.js',
|
||||
'payment.js',
|
||||
@@ -1758,8 +1718,6 @@ const h5HostBridgeTests = [
|
||||
'src/services/clipboard.test.ts',
|
||||
'src/services/appTitle.test.ts',
|
||||
];
|
||||
const h5PlatformHostBridgeIntegrationTest =
|
||||
'native app jump hop draft completion sends host notification and badge from platform shell';
|
||||
const h5NativeAppRouteFlowTestSteps = h5NativeAppRouteFlowContracts.flatMap(
|
||||
(contract) =>
|
||||
(contract.targetedTests ?? []).map((test) => ({
|
||||
@@ -1790,18 +1748,6 @@ const steps = [
|
||||
command: npmCommand,
|
||||
args: ['run', 'test', '--', ...h5HostBridgeTests],
|
||||
},
|
||||
{
|
||||
label: 'h5-platform-host-bridge-integration',
|
||||
command: npmCommand,
|
||||
args: [
|
||||
'run',
|
||||
'test',
|
||||
'--',
|
||||
'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx',
|
||||
'-t',
|
||||
h5PlatformHostBridgeIntegrationTest,
|
||||
],
|
||||
},
|
||||
...h5NativeAppRouteFlowTestSteps,
|
||||
{
|
||||
label: 'wechat-shell-tests',
|
||||
@@ -1833,6 +1779,11 @@ const steps = [
|
||||
command: npmCommand,
|
||||
args: ['run', 'mobile-shell:export'],
|
||||
},
|
||||
{
|
||||
label: 'desktop-shell-test',
|
||||
command: npmCommand,
|
||||
args: ['run', 'desktop-shell:test'],
|
||||
},
|
||||
{
|
||||
label: 'desktop-shell-typecheck',
|
||||
command: npmCommand,
|
||||
@@ -1848,11 +1799,6 @@ const steps = [
|
||||
command: npmCommand,
|
||||
args: ['run', 'ai-game-creator-shell:build', '--', '--no-bundle'],
|
||||
},
|
||||
{
|
||||
label: 'desktop-shell-test',
|
||||
command: npmCommand,
|
||||
args: ['run', 'desktop-shell:test'],
|
||||
},
|
||||
{
|
||||
label: 'desktop-shell-release-build-smoke',
|
||||
command: npmCommand,
|
||||
@@ -2586,7 +2532,6 @@ function assertH5HostBridgePayloadBoundaries() {
|
||||
'HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS',
|
||||
'HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS',
|
||||
'HOST_BRIDGE_SCANNER_TIMEOUT_MS',
|
||||
'HOST_BRIDGE_APP_TITLE_MAX_LENGTH',
|
||||
]) {
|
||||
if (!h5HostBridgeSource.includes(`${sharedBoundary},`)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import {
|
||||
chmodSync,
|
||||
copyFileSync,
|
||||
existsSync,
|
||||
symlinkSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import {
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
import net from 'node:net';
|
||||
import tls from 'node:tls';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import nodePath from 'node:path';
|
||||
import tls from 'node:tls';
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 5000;
|
||||
const DEFAULT_SPACETIME_DATABASE = 'genarrative-prod';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { accessSync, constants, readFileSync } from 'node:fs';
|
||||
import net from 'node:net';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { userInfo } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import {
|
||||
chmodSync,
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
realpathSync,
|
||||
readFileSync,
|
||||
realpathSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
symlinkSync,
|
||||
|
||||
@@ -4,8 +4,8 @@ import { spawnSync } from 'node:child_process';
|
||||
import {
|
||||
chmodSync,
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
|
||||
@@ -13,11 +13,56 @@ const checks = [
|
||||
includes: 'npm run check:rustfmt',
|
||||
reason: 'API 生产构建必须在编译前执行 Rust 格式检查。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-api-build',
|
||||
includes: 'npm run check:server-rs-ddd',
|
||||
reason: 'API 生产构建必须执行 server-rs DDD/schema/runtime-access 门禁。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-api-build',
|
||||
includes: 'cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml',
|
||||
reason: 'API 生产构建必须执行 api-server 全 target 编译检查。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
|
||||
includes: 'npm run check:rustfmt',
|
||||
reason: 'Stdb module 生产构建必须在编译前执行 Rust 格式检查。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
|
||||
includes: 'npm run check:server-rs-ddd',
|
||||
reason: 'Stdb module 生产构建必须执行 DDD/schema/runtime-access 门禁。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
|
||||
includes: 'npm run check:admin-account-procedures',
|
||||
reason: 'Stdb module 生产构建必须执行管理员 procedure smoke。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-web-build',
|
||||
includes: 'npm run check:production-ops',
|
||||
reason: 'Web 生产构建必须执行生产运维静态门禁。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-web-build',
|
||||
includes: 'npm run lint:eslint',
|
||||
reason: 'Web 生产构建必须执行 ESLint 门禁。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-web-build',
|
||||
includes: 'npm run typecheck',
|
||||
reason: 'Web 生产构建必须执行主站类型检查。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-web-build',
|
||||
includes: 'npm run admin-web:typecheck',
|
||||
reason: 'Web 生产构建必须执行后台类型检查。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-web-build',
|
||||
includes: 'npm run test',
|
||||
reason: 'Web 生产构建必须执行当前维护范围内的 Vitest。',
|
||||
},
|
||||
{
|
||||
file: 'server-rs/crates/spacetime-module/src/migration.rs',
|
||||
includes:
|
||||
@@ -514,6 +559,76 @@ const checks = [
|
||||
reason:
|
||||
'生产冷备份 service 必须用 node -- 分隔脚本参数,避免 Node 22 抢占业务 --env-file。',
|
||||
},
|
||||
{
|
||||
file: 'deploy/systemd/genarrative-database-backup.service',
|
||||
excludes: '--storage-format files',
|
||||
reason: '生产数据库备份主 service 必须继续保持 archive full 默认行为。',
|
||||
},
|
||||
{
|
||||
file: 'deploy/systemd/genarrative-database-backup-files-history.conf',
|
||||
includes: 'ExecStart=\nExecStart=/usr/bin/node -- /opt/genarrative/current/scripts/database-backup-to-oss.mjs --env-file',
|
||||
reason: 'files-history drop-in 必须先清空主 service 的 ExecStart,并使用 current release 脚本。',
|
||||
},
|
||||
{
|
||||
file: 'deploy/systemd/genarrative-database-backup-files-history.conf',
|
||||
includes: '--storage-format files --mode history --work-dir /var/lib/genarrative/database-backups/files-history',
|
||||
reason: 'files-history drop-in 必须只执行逐文件历史归档,并复用已建立基线的独立 work-dir。',
|
||||
},
|
||||
{
|
||||
file: 'deploy/systemd/genarrative-database-backup-files-history.conf',
|
||||
excludes: '--stop-service',
|
||||
reason: 'files-history 在线归档不可停止 SpacetimeDB。',
|
||||
},
|
||||
{
|
||||
file: 'deploy/systemd/genarrative-database-backup-files-history.conf',
|
||||
excludes: '--database',
|
||||
reason: 'files-history drop-in 不得写死数据库名,必须从环境文件读取。',
|
||||
},
|
||||
{
|
||||
file: 'deploy/systemd/genarrative-database-backup-files-history.conf',
|
||||
excludes: '--bucket',
|
||||
reason: 'files-history drop-in 不得写死 OSS bucket,必须从环境文件读取。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/jenkins-server-provision.sh',
|
||||
includes: 'DATABASE_BACKUP_PROFILE="${DATABASE_BACKUP_PROFILE:-archive-full}"',
|
||||
reason: 'Server-Provision 的数据库备份 profile 必须默认保持 archive-full。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/jenkins-server-provision.sh',
|
||||
includes: 'archive-full|files-history)',
|
||||
reason: 'Server-Provision 必须拒绝未知数据库备份 profile。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/jenkins-server-provision.sh',
|
||||
includes: '--storage-format files --mode history --work-dir',
|
||||
reason: 'Server-Provision 启用 files-history 前必须用真实 current release 脚本执行只读 baseline 预检。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/jenkins-server-provision.sh',
|
||||
includes: 'run_cmd rm -f "${DATABASE_BACKUP_FILES_HISTORY_DROP_IN}" "${DATABASE_BACKUP_LEGACY_DEV_DROP_IN}"',
|
||||
reason: 'Server-Provision 切回 archive-full 时必须移除托管及现场遗留的 history drop-in。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/jenkins-server-provision.sh',
|
||||
includes: 'install_file "${rendered_drop_in}" "${DATABASE_BACKUP_FILES_HISTORY_DROP_IN}" 0644',
|
||||
reason: 'Server-Provision 必须从仓库模板安装托管的 files-history drop-in。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-server-provision',
|
||||
includes: "choice(name: 'DATABASE_BACKUP_PROFILE', choices: ['archive-full', 'files-history']",
|
||||
reason: 'Server-Provision Job 必须显式暴露 archive-first 的数据库备份 profile。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-server-provision',
|
||||
includes: "string(name: 'DATABASE_BACKUP_FILES_HISTORY_WORK_DIR', defaultValue: '/var/lib/genarrative/database-backups/files-history'",
|
||||
reason: 'Server-Provision Job 必须允许 dev/release 为 files-history 选择各自的 baseline state 目录。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-server-provision',
|
||||
excludes: "params.DEPLOY_TARGET == 'release' && databaseBackupProfile == 'files-history'",
|
||||
reason: 'release 必须能在显式选择 profile 且 baseline 预检通过后启用 files-history。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/database-backup-to-oss.mjs',
|
||||
includes: 'assertSufficientWorkDirSpace({dataDir, workDir, args, env})',
|
||||
@@ -6837,6 +6952,7 @@ const checks = [
|
||||
const nodeEnvFileCommandFiles = [
|
||||
'package.json',
|
||||
'deploy/systemd/genarrative-database-backup.service',
|
||||
'deploy/systemd/genarrative-database-backup-files-history.conf',
|
||||
'scripts/deploy/production-stdb-publish.sh',
|
||||
'scripts/deploy/pingora-direct-enable.sh',
|
||||
'scripts/deploy/pingora-direct-rollback.sh',
|
||||
|
||||
@@ -37,11 +37,11 @@ chmod +x "${TARGET_BIN_DIR}/otelcol-contrib"
|
||||
|
||||
cat >"${SPACETIME_ROOT_DIR}/bin/current/spacetimedb-cli" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
echo "spacetimedb-cli 2.6.0"
|
||||
echo "spacetimedb-cli 2.6.1"
|
||||
EOF
|
||||
cat >"${SPACETIME_ROOT_DIR}/bin/current/spacetimedb-standalone" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
echo "spacetimedb-standalone 2.6.0"
|
||||
echo "spacetimedb-standalone 2.6.1"
|
||||
EOF
|
||||
chmod +x \
|
||||
"${SPACETIME_ROOT_DIR}/bin/current/spacetimedb-cli" \
|
||||
@@ -58,7 +58,7 @@ if ! (
|
||||
OTELCOL_TARGET_BIN="${TARGET_BIN_DIR}/otelcol-contrib" \
|
||||
OTELCOL_VERSION="0.151.0" \
|
||||
SPACETIME_ROOT="${SPACETIME_ROOT_DIR}" \
|
||||
SPACETIME_EXPECTED_VERSION="2.6.0" \
|
||||
SPACETIME_EXPECTED_VERSION="2.6.1" \
|
||||
"${REPO_ROOT}/scripts/prepare-server-provision-tools.sh" \
|
||||
>"${OUTPUT_LOG}" 2>&1
|
||||
); then
|
||||
|
||||
@@ -146,8 +146,11 @@ function collectMigrationTables() {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const migrationTableBody = macroMatch[1]
|
||||
.replace(/\/\*[\s\S]*?\*\//gu, ' ')
|
||||
.replace(/\/\/[^\r\n]*/gu, ' ');
|
||||
return new Set(
|
||||
[...macroMatch[1].matchAll(/\b([a-z][a-z0-9_]*)\b/gu)]
|
||||
[...migrationTableBody.matchAll(/\b([a-z][a-z0-9_]*)\b/gu)]
|
||||
.map((match) => match[1])
|
||||
.filter((name) => !['arg'].includes(name)),
|
||||
);
|
||||
|
||||
@@ -108,6 +108,12 @@ const forbiddenSnippets = [
|
||||
snippet: '.custom_world_profile()\n .iter()\n .filter(|profile| {',
|
||||
reason: 'custom_world_profile Published 同步已有 by_custom_world_profile_publication_status 索引',
|
||||
},
|
||||
{
|
||||
file: 'server-rs/crates/spacetime-module/src/editor_project_storage.rs',
|
||||
snippet: 'require_editor_generation_runtime_service_identity(ctx, ctx.sender())',
|
||||
reason:
|
||||
'procedure runtime writer 鉴权必须显式传 caller,兼容 TxContext sender 为 Identity::ZERO 的 SpacetimeDB 2.6.0',
|
||||
},
|
||||
];
|
||||
|
||||
const procedureResultFiles = [
|
||||
|
||||
@@ -481,9 +481,9 @@ function getChangedFiles(baseRef) {
|
||||
tryGit(['ls-files', '--others', '--exclude-standard', '-z', bindingsRoot]) ?? '';
|
||||
return new Set(
|
||||
[
|
||||
...diffOutput.split(/\u0000/u),
|
||||
...untrackedModuleOutput.split(/\u0000/u),
|
||||
...untrackedBindingsOutput.split(/\u0000/u),
|
||||
...diffOutput.split(String.fromCharCode(0)),
|
||||
...untrackedModuleOutput.split(String.fromCharCode(0)),
|
||||
...untrackedBindingsOutput.split(String.fromCharCode(0)),
|
||||
]
|
||||
.map(normalizePath)
|
||||
.filter(Boolean),
|
||||
@@ -575,7 +575,7 @@ function compareTables(baseTables, currentTables) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const [accessor, table] of currentTables) {
|
||||
for (const accessor of currentTables.keys()) {
|
||||
if (!baseTables.has(accessor)) {
|
||||
schemaChanged = true;
|
||||
}
|
||||
|
||||
@@ -180,10 +180,6 @@ function hasExternalPlatformMarker(line) {
|
||||
return externalPlatformPatterns.some((pattern) => pattern.test(line));
|
||||
}
|
||||
|
||||
function formatHits(hits) {
|
||||
return hits.map((hit) => ` - ${hit.file}:${hit.lineNumber} ${hit.text}`);
|
||||
}
|
||||
|
||||
const documentFiles = collectFiles(documentTargets);
|
||||
const visualNovelFiles = collectFiles(visualNovelImplementationTargets);
|
||||
|
||||
|
||||
@@ -761,7 +761,7 @@ function composeEnv() {
|
||||
}
|
||||
|
||||
function localSpacetimeImageName() {
|
||||
return `${projectName}-spacetimedb:2.6.0`;
|
||||
return `${projectName}-spacetimedb:2.6.1`;
|
||||
}
|
||||
|
||||
function spacetimeServerUrl(state) {
|
||||
|
||||
+2173
-72
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {
|
||||
chownSync,
|
||||
chmodSync,
|
||||
chownSync,
|
||||
lstatSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import {
|
||||
chownSync,
|
||||
chmodSync,
|
||||
chownSync,
|
||||
lstatSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import {
|
||||
accessSync,
|
||||
@@ -15,9 +16,8 @@ import {
|
||||
rmSync,
|
||||
statSync,
|
||||
} from 'node:fs';
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { userInfo } from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const DEFAULT_SERVICE_USER = 'genarrative';
|
||||
const DEFAULT_SERVICE_GROUP = 'genarrative';
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
import {
|
||||
callSpacetimeProcedureViaCli,
|
||||
ensureProcedureOk,
|
||||
validateSpacetimeDatabaseName,
|
||||
} from '../spacetime-migration-common.mjs';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
const PROCEDURE = 'rotate_editor_generation_runtime_service_identity_and_return';
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import {createServer} from 'node:net';
|
||||
import {hostname, userInfo} from 'node:os';
|
||||
import {dirname, resolve} from 'node:path';
|
||||
import {createServer} from 'node:net';
|
||||
|
||||
const LINUX_DEV_PORT_RANGE_REGISTRY_ROOT = '/var/tmp/genarrative-dev-port-ranges';
|
||||
const LINUX_DEV_PORT_RANGE_POOL_START = 10000;
|
||||
@@ -308,7 +308,7 @@ function nextLinuxPortRangeCandidate(registry) {
|
||||
async function withLinuxPortRangeRegistryLock(lockPath, action) {
|
||||
ensureWorldWritableDirectory(dirname(lockPath));
|
||||
|
||||
while (true) {
|
||||
for (;;) {
|
||||
try {
|
||||
writeFileSync(
|
||||
lockPath,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {createServer} from 'node:net';
|
||||
import {mkdtempSync, readFileSync, rmSync} from 'node:fs';
|
||||
import {createServer} from 'node:net';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
import {
|
||||
findAvailablePort,
|
||||
mapDevPortsToPortRange,
|
||||
@@ -23,6 +25,27 @@ function reservePort(port) {
|
||||
});
|
||||
}
|
||||
|
||||
async function reserveConsecutivePorts() {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
const firstServer = await reservePort(0);
|
||||
const firstPort = firstServer.address().port;
|
||||
|
||||
if (firstPort === 65535) {
|
||||
await new Promise((resolve) => firstServer.close(resolve));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const secondServer = await reservePort(firstPort + 1);
|
||||
return {firstServer, secondServer, firstPort};
|
||||
} catch {
|
||||
await new Promise((resolve) => firstServer.close(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('无法为测试保留两个连续端口');
|
||||
}
|
||||
|
||||
describe('dev stack port utils', () => {
|
||||
it('解析端口段并映射到四个 dev 端口', () => {
|
||||
expect(parsePortRangeSpec('10000-10099')).toEqual({
|
||||
@@ -39,9 +62,7 @@ describe('dev stack port utils', () => {
|
||||
});
|
||||
|
||||
it('使用端口可用性检查为被占用端口寻找后续可用端口', async () => {
|
||||
const firstServer = await reservePort(0);
|
||||
const firstPort = firstServer.address().port;
|
||||
const secondServer = await reservePort(firstPort + 1);
|
||||
const {firstServer, secondServer, firstPort} = await reserveConsecutivePorts();
|
||||
|
||||
try {
|
||||
const availablePort = await findAvailablePort({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user