Deploy Error Analysis
에러 개요
섹션 제목: “에러 개요”GitHub Actions 배포 워크플로우 실행 중 TypeScript 컴파일 에러와 Queue 리소스 부재 에러가 발생했습니다.
📋 에러 분류
섹션 제목: “📋 에러 분류”1. TypeScript 컴파일 에러 (빌드 단계)
섹션 제목: “1. TypeScript 컴파일 에러 (빌드 단계)”에러 1.1: Bindings 타입 불일치
섹션 제목: “에러 1.1: Bindings 타입 불일치”src/apps/api/index.ts(175,40): error TS2345:Argument of type 'import(".../src/apps/api/index").Bindings'is not assignable to parameter of type 'import(".../src/index").Bindings'.Type 'Bindings' is missing the following properties from type 'Bindings':SEED_QUEUE, DOMAIN_QUEUE위치: src/apps/api/index.ts:175, src/apps/api/index.ts:179
원인:
src/apps/api/index.ts에 정의된Bindings타입에SEED_QUEUE,DOMAIN_QUEUE가 없음src/index.ts의Bindings에는 있지만,src/apps/api/index.ts에는 없음- 두 파일에서 서로 다른
Bindings타입을 정의하여 타입 불일치 발생
현재 코드:
export type Bindings = { // ... RESEARCH_QUEUE: Queue; CONTRACT_QUEUE: Queue; LIVENESS_QUEUE: Queue; // ❌ SEED_QUEUE, DOMAIN_QUEUE 없음};
// src/index.tsexport type Bindings = { // ... RESEARCH_QUEUE: Queue; CONTRACT_QUEUE: Queue; LIVENESS_QUEUE: Queue; SEED_QUEUE: Queue; // ✅ 있음 DOMAIN_QUEUE: Queue; // ✅ 있음};에러 1.2: seed-queue-handlers.ts 타입 에러들
섹션 제목: “에러 1.2: seed-queue-handlers.ts 타입 에러들”src/lib/queue/seed-queue-handlers.ts(182,11): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(182,48): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(183,31): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(186,27): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(188,50): error TS2345: Argument of type '...' is not assignable to parameter of type '{ domain_id: string; normalized_domain: string; raw_url: string; }'. Type 'undefined' is not assignable to type '{ domain_id: string; normalized_domain: string; raw_url: string; }'.src/lib/queue/seed-queue-handlers.ts(191,27): error TS1361: 'NormalizedDomain' cannot be used as a value because it was imported using 'import type'.src/lib/queue/seed-queue-handlers.ts(194,24): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(197,40): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(203,38): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(215,14): error TS18048: 'record' is possibly 'undefined'.src/lib/queue/seed-queue-handlers.ts(229,3): error TS6133: 'country' is declared but its value is never read.src/lib/queue/seed-queue-handlers.ts(233,23): error TS2532: Object is possibly 'undefined'.원인 분석:
recordpossibly undefined: 배열 인덱스 접근 시 타입 가드 없음NormalizedDomainimport type:import type으로 import했지만 값으로 사용countryunused: 함수 파라미터로 선언했지만 사용하지 않음- Object possibly undefined: 옵셔널 체이닝 누락
에러 1.3: seed-orchestrator.ts 미사용 변수
섹션 제목: “에러 1.3: seed-orchestrator.ts 미사용 변수”src/routes/seed-orchestrator.ts(134,3): error TS6133:'force' is declared but its value is never read.원인: force 파라미터를 선언했지만 실제로 사용하지 않음
에러 1.4: SourceInfo 중복 정의
섹션 제목: “에러 1.4: SourceInfo 중복 정의”src/schemas/index.ts(70,3): error TS2300: Duplicate identifier 'SourceInfo'.src/schemas/index.ts(176,3): error TS2300: Duplicate identifier 'SourceInfo'.원인:
src/schemas/seed.ts에서SourceInfoexportsrc/schemas/seed-engine.ts에서도SourceInfoexportsrc/schemas/index.ts에서 둘 다 re-export하여 중복 발생
에러 1.5: seed-engine.ts 미사용 import
섹션 제목: “에러 1.5: seed-engine.ts 미사용 import”src/schemas/seed-engine.ts(9,1): error TS6192:All imports in import declaration are unused.원인: CountryCode, ContentCategory를 import했지만 사용하지 않음
2. Queue 리소스 부재 에러 (배포 단계)
섹션 제목: “2. Queue 리소스 부재 에러 (배포 단계)”에러 2.1: Queue가 존재하지 않음
섹션 제목: “에러 2.1: Queue가 존재하지 않음”✘ [ERROR] Queue "newsfork-seed-staging" does not exist.To create it, run: wrangler queues create newsfork-seed-staging위치: 배포 단계 (wrangler deploy --env staging --minify)
원인:
provision_queues함수가 기존 Queue들만 생성:newsfork-research-${env}newsfork-contract-${env}newsfork-liveness-${env}newsfork-dlq-${env}
- 새로운 Seed Engine Queue들이 누락:
newsfork-seed-${env}❌newsfork-domain-${env}❌newsfork-seed-dlq-${env}❌newsfork-domain-dlq-${env}❌
현재 코드 (cloudflare-resources.sh):
provision_queues() { local env="$1"
# 기존 Queue만 생성 create_queue_with_auth "newsfork-research-${env}" create_queue_with_auth "newsfork-contract-${env}" create_queue_with_auth "newsfork-liveness-${env}" create_queue_with_auth "newsfork-dlq-${env}"
# ❌ 새로운 Queue들 누락}🔧 해결 방안
섹션 제목: “🔧 해결 방안”방안 1: Bindings 타입 통일 (Critical)
섹션 제목: “방안 1: Bindings 타입 통일 (Critical)”문제: 두 파일에서 서로 다른 Bindings 타입 정의
해결:
src/apps/api/index.ts의Bindings타입 제거src/index.ts의Bindings타입을 import하여 사용
수정 내용:
// Beforeexport type Bindings = { // ... RESEARCH_QUEUE: Queue; CONTRACT_QUEUE: Queue; LIVENESS_QUEUE: Queue;};
// Afterimport type { Bindings } from '../../index'; // ✅ 중앙 정의 사용// export type Bindings 제거방안 2: seed-queue-handlers.ts 타입 에러 수정
섹션 제목: “방안 2: seed-queue-handlers.ts 타입 에러 수정”2.1 record possibly undefined 해결
섹션 제목: “2.1 record possibly undefined 해결”문제: 배열 인덱스 접근 시 타입 가드 없음
해결:
// Beforefor (let i = 0; i < data.records.length; i++) { const record = data.records[i]; // ❌ possibly undefined // ...}
// Afterfor (const record of data.records) { // ✅ for...of 사용 if (!record) continue; // 타입 가드 // ...}2.2 NormalizedDomain import 수정
섹션 제목: “2.2 NormalizedDomain import 수정”문제: import type으로 import했지만 값으로 사용
해결:
// Beforeimport type { NormalizedDomain } from '../../schemas/domain';// ...const validated = NormalizedDomain.safeParse(normalized); // ❌ type을 값으로 사용
// Afterimport { NormalizedDomain } from '../../schemas/domain'; // ✅ type 제거2.3 country 파라미터 제거 또는 사용
섹션 제목: “2.3 country 파라미터 제거 또는 사용”문제: 선언했지만 사용하지 않음
해결:
// Beforefunction reconstructNormalizedDomain( record: { domain_id: string; normalized_domain: string; raw_url: string }, country: string // ❌ 사용하지 않음): NormalizedDomain { // ...}
// Afterfunction reconstructNormalizedDomain( record: { domain_id: string; normalized_domain: string; raw_url: string } // ✅ country 파라미터 제거 (이미 record.domain_id에 country 정보 포함)): NormalizedDomain { // ...}2.4 옵셔널 체이닝 추가
섹션 제목: “2.4 옵셔널 체이닝 추가”문제: 객체가 possibly undefined
해결:
// Beforeconst urlParts = record.domain_id.split(':');const authority = urlParts[0] as Authority; // ❌ urlParts[0] possibly undefined
// Afterconst urlParts = record.domain_id.split(':');if (urlParts.length < 3) { throw new Error(`Invalid domain_id format: ${record.domain_id}`);}const authority = urlParts[0] as Authority; // ✅ 타입 가드 추가방안 3: seed-orchestrator.ts 미사용 변수 제거
섹션 제목: “방안 3: seed-orchestrator.ts 미사용 변수 제거”문제: force 파라미터 선언했지만 사용하지 않음
해결:
// Beforeasync function listRawFiles( bucket: R2Bucket, prefix: string, force: boolean // ❌ 사용하지 않음): Promise<R2Object[]> { // ...}
// Afterasync function listRawFiles( bucket: R2Bucket, prefix: string // ✅ force 파라미터 제거 (또는 실제로 사용)): Promise<R2Object[]> { // ...}또는 force를 실제로 사용:
// force가 true면 .success 파일 체크 스킵if (!request.force) { const successPath = `${file.key}.success`; const exists = await c.env.DATASETS_BUCKET.head(successPath); if (exists) { skippedCount++; continue; }}방안 4: SourceInfo 중복 정의 해결
섹션 제목: “방안 4: SourceInfo 중복 정의 해결”문제: 여러 파일에서 SourceInfo를 export하여 중복
해결 방안 4-1: 이름 변경 (권장)
seed-engine.ts의 SourceInfo를 다른 이름으로 변경:
// Beforeexport const SourceInfoSchema = z.object({ ... });export type SourceInfo = z.infer<typeof SourceInfoSchema>;
// Afterexport const DomainSourceInfoSchema = z.object({ ... }); // ✅ 이름 변경export type DomainSourceInfo = z.infer<typeof DomainSourceInfoSchema>;
// DomainMetadataSchema에서도 수정export const DomainMetadataSchema = z.object({ // ... source: DomainSourceInfoSchema.optional() // ✅ 이름 변경});해결 방안 4-2: 기존 SourceInfo 재사용
seed-engine.ts에서 기존 SourceInfo를 import하여 사용:
// Beforeexport const SourceInfoSchema = z.object({ ... });
// Afterimport { SourceInfo as SeedSourceInfo } from './seed'; // ✅ 기존 것 사용// 또는 별도 타입으로 확장export const DomainSourceInfoSchema = SeedSourceInfo.extend({ raw_file_path: z.string(), record_index: z.number().int().optional()});권장: 방안 4-1 (이름 변경) - 명확한 구분
방안 5: seed-engine.ts 미사용 import 제거
섹션 제목: “방안 5: seed-engine.ts 미사용 import 제거”문제: CountryCode, ContentCategory import했지만 사용하지 않음
해결:
// Beforeimport { DomainId, DomainCountryCode } from './domain';import { CountryCode, ContentCategory } from './common'; // ❌ 사용하지 않음
// Afterimport { DomainId, DomainCountryCode } from './domain';// ✅ CountryCode, ContentCategory import 제거방안 6: Queue 생성 스크립트 업데이트 (Critical)
섹션 제목: “방안 6: Queue 생성 스크립트 업데이트 (Critical)”문제: provision_queues 함수가 새로운 Queue들을 생성하지 않음
해결:
provision_queues() { local env="$1"
echo "📬 Provisioning Queues for ${env} environment..." >&2
# ... (기존 코드)
# 기존 Queue들 create_queue_with_auth "newsfork-research-${env}" create_queue_with_auth "newsfork-contract-${env}" create_queue_with_auth "newsfork-liveness-${env}" create_queue_with_auth "newsfork-dlq-${env}"
# ✅ 새로운 Seed Engine Queue들 추가 create_queue_with_auth "newsfork-seed-${env}" create_queue_with_auth "newsfork-domain-${env}" create_queue_with_auth "newsfork-seed-dlq-${env}" create_queue_with_auth "newsfork-domain-dlq-${env}"
echo "✅ Queue provisioning completed" >&2}📊 에러 우선순위
섹션 제목: “📊 에러 우선순위”Critical (배포 실패)
섹션 제목: “Critical (배포 실패)”- ✅ Queue 생성 누락 - 배포 즉시 실패
- ✅ Bindings 타입 불일치 - TypeScript 컴파일 실패
High (기능 동작 불가)
섹션 제목: “High (기능 동작 불가)”- ✅ seed-queue-handlers.ts 타입 에러 - 런타임 에러 가능성
- ✅ SourceInfo 중복 정의 - 타입 충돌
Medium (경고 수준)
섹션 제목: “Medium (경고 수준)”- ✅ 미사용 변수/import - 코드 품질 이슈
🔄 수정 체크리스트
섹션 제목: “🔄 수정 체크리스트”TypeScript 에러 수정
섹션 제목: “TypeScript 에러 수정”-
src/apps/api/index.ts: Bindings 타입을src/index.ts에서 import -
src/lib/queue/seed-queue-handlers.ts:-
recordpossibly undefined → for…of 사용 -
NormalizedDomainimport type → import로 변경 -
country미사용 파라미터 제거 - 옵셔널 체이닝 추가
-
-
src/routes/seed-orchestrator.ts:force파라미터 사용 또는 제거 -
src/schemas/seed-engine.ts:-
SourceInfo이름을DomainSourceInfo로 변경 - 미사용 import 제거
-
-
src/schemas/index.ts: 중복 export 제거
Queue 생성 스크립트 수정
섹션 제목: “Queue 생성 스크립트 수정”-
.github/scripts/lib/cloudflare-resources.sh:-
provision_queues함수에 새로운 Queue 생성 추가 - 4개 Queue 추가:
seed,domain,seed-dlq,domain-dlq
-
- 로컬 TypeScript 컴파일 확인 (
pnpm exec tsc --noEmit) - 로컬 빌드 확인 (
pnpm dev:local) - GitHub Actions 재실행
📝 수정 상세 계획
섹션 제목: “📝 수정 상세 계획”1. Bindings 타입 통일
섹션 제목: “1. Bindings 타입 통일”파일: src/apps/api/index.ts
변경 전:
export type Bindings = { GH_TOKEN: string; GH_OWNER: string; GH_REPO: string; ENVIRONMENT: string; CF_ENV: string; DATA_PATH_PREFIX: string; DATASETS_BUCKET: R2Bucket; METADATA_BUCKET: R2Bucket; METADATA_DB: D1Database; DOMAIN_KV: KVNamespace; RESEARCH_QUEUE: Queue; CONTRACT_QUEUE: Queue; LIVENESS_QUEUE: Queue;};변경 후:
import type { Bindings } from '../../index';
// export type Bindings 제거 - 중앙 정의 사용2. seed-queue-handlers.ts 타입 에러 수정
섹션 제목: “2. seed-queue-handlers.ts 타입 에러 수정”파일: src/lib/queue/seed-queue-handlers.ts
주요 수정 사항:
NormalizedDomainimport 수정- for…of 루프 사용
- 타입 가드 추가
- 미사용 파라미터 제거
3. SourceInfo 중복 해결
섹션 제목: “3. SourceInfo 중복 해결”파일: src/schemas/seed-engine.ts
변경 전:
export const SourceInfoSchema = z.object({ ... });export type SourceInfo = z.infer<typeof SourceInfoSchema>;변경 후:
export const DomainSourceInfoSchema = z.object({ raw_file_path: z.string(), record_index: z.number().int().optional()});export type DomainSourceInfo = z.infer<typeof DomainSourceInfoSchema>;파일: src/schemas/index.ts
변경 전:
export { SourceInfo } from "./seed";export { SourceInfoSchema, SourceInfo } from "./seed-engine"; // ❌ 중복변경 후:
export { SourceInfo } from "./seed";export { DomainSourceInfoSchema, DomainSourceInfo } from "./seed-engine"; // ✅ 이름 변경4. Queue 생성 스크립트 업데이트
섹션 제목: “4. Queue 생성 스크립트 업데이트”파일: .github/scripts/lib/cloudflare-resources.sh
변경 전:
provision_queues() { local env="$1"
create_queue_with_auth "newsfork-research-${env}" create_queue_with_auth "newsfork-contract-${env}" create_queue_with_auth "newsfork-liveness-${env}" create_queue_with_auth "newsfork-dlq-${env}"}변경 후:
provision_queues() { local env="$1"
# 기존 Queue들 create_queue_with_auth "newsfork-research-${env}" create_queue_with_auth "newsfork-contract-${env}" create_queue_with_auth "newsfork-liveness-${env}" create_queue_with_auth "newsfork-dlq-${env}"
# ✅ Seed Engine Queue들 create_queue_with_auth "newsfork-seed-${env}" create_queue_with_auth "newsfork-domain-${env}" create_queue_with_auth "newsfork-seed-dlq-${env}" create_queue_with_auth "newsfork-domain-dlq-${env}"}🎯 예상 결과
섹션 제목: “🎯 예상 결과”수정 전
섹션 제목: “수정 전”❌ TypeScript 컴파일 실패 (15개 에러)❌ 배포 실패 (Queue 부재)수정 후
섹션 제목: “수정 후”✅ TypeScript 컴파일 성공✅ Queue 자동 생성✅ 배포 성공📌 추가 고려 사항
섹션 제목: “📌 추가 고려 사항”1. 환경별 Queue 생성
섹션 제목: “1. 환경별 Queue 생성”현재 provision_queues 함수는 환경별로 Queue를 생성하지만, 로컬 개발 환경에서는 어떻게 처리할지 확인 필요:
- 로컬:
wrangler dev --local시 자동 생성되는지 확인 - 개발:
newsfork-seed-dev,newsfork-domain-dev등 - 스테이징:
newsfork-seed-staging,newsfork-domain-staging등 - 프로덕션:
newsfork-seed-prod,newsfork-domain-prod등
2. DLQ (Dead Letter Queue) 생성
섹션 제목: “2. DLQ (Dead Letter Queue) 생성”새로운 Queue들의 DLQ도 함께 생성해야 함:
newsfork-seed-dlq-${env}newsfork-domain-dlq-${env}
3. 타입 안정성
섹션 제목: “3. 타입 안정성”모든 타입 에러를 수정하여 런타임 에러 가능성을 최소화해야 함.
분석 일시: 2026-01-28
에러 타입: TypeScript 컴파일 에러 + 인프라 리소스 부재
심각도: Critical (배포 실패)
해결 난이도: 중간 (타입 수정 + 스크립트 업데이트)