合并编辑器素材库分支
合并 codex/editor-asset-library 到 master 保留原生壳桥接统一分支已合入内容 合并图片编辑器素材库、模型定价和项目记忆更新 解决项目记忆踩坑记录冲突
This commit is contained in:
+107
-12
@@ -37,6 +37,92 @@ const manifestPath = resolve(serverRsDir, 'Cargo.toml');
|
||||
const modulePath = resolve(serverRsDir, 'crates/spacetime-module');
|
||||
const viteCliPath = resolve(repoRoot, 'scripts/vite-cli.mjs');
|
||||
const adminWebDir = resolve(repoRoot, 'apps/admin-web');
|
||||
|
||||
function resolveLocalGenarrativeFfmpeg(env = process.env, platform = process.platform) {
|
||||
if (platform !== 'win32') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const localAppData =
|
||||
String(env.LOCALAPPDATA ?? '').trim() ||
|
||||
(String(env.USERPROFILE ?? '').trim()
|
||||
? join(String(env.USERPROFILE).trim(), 'AppData', 'Local')
|
||||
: '');
|
||||
if (!localAppData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const binDir = join(localAppData, 'Genarrative', 'ffmpeg', 'bin');
|
||||
const ffmpegPath = join(binDir, 'ffmpeg.exe');
|
||||
const ffprobePath = join(binDir, 'ffprobe.exe');
|
||||
if (!existsSync(ffmpegPath) || !existsSync(ffprobePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {binDir, ffmpegPath, ffprobePath};
|
||||
}
|
||||
|
||||
function resolvePathEnvKey(env, platform = process.platform) {
|
||||
return (
|
||||
Object.keys(env).find((key) => key.toLowerCase() === 'path') ||
|
||||
(platform === 'win32' ? 'Path' : 'PATH')
|
||||
);
|
||||
}
|
||||
|
||||
function prependEnvPathEntry(env, entry, platform = process.platform) {
|
||||
const pathKey = resolvePathEnvKey(env, platform);
|
||||
const separator = platform === 'win32' ? ';' : ':';
|
||||
const currentEntries = String(env[pathKey] ?? '')
|
||||
.split(separator)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
const normalize = (value) =>
|
||||
platform === 'win32' ? value.toLowerCase() : value;
|
||||
if (currentEntries.some((value) => normalize(value) === normalize(entry))) {
|
||||
return;
|
||||
}
|
||||
|
||||
env[pathKey] = [entry, ...currentEntries].join(separator);
|
||||
}
|
||||
|
||||
function setDefaultEnvValue(env, key, value) {
|
||||
if (!String(env[key] ?? '').trim()) {
|
||||
env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function applyLocalFfmpegEnv(env, platform = process.platform) {
|
||||
const localFfmpeg = resolveLocalGenarrativeFfmpeg(env, platform);
|
||||
if (!localFfmpeg) {
|
||||
return env;
|
||||
}
|
||||
|
||||
// 本地 dev 进程常在安装 FFmpeg 前已经启动;这里用绝对路径兜底,避免只依赖旧 PATH。
|
||||
prependEnvPathEntry(env, localFfmpeg.binDir, platform);
|
||||
const ffmpegPath =
|
||||
String(env.CHARACTER_ANIMATION_FFMPEG_PATH ?? '').trim() ||
|
||||
String(env.GENARRATIVE_CHARACTER_ANIMATION_FFMPEG_PATH ?? '').trim() ||
|
||||
localFfmpeg.ffmpegPath;
|
||||
const ffprobePath =
|
||||
String(env.CHARACTER_ANIMATION_FFPROBE_PATH ?? '').trim() ||
|
||||
String(env.GENARRATIVE_CHARACTER_ANIMATION_FFPROBE_PATH ?? '').trim() ||
|
||||
localFfmpeg.ffprobePath;
|
||||
|
||||
setDefaultEnvValue(env, 'CHARACTER_ANIMATION_FFMPEG_PATH', ffmpegPath);
|
||||
setDefaultEnvValue(
|
||||
env,
|
||||
'GENARRATIVE_CHARACTER_ANIMATION_FFMPEG_PATH',
|
||||
ffmpegPath,
|
||||
);
|
||||
setDefaultEnvValue(env, 'CHARACTER_ANIMATION_FFPROBE_PATH', ffprobePath);
|
||||
setDefaultEnvValue(
|
||||
env,
|
||||
'GENARRATIVE_CHARACTER_ANIMATION_FFPROBE_PATH',
|
||||
ffprobePath,
|
||||
);
|
||||
return env;
|
||||
}
|
||||
|
||||
function resolveLocalDevRustcWrapperBypass() {
|
||||
// Windows 下不能把 rustc 自身当成 Cargo wrapper;空值会覆盖仓库 .cargo/config.toml 中的 sccache。
|
||||
return process.platform === 'win32' ? '' : '/usr/bin/env';
|
||||
@@ -2156,23 +2242,32 @@ function isSpacetimePublishPermissionError(error) {
|
||||
);
|
||||
}
|
||||
|
||||
function buildApiServerProcessEnv({baseEnv, options, state}) {
|
||||
return {
|
||||
...baseEnv,
|
||||
// 本地 dev 允许密码入口直接创建账号,生产默认仍由 api-server 配置保持关闭。
|
||||
GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED: 'true',
|
||||
GENARRATIVE_API_HOST: options.apiHost,
|
||||
GENARRATIVE_API_PORT: String(options.apiPort),
|
||||
GENARRATIVE_API_LOG: options.apiLog,
|
||||
GENARRATIVE_SPACETIME_SERVER_URL: state.spacetimeServer,
|
||||
GENARRATIVE_SPACETIME_DATABASE: options.database,
|
||||
GENARRATIVE_SPACETIME_TOKEN: baseEnv.GENARRATIVE_SPACETIME_TOKEN || '',
|
||||
};
|
||||
function buildApiServerProcessEnv({
|
||||
baseEnv,
|
||||
options,
|
||||
state,
|
||||
platform = process.platform,
|
||||
}) {
|
||||
return applyLocalFfmpegEnv(
|
||||
{
|
||||
...baseEnv,
|
||||
// 本地 dev 允许密码入口直接创建账号,生产默认仍由 api-server 配置保持关闭。
|
||||
GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED: 'true',
|
||||
GENARRATIVE_API_HOST: options.apiHost,
|
||||
GENARRATIVE_API_PORT: String(options.apiPort),
|
||||
GENARRATIVE_API_LOG: options.apiLog,
|
||||
GENARRATIVE_SPACETIME_SERVER_URL: state.spacetimeServer,
|
||||
GENARRATIVE_SPACETIME_DATABASE: options.database,
|
||||
GENARRATIVE_SPACETIME_TOKEN: baseEnv.GENARRATIVE_SPACETIME_TOKEN || '',
|
||||
},
|
||||
platform,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
assertReusableSpacetimeProcessVersionMatchesWorkspace,
|
||||
assertSpacetimeToolVersionMatchesWorkspace,
|
||||
applyLocalFfmpegEnv,
|
||||
buildApiServerProcessEnv,
|
||||
buildDevStackSnapshot,
|
||||
buildLocalRustProcessEnv,
|
||||
|
||||
+55
-1
@@ -1,4 +1,4 @@
|
||||
import {mkdtempSync, readFileSync, rmSync, writeFileSync} from 'node:fs';
|
||||
import {mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
|
||||
@@ -214,6 +214,60 @@ describe('dev scheduler api-server env', () => {
|
||||
expect(env.GENARRATIVE_API_PORT).toBe('9091');
|
||||
expect(env.GENARRATIVE_SPACETIME_SERVER_URL).toBe('http://127.0.0.1:3199');
|
||||
});
|
||||
|
||||
test('Windows 本地 dev 自动注入已安装的 FFmpeg 路径', () => {
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-ffmpeg-'));
|
||||
try {
|
||||
const binDir = join(tempDir, 'Genarrative', 'ffmpeg', 'bin');
|
||||
const ffmpegPath = join(binDir, 'ffmpeg.exe');
|
||||
const ffprobePath = join(binDir, 'ffprobe.exe');
|
||||
mkdirSync(binDir, {recursive: true});
|
||||
writeFileSync(ffmpegPath, '', 'utf8');
|
||||
writeFileSync(ffprobePath, '', 'utf8');
|
||||
|
||||
const {options} = parseArgs(['api-server'], {});
|
||||
const env = buildApiServerProcessEnv({
|
||||
baseEnv: {LOCALAPPDATA: tempDir, Path: 'C:\\Windows\\System32'},
|
||||
options,
|
||||
state: {spacetimeServer: 'http://127.0.0.1:3199'},
|
||||
platform: 'win32',
|
||||
});
|
||||
|
||||
expect(env.CHARACTER_ANIMATION_FFMPEG_PATH).toBe(ffmpegPath);
|
||||
expect(env.CHARACTER_ANIMATION_FFPROBE_PATH).toBe(ffprobePath);
|
||||
expect(env.GENARRATIVE_CHARACTER_ANIMATION_FFMPEG_PATH).toBe(ffmpegPath);
|
||||
expect(env.Path?.split(';')[0]).toBe(binDir);
|
||||
} finally {
|
||||
rmSync(tempDir, {recursive: true, force: true});
|
||||
}
|
||||
});
|
||||
|
||||
test('Windows 本地 dev 保留显式 FFmpeg 配置', () => {
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-ffmpeg-'));
|
||||
try {
|
||||
const binDir = join(tempDir, 'Genarrative', 'ffmpeg', 'bin');
|
||||
mkdirSync(binDir, {recursive: true});
|
||||
writeFileSync(join(binDir, 'ffmpeg.exe'), '', 'utf8');
|
||||
writeFileSync(join(binDir, 'ffprobe.exe'), '', 'utf8');
|
||||
|
||||
const {options} = parseArgs(['api-server'], {});
|
||||
const env = buildApiServerProcessEnv({
|
||||
baseEnv: {
|
||||
LOCALAPPDATA: tempDir,
|
||||
CHARACTER_ANIMATION_FFMPEG_PATH: 'D:\\tools\\ffmpeg.exe',
|
||||
CHARACTER_ANIMATION_FFPROBE_PATH: 'D:\\tools\\ffprobe.exe',
|
||||
},
|
||||
options,
|
||||
state: {spacetimeServer: 'http://127.0.0.1:3199'},
|
||||
platform: 'win32',
|
||||
});
|
||||
|
||||
expect(env.CHARACTER_ANIMATION_FFMPEG_PATH).toBe('D:\\tools\\ffmpeg.exe');
|
||||
expect(env.CHARACTER_ANIMATION_FFPROBE_PATH).toBe('D:\\tools\\ffprobe.exe');
|
||||
} finally {
|
||||
rmSync(tempDir, {recursive: true, force: true});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('dev scheduler Rust build env', () => {
|
||||
|
||||
Reference in New Issue
Block a user