- 新增 script/playable_soak.sh:先做 soak 配置/资源校验,无 --allow-gameplay 只校验不启动客户端;N(>=10) 次独立正常退出运行 + 墙钟 soak,失败即停止后续 运行并记 BLOCKED;写本批次 release-manifest.json 后聚合 - run_client_gate.sh:确认的故障代理对所有 suite 启动(共享场景的退出运行也经代理); soak 超时下限只约束 soak 客户端(validate_soak 增加 soak_client 参数) - 新增本地 127.0.0.1 故障代理、RSS 采样/内存判定、窗口/指标/流程模块及其测试 - forest_mob_render_test 输出 PASS/FAIL 标记,供 rendering_batch_test.sh 识别 - 新增 docs/FIRST-MAC-PLAYABLE-STATUS.md:如实记录 PASS/BLOCKED、已知问题与环境需求 - .gitignore 排除本地场景配置、凭据文件与运行输出 离线回归:rendering_batch_test.sh failures=0,playable_gate_test.sh PASS, node 夹具测试 PASS。未联网运行,未重建候选包。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
64 lines
2.1 KiB
JavaScript
Executable File
64 lines
2.1 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/* Copy stdin to stdout line by line, replacing the test credentials before any
|
|
* byte reaches disk. Secrets come only from MT_ACCOUNT / MT_PASSWORD in the
|
|
* environment and are never printed, logged or passed as arguments.
|
|
*
|
|
* node script/redact_stream.mjs <fifo >client.log
|
|
*
|
|
* Uses blocking reads on fd 0: async stdin on a macOS FIFO does not reliably
|
|
* report EOF, which would leave the runner waiting for the redactor. */
|
|
import fs from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const MIN_LENGTH = 2;
|
|
|
|
export function secretLiterals(env = process.env) {
|
|
return ['MT_PASSWORD', 'MT_ACCOUNT']
|
|
.map((name) => env[name] || '')
|
|
.filter((value) => value.length >= MIN_LENGTH)
|
|
// Longest first so an account contained in a password is not half-replaced.
|
|
.sort((a, b) => b.length - a.length);
|
|
}
|
|
|
|
export function redact(line, literals = secretLiterals()) {
|
|
let out = line;
|
|
for (const literal of literals) out = out.split(literal).join('[redacted]');
|
|
return out;
|
|
}
|
|
|
|
function pump() {
|
|
const literals = secretLiterals();
|
|
const buffer = Buffer.alloc(64 * 1024);
|
|
let pending = '';
|
|
const write = (text) => {
|
|
const bytes = Buffer.from(text);
|
|
let offset = 0;
|
|
while (offset < bytes.length) offset += fs.writeSync(1, bytes, offset);
|
|
};
|
|
for (;;) {
|
|
let count;
|
|
try {
|
|
count = fs.readSync(0, buffer, 0, buffer.length, null);
|
|
} catch (error) {
|
|
if (error.code === 'EAGAIN') continue;
|
|
if (error.code === 'EOF') break;
|
|
throw error;
|
|
}
|
|
if (count === 0) break;
|
|
pending += buffer.toString('utf8', 0, count);
|
|
const lines = pending.split('\n');
|
|
pending = lines.pop();
|
|
if (lines.length > 0) write(lines.map((line) => `${redact(line, literals)}\n`).join(''));
|
|
}
|
|
// A trailing partial line is still redacted as a whole.
|
|
if (pending !== '') write(`${redact(pending, literals)}\n`);
|
|
}
|
|
|
|
if (process.argv[1] && fileURLToPath(import.meta.url) === fs.realpathSync(process.argv[1])) {
|
|
if (process.argv.includes('--help')) {
|
|
console.log('usage: node script/redact_stream.mjs < INPUT > OUTPUT (reads MT_ACCOUNT/MT_PASSWORD from env)');
|
|
} else {
|
|
pump();
|
|
}
|
|
}
|