8000 fix: CLI의 큰 JSON 출력 잘림 방지 by hmmhmmhm · Pull Request #227 · hmmhmmhm/daiso-mcp · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/quality-improvements-2026-10-05.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@
운영 확인은 배포 후 같은 상품 코드·매장·지역의 결과를 REST/MCP/공개 CLI로 확인해야 합니다. 정상 검색 결과만으로 모든 실패 경로가 정상이라는 결론을 내리지 않습니다. 편의점 공유 매분 30회·일일 3,000회 원장과 인증 세션을 보존하고, 예산이 부족하면 재시도 시각을 기다립니다.

수정 전의 잘못된 성공 응답이 Edge Cache에서 재사용되지 않도록 관련 조회 경로의 캐시 namespace도 갱신했습니다. 비교의 오래된 403 부분 결과를 캐시에 주입한 실제 앱 회귀 테스트로 새 조회가 수행되는 것을 확인했습니다.

공개 CLI의 큰 JSON 응답을 파이프로 읽을 때 `process.exit()`가 stdout 배출 전에 프로세스를 끝내는 추가 오류도 확인했습니다. 종료 코드를 설정하고 Node가 출력 완료 후 자연스럽게 종료하도록 수정했습니다. 실제 자식 프로세스의 256KiB JSON과 성공·실패 종료 코드를 검증하는 회귀 테스트를 추가했습니다.
2 changes: 1 addition & 1 deletion src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
import { runCli } from './cli.js';

runCli(process.argv.slice(2)).then((exitCode) => {
process.exit(exitCode);
process.exitCode = exitCode;
});
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export function isDirectExecution(
/* c8 ignore start */
if (isDirectExecution()) {
runCli(process.argv.slice(2)).then((exitCode) => {
process.exit(exitCode);
process.exitCode = exitCode;
});
}
/* c8 ignore stop */
48 changes: 48 additions & 0 deletions tests/app/cli-output-drain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { execFile } from 'node:child_process';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { promisify } from 'node:util';
import { expect, it } from 'vitest';

it('CLI는 큰 JSON 파이프 출력을 완전히 전달하고 반환된 종료 코드를 보존한다', async () => {
const directory = await mkdtemp(join(tmpdir(), 'daiso-cli-output-'));
try {
await writeFile(join(directory, 'package.json'), '{"type":"module"}');
await writeFile(join(directory, 'bin.js'), await readFile(new URL('../../src/bin.ts', import.meta.url), 'utf8'));
await writeFile(join(directory, 'cli.js'), `export async function runCli(args) {
console.log(JSON.stringify({ data: 'x'.repeat(256 * 1024) }));
return args.includes('--fail') ? 7 : 0;
}`);
const run = promisify(execFile);
const result = await run(process.execPath, [join(directory, 'bin.js')], { maxBuffer: 1024 * 1024 });
expect(JSON.parse(result.stdout).data).toHaveLength(256 * 1024);
try {
await run(process.execPath, [join(directory, 'bin.js'), '--fail'], { maxBuffer: 1024 * 1024 });
throw new Error('Expected nonzero CLI exit');
} catch (error) {
expect(error).toMatchObject({ code: 7 });
expect(JSON.parse((error as { stdout: string }).stdout).data).toHaveLength(256 * 1024);
}
} finally {
await rm(directory, { recursive: true, force: true });
}
});

it('npm 및 직접 실행 CLI는 실제 응답의 큰 JSON을 파이프에서 보존한다', async () => {
const directory = await mkdtemp(join(tmpdir(), 'daiso-cli-response-'));
try {
const preload = join(directory, 'fetch.mjs');
await writeFile(preload, `globalThis.fetch = async () => Response.json({success:true,data:{value:'x'.repeat(256 * 1024)}});`);
for (const entry of ['bin.ts', 'cli.ts']) {
const { stdout } = await promisify(execFile)(process.execPath, [
'--import', 'tsx', '--import', preload,
new URL('../../src/' + entry, import.meta.url).pathname,
'get', '/api/opinet/average', '--json',
], { maxBuffer: 1024 * 1024 });
expect(JSON.parse(stdout).data.value).toHaveLength(256 * 1024);
}
} finally {
await rm(directory, { recursive: true, force: true });
}
});
Loading
0