1
This commit is contained in:
@@ -8,9 +8,18 @@ const apiServerExePath = resolve(
|
||||
repoRoot,
|
||||
'server-rs/target/debug/api-server.exe',
|
||||
);
|
||||
const shellEnvKeys = new Set(Object.keys(process.env));
|
||||
const LOCAL_ENV_FILES = ['.env', '.env.local', '.env.secrets.local'];
|
||||
|
||||
function buildProtectedEnvKeys(baseEnv) {
|
||||
return new Set(
|
||||
Object.entries(baseEnv)
|
||||
.filter(([, value]) => String(value ?? '').trim())
|
||||
.map(([key]) => key),
|
||||
);
|
||||
}
|
||||
|
||||
const shellEnvKeys = buildProtectedEnvKeys(process.env);
|
||||
|
||||
function loadEnvFile(path, target, protectedKeys = shellEnvKeys) {
|
||||
if (!existsSync(path)) {
|
||||
return;
|
||||
@@ -54,7 +63,7 @@ export function loadApiServerEnv(
|
||||
|
||||
export function mergeApiServerEnv(repoRootPath, baseEnv = process.env) {
|
||||
const mergedEnv = { ...baseEnv };
|
||||
loadApiServerEnv(repoRootPath, mergedEnv, new Set(Object.keys(baseEnv)));
|
||||
loadApiServerEnv(repoRootPath, mergedEnv, buildProtectedEnvKeys(baseEnv));
|
||||
return mergedEnv;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,4 +63,32 @@ describe('api-server-dev env merge', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('空外层 shell 变量不会遮蔽本地私密配置', () => {
|
||||
withTempEnvFiles(
|
||||
{
|
||||
'.env.local': [
|
||||
'ALIYUN_OSS_BUCKET=dev-bucket',
|
||||
'ALIYUN_OSS_ENDPOINT=oss-cn-shanghai.aliyuncs.com',
|
||||
].join('\n'),
|
||||
'.env.secrets.local': [
|
||||
'ALIYUN_OSS_ACCESS_KEY_ID=local-access-key',
|
||||
'ALIYUN_OSS_ACCESS_KEY_SECRET=local-access-secret',
|
||||
].join('\n'),
|
||||
},
|
||||
(_env, tempDir) => {
|
||||
const env = mergeApiServerEnv(tempDir, {
|
||||
ALIYUN_OSS_BUCKET: '',
|
||||
ALIYUN_OSS_ENDPOINT: ' ',
|
||||
ALIYUN_OSS_ACCESS_KEY_ID: 'shell-access-key',
|
||||
ALIYUN_OSS_ACCESS_KEY_SECRET: '',
|
||||
});
|
||||
|
||||
expect(env.ALIYUN_OSS_BUCKET).toBe('dev-bucket');
|
||||
expect(env.ALIYUN_OSS_ENDPOINT).toBe('oss-cn-shanghai.aliyuncs.com');
|
||||
expect(env.ALIYUN_OSS_ACCESS_KEY_ID).toBe('shell-access-key');
|
||||
expect(env.ALIYUN_OSS_ACCESS_KEY_SECRET).toBe('local-access-secret');
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
51
scripts/check-api-server-env.mjs
Normal file
51
scripts/check-api-server-env.mjs
Normal file
@@ -0,0 +1,51 @@
|
||||
import { mergeApiServerEnv } from './api-server-dev.mjs';
|
||||
|
||||
const REQUIRED_FOR_PUZZLE_GENERATION = [
|
||||
'VECTOR_ENGINE_BASE_URL',
|
||||
'VECTOR_ENGINE_API_KEY',
|
||||
'ALIYUN_OSS_BUCKET',
|
||||
'ALIYUN_OSS_ENDPOINT',
|
||||
'ALIYUN_OSS_ACCESS_KEY_ID',
|
||||
'ALIYUN_OSS_ACCESS_KEY_SECRET',
|
||||
];
|
||||
|
||||
const COMMON_MISTYPED_KEYS = [
|
||||
'ALIYUN_0SS_BUCKET',
|
||||
'ALIYUN_0SS_ENDPOINT',
|
||||
'ALIYUN_0SS_ACCESS_KEY_ID',
|
||||
'ALIYUN_0SS_ACCESS_KEY_SECRET',
|
||||
];
|
||||
|
||||
function hasValue(value) {
|
||||
return typeof value === 'string' && value.trim().length > 0;
|
||||
}
|
||||
|
||||
function printStatus(key, present) {
|
||||
console.log(`${key}: ${present ? 'present' : 'missing'}`);
|
||||
}
|
||||
|
||||
const env = mergeApiServerEnv(process.cwd(), process.env);
|
||||
const missing = [];
|
||||
|
||||
console.log('[api-server-env] 拼图真实生成配置检查');
|
||||
for (const key of REQUIRED_FOR_PUZZLE_GENERATION) {
|
||||
const present = hasValue(env[key]);
|
||||
printStatus(key, present);
|
||||
if (!present) {
|
||||
missing.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const typoKeys = COMMON_MISTYPED_KEYS.filter((key) => hasValue(env[key]));
|
||||
if (typoKeys.length > 0) {
|
||||
console.log(
|
||||
`[api-server-env] 检测到疑似拼错的 OSS 键名:${typoKeys.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.error(`[api-server-env] 缺少:${missing.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('[api-server-env] 配置齐全。重启 npm run api-server 或 npm run dev 后生效。');
|
||||
@@ -63,7 +63,11 @@ load_api_server_env_files() {
|
||||
done < <(
|
||||
node - "${env_files[@]}" <<'NODE'
|
||||
const fs = require('fs');
|
||||
const shellEnvKeys = new Set(Object.keys(process.env));
|
||||
const shellEnvKeys = new Set(
|
||||
Object.entries(process.env)
|
||||
.filter(([, value]) => String(value ?? '').trim())
|
||||
.map(([key]) => key),
|
||||
);
|
||||
const values = new Map();
|
||||
|
||||
for (const filePath of process.argv.slice(2)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {spawn} from 'node:child_process';
|
||||
import {existsSync, readFileSync} from 'node:fs';
|
||||
import {resolve} from 'node:path';
|
||||
|
||||
import {
|
||||
findAvailablePort,
|
||||
formatPortDecision,
|
||||
@@ -8,7 +9,11 @@ import {
|
||||
} from './dev-stack-port-utils.mjs';
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const shellEnvKeys = new Set(Object.keys(process.env));
|
||||
const shellEnvKeys = new Set(
|
||||
Object.entries(process.env)
|
||||
.filter(([, value]) => String(value ?? '').trim())
|
||||
.map(([key]) => key),
|
||||
);
|
||||
|
||||
function loadEnvFile(path, target) {
|
||||
if (!existsSync(path)) {
|
||||
|
||||
@@ -25,7 +25,7 @@ const styleTemplates = [
|
||||
id: 'pixel-retro',
|
||||
title: '像素复古',
|
||||
prompt:
|
||||
'复古像素游戏道具素材风格,有限色板,清晰像素边缘,主体轮廓稳定,像 32-bit 休闲游戏图标。',
|
||||
'真正复古像素游戏道具 sprite 风格,先以约 64x64 低分辨率像素块绘制再按整数倍放大,硬边方块像素清晰可见,有限色板 12-24 色,主体轮廓稳定,禁止抗锯齿、柔焦、平滑渐变、真实 3D 渲染、PBR 材质和摄影光照。',
|
||||
},
|
||||
{
|
||||
id: 'watercolor',
|
||||
@@ -117,14 +117,15 @@ function buildVectorEngineImagesGenerationUrl(baseUrl) {
|
||||
: `${baseUrl}/v1/images/generations`;
|
||||
}
|
||||
|
||||
// 中文注释:入口缩略图只用于比较画风,必须展示单个代表道具,避免误导为一组待切割物品。
|
||||
function buildPrompt(template) {
|
||||
return [
|
||||
'请生成一张 1:1 方形抓大鹅入口 2D 素材风格参考图。',
|
||||
'画面是一组 5 个小型游戏道具样张,题材统一为水果、甜点、玩具和宝石的混合展示。',
|
||||
'画面只允许出现 1 个完整独立的游戏道具主体,题材固定为一颗红苹果,不要出现第二个物品。',
|
||||
`整体风格:${template.prompt}`,
|
||||
'要求:每个道具都是独立 2D 素材示例,主体集中,轮廓清晰,适合被切成抓大鹅局内物品素材。',
|
||||
'构图:浅色干净背景,散点排列,留有呼吸感,不要九宫格边框,不要 UI 面板,不要按钮。',
|
||||
'避免:文字、水印、logo、教程标注、真实照片、复杂场景、人物、动物、3D 模型视口、明显透视地面、厚重阴影。',
|
||||
'要求:这个道具是独立 2D 素材示例,主体集中,轮廓清晰,适合作为抓大鹅局内物品素材。',
|
||||
'构图:浅色干净背景,单物体居中放大,四周留少量呼吸感,不要九宫格边框,不要 UI 面板,不要按钮。',
|
||||
'避免:多个物品、5 个物品、物品组合、重复视角、散点排列、文字、水印、logo、教程标注、真实照片、复杂场景、人物、动物、3D 模型视口、明显透视地面、厚重阴影。',
|
||||
].join('');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user