Compare commits

..

2 Commits

Author SHA1 Message Date
Claude Owner
204b813ecc build: make npm run build self-sufficient
The ts-cleaner step in the build script scans dist/ and crashes with
ENOENT if the directory doesn't exist (e.g. on a fresh clone or after
git clean). Previously README told users to 'mkdir -p dist' first,
but Dockerfile and CI didn't necessarily follow that. Prepend a small
node one-liner that mkdir's dist recursively before ts-cleaner runs,
and drop the now-redundant manual step from README.
2026-05-26 14:48:30 +09:00
Claude Owner
499852b2a7 fix(tts): avoid char-by-char split when signature list is empty
SignatureClient.regex returned new RegExp("()", "g") when nameSet
was empty (server unreachable, list not yet synced). text.split() with
that regex matches every zero-width position, so 'abc' became
['a','','b','','c',''] and TTSClient sent a separate Chzzk request
per character.

Two-layer fix:
- SignatureClient.regex returns /$^/g (never-match) when names is
  empty, so split() returns the original string as a single element.
- TTSClient.getSource explicitly skips split when nameSet.size === 0
  so the intent is obvious at the call site.
2026-05-26 14:48:24 +09:00
4 changed files with 13 additions and 5 deletions

View File

@@ -85,8 +85,7 @@ npm install
# 개발 (ts-node) # 개발 (ts-node)
npm run dev npm run dev
# 빌드 후 실행 (ts-cleaner가 dist/를 스캔하므로 먼저 만들어야 함) # 빌드 후 실행 (build 스크립트가 dist 디렉터리를 자동 생성)
mkdir -p dist
npm run build npm run build
npm start npm start

View File

@@ -15,7 +15,7 @@
"type": "commonjs", "type": "commonjs",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"build": "ts-cleaner && tsc", "build": "node -e \"require('fs').mkdirSync('dist',{recursive:true})\" && ts-cleaner && tsc",
"start": "node .", "start": "node .",
"dev": "ts-node src/index.ts", "dev": "ts-node src/index.ts",
"prod": "ts-node src/utils/Prod-commands.ts", "prod": "ts-node src/utils/Prod-commands.ts",

View File

@@ -35,7 +35,12 @@ export class SignatureClient {
} }
get regex() { get regex() {
const escaped = [...this.nameSet].map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); const names = [...this.nameSet];
// 빈 목록이면 new RegExp("()", "g")가 모든 위치에서 매칭돼
// text.split() 결과가 글자 단위로 쪼개진다. 절대 매칭되지 않는
// 패턴을 돌려줘 split 호출자가 원문을 그대로 받게 한다.
if (names.length === 0) return /$^/g;
const escaped = names.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
return new RegExp(`(${escaped.join("|")})`, "g"); return new RegExp(`(${escaped.join("|")})`, "g");
} }

View File

@@ -92,7 +92,11 @@ export class TTSClient {
private async getSource(text: string): Promise<Buffer | null> { private async getSource(text: string): Promise<Buffer | null> {
const parts = text.split(signature.regex); // 시그니처 목록이 비어 있으면 split을 건너뛰고 원문을 한 번에 합성한다.
// (SignatureClient.regex가 빈 목록일 때 never-match 패턴을 돌려주지만,
// 소비자 측에서도 명시적으로 가드해 의도를 분명히 한다.)
const names = signature.nameSet;
const parts = names.size === 0 ? [text] : text.split(signature.regex);
const bufferList: Buffer[] = []; const bufferList: Buffer[] = [];
for (const part of parts) { for (const part of parts) {