diff --git a/README.md b/README.md index dc66faa..426dd7b 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,26 @@ LLM 서비스의 스펙과 타입을 정의하는 패키지입니다. LLM 브릿지를 로드하는 인터페이스 정의는 `docs/BRIDGE_LOADER_SPEC.md` 문서를 참고하세요. +### 모듈 사용법 + +CommonJS와 ES Module 환경 모두에서 패키지를 그대로 사용할 수 있도록 듀얼 번들을 제공합니다. + +```javascript +// CommonJS +const { DependencyBridgeLoader } = require('llm-bridge-loader'); +const { default: OpenAIBridge } = require('openai-llm-bridge'); +``` + +```javascript +// ES Module +import { DependencyBridgeLoader } from 'llm-bridge-loader'; +import OpenAIBridge from 'openai-llm-bridge'; +``` + +각 패키지의 `esm/` 디렉터리에는 `package.json`이 포함되어 Node.js가 ES Module로 정확히 인식합니다. `pnpm --filter <패키지> build` 스크립트는 `dist/`(CJS)와 `esm/`(ESM)을 동시에 갱신하며, CI에서도 빌드 후 일반 테스트(`pnpm test:ci`)를 바로 수행하도록 구성했습니다. + +압축 아카이브 형태의 브리지를 로드하려면 `ArchiveBridgeLoader`를 사용할 수 있습니다. + ## 라이선스 MIT diff --git a/packages/anthropic-llm-bridge/package.json b/packages/anthropic-llm-bridge/package.json index 68dc20d..666c38c 100644 --- a/packages/anthropic-llm-bridge/package.json +++ b/packages/anthropic-llm-bridge/package.json @@ -19,7 +19,7 @@ ], "sideEffects": false, "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -27,7 +27,9 @@ "test:coverage": "vitest run --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs" }, "dependencies": {}, "devDependencies": { diff --git a/packages/anthropic-llm-bridge/tsconfig.esm.json b/packages/anthropic-llm-bridge/tsconfig.esm.json index 5975a67..d428b6c 100644 --- a/packages/anthropic-llm-bridge/tsconfig.esm.json +++ b/packages/anthropic-llm-bridge/tsconfig.esm.json @@ -4,6 +4,11 @@ "module": "ESNext", "moduleResolution": "node", "outDir": "./esm", - "target": "ES2020" + "target": "ES2020", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/packages/bedrock-llm-bridge/package.json b/packages/bedrock-llm-bridge/package.json index a04bc06..f6d0293 100644 --- a/packages/bedrock-llm-bridge/package.json +++ b/packages/bedrock-llm-bridge/package.json @@ -6,7 +6,7 @@ "module": "./esm/index.js", "types": "./dist/index.d.ts", "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -15,7 +15,9 @@ "test:e2e": "vitest run src/**/*.e2e.test.ts", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs" }, "exports": { ".": { diff --git a/packages/bedrock-llm-bridge/tsconfig.esm.json b/packages/bedrock-llm-bridge/tsconfig.esm.json index 5975a67..d428b6c 100644 --- a/packages/bedrock-llm-bridge/tsconfig.esm.json +++ b/packages/bedrock-llm-bridge/tsconfig.esm.json @@ -4,6 +4,11 @@ "module": "ESNext", "moduleResolution": "node", "outDir": "./esm", - "target": "ES2020" + "target": "ES2020", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/packages/embedding-bridge-spec/package.json b/packages/embedding-bridge-spec/package.json index 799bbae..7929858 100644 --- a/packages/embedding-bridge-spec/package.json +++ b/packages/embedding-bridge-spec/package.json @@ -2,15 +2,14 @@ "name": "embedding-bridge-spec", "version": "0.1.1", "private": false, - "type": "module", "main": "./dist/index.js", "module": "./esm/index.js", - "types": "./dist/types/index.d.ts", + "types": "./dist/index.d.ts", "exports": { ".": { "require": "./dist/index.js", "import": "./esm/index.js", - "types": "./dist/types/index.d.ts" + "types": "./dist/index.d.ts" } }, "files": [ @@ -20,15 +19,15 @@ ], "scripts": { "build": "pnpm build:clean && pnpm build:types && pnpm build:esm && pnpm build:cjs", - "build:clean": "rimraf dist", + "build:clean": "rimraf dist && rimraf esm", "build:types": "tsc -p tsconfig.types.json", - "build:esm": "tsc -p tsconfig.esm.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs", "build:cjs": "tsc -p tsconfig.cjs.json", "test": "vitest run", "test:watch": "vitest", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rimraf dist", + "clean": "rimraf dist && rimraf esm", "prepublishOnly": "pnpm build" }, "devDependencies": { diff --git a/packages/embedding-bridge-spec/tsconfig.esm.json b/packages/embedding-bridge-spec/tsconfig.esm.json index 49cd903..198cc99 100644 --- a/packages/embedding-bridge-spec/tsconfig.esm.json +++ b/packages/embedding-bridge-spec/tsconfig.esm.json @@ -3,6 +3,9 @@ "compilerOptions": { "outDir": "./esm", "rootDir": "./src", - "module": "ESNext" + "module": "ESNext", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false } } diff --git a/packages/embedding-bridge-spec/tsconfig.types.json b/packages/embedding-bridge-spec/tsconfig.types.json index 18d3707..22ba378 100644 --- a/packages/embedding-bridge-spec/tsconfig.types.json +++ b/packages/embedding-bridge-spec/tsconfig.types.json @@ -1,9 +1,10 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./dist/types", + "outDir": "./dist", "rootDir": "./src", "declaration": true, - "emitDeclarationOnly": true + "emitDeclarationOnly": true, + "declarationMap": true } } diff --git a/packages/google-llm-bridge/package.json b/packages/google-llm-bridge/package.json index 8eeb67f..7cb9974 100644 --- a/packages/google-llm-bridge/package.json +++ b/packages/google-llm-bridge/package.json @@ -19,7 +19,7 @@ ], "sideEffects": false, "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -27,7 +27,9 @@ "test:coverage": "vitest run --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs" }, "dependencies": {}, "devDependencies": { diff --git a/packages/google-llm-bridge/tsconfig.esm.json b/packages/google-llm-bridge/tsconfig.esm.json index 5975a67..d428b6c 100644 --- a/packages/google-llm-bridge/tsconfig.esm.json +++ b/packages/google-llm-bridge/tsconfig.esm.json @@ -4,6 +4,11 @@ "module": "ESNext", "moduleResolution": "node", "outDir": "./esm", - "target": "ES2020" + "target": "ES2020", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/packages/llm-bridge-loader/package.json b/packages/llm-bridge-loader/package.json index 3c4c60f..a2e8f9a 100644 --- a/packages/llm-bridge-loader/package.json +++ b/packages/llm-bridge-loader/package.json @@ -20,8 +20,8 @@ "sideEffects": false, "scripts": { "build": "pnpm build:clean && pnpm build:esm && pnpm build:cjs", - "build:clean": "rm -rf dist", - "build:esm": "tsc -p tsconfig.esm.json", + "build:clean": "rimraf dist && rimraf esm", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs", "build:cjs": "tsc -p tsconfig.cjs.json", "dev": "tsc -p tsconfig.json", "test": "vitest run", diff --git a/packages/llm-bridge-loader/tsconfig.esm.json b/packages/llm-bridge-loader/tsconfig.esm.json index 24ac7d9..204af06 100644 --- a/packages/llm-bridge-loader/tsconfig.esm.json +++ b/packages/llm-bridge-loader/tsconfig.esm.json @@ -3,7 +3,10 @@ "compilerOptions": { "outDir": "./esm", "rootDir": "./src", - "module": "ESNext" + "module": "ESNext", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false }, "include": ["src/**/*.ts"], "exclude": ["node_modules", "dist", "**/*.test.ts", "__tests__"] diff --git a/packages/llm-bridge-spec/package.json b/packages/llm-bridge-spec/package.json index 12b2433..ae4506e 100644 --- a/packages/llm-bridge-spec/package.json +++ b/packages/llm-bridge-spec/package.json @@ -2,15 +2,14 @@ "name": "llm-bridge-spec", "version": "1.0.6", "private": false, - "type": "module", "main": "./dist/index.js", "module": "./esm/index.js", - "types": "./dist/types/index.d.ts", + "types": "./dist/index.d.ts", "exports": { ".": { - "require": "./dist/index.js", "import": "./esm/index.js", - "types": "./dist/types/index.d.ts" + "require": "./dist/index.js", + "types": "./dist/index.d.ts" } }, "files": [ @@ -20,15 +19,15 @@ ], "scripts": { "build": "pnpm build:clean && pnpm build:types && pnpm build:esm && pnpm build:cjs", - "build:clean": "rimraf dist", + "build:clean": "rimraf dist && rimraf esm", "build:types": "tsc -p tsconfig.types.json", - "build:esm": "tsc -p tsconfig.esm.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs", "build:cjs": "tsc -p tsconfig.cjs.json", "test": "vitest run", "test:watch": "vitest", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rimraf dist", + "clean": "rimraf dist && rimraf esm", "prepublishOnly": "pnpm build" }, "devDependencies": { diff --git a/packages/llm-bridge-spec/tsconfig.cjs.json b/packages/llm-bridge-spec/tsconfig.cjs.json index 4d3edff..9c45868 100644 --- a/packages/llm-bridge-spec/tsconfig.cjs.json +++ b/packages/llm-bridge-spec/tsconfig.cjs.json @@ -3,6 +3,9 @@ "compilerOptions": { "outDir": "./dist", "rootDir": "./src", - "module": "CommonJS" + "module": "CommonJS", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false } } diff --git a/packages/llm-bridge-spec/tsconfig.esm.json b/packages/llm-bridge-spec/tsconfig.esm.json index 49cd903..198cc99 100644 --- a/packages/llm-bridge-spec/tsconfig.esm.json +++ b/packages/llm-bridge-spec/tsconfig.esm.json @@ -3,6 +3,9 @@ "compilerOptions": { "outDir": "./esm", "rootDir": "./src", - "module": "ESNext" + "module": "ESNext", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false } } diff --git a/packages/llm-bridge-spec/tsconfig.types.json b/packages/llm-bridge-spec/tsconfig.types.json index 18d3707..0c9f925 100644 --- a/packages/llm-bridge-spec/tsconfig.types.json +++ b/packages/llm-bridge-spec/tsconfig.types.json @@ -1,9 +1,10 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./dist/types", + "outDir": "./dist", "rootDir": "./src", "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true } } diff --git a/packages/ollama-llm-bridge/package.json b/packages/ollama-llm-bridge/package.json index 0cbbb80..66150ae 100644 --- a/packages/ollama-llm-bridge/package.json +++ b/packages/ollama-llm-bridge/package.json @@ -19,7 +19,9 @@ ], "sideEffects": false, "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -28,7 +30,7 @@ "test:coverage": "vitest run --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm" }, "keywords": [ "llm", diff --git a/packages/ollama-llm-bridge/src/bridge/ollama-bridge.ts b/packages/ollama-llm-bridge/src/bridge/ollama-bridge.ts index 7261bbd..4248c96 100644 --- a/packages/ollama-llm-bridge/src/bridge/ollama-bridge.ts +++ b/packages/ollama-llm-bridge/src/bridge/ollama-bridge.ts @@ -7,7 +7,7 @@ import { ModelNotSupportedError, } from 'llm-bridge-spec'; import { ChatResponse, Ollama } from 'ollama'; -import { AbstractOllamaModel, ALL_SUPPORTED_MODELS, createModelFromId } from '../models'; +import { AbstractOllamaModel, ALL_SUPPORTED_MODELS, createModelFromId } from '../models/index'; import { OllamaBaseConfig } from '../types/config'; import { handleOllamaError } from '../utils/error-handler'; diff --git a/packages/ollama-llm-bridge/src/index.ts b/packages/ollama-llm-bridge/src/index.ts index 0d908cc..c97c94c 100644 --- a/packages/ollama-llm-bridge/src/index.ts +++ b/packages/ollama-llm-bridge/src/index.ts @@ -27,7 +27,7 @@ export { GptOssConfig, ALL_SUPPORTED_MODELS, createModelFromId, -} from './models'; +} from './models/index'; // 에러 핸들러 export { handleOllamaError, handleFactoryError, validateModel } from './utils/error-handler'; diff --git a/packages/ollama-llm-bridge/src/utils/error-handler.ts b/packages/ollama-llm-bridge/src/utils/error-handler.ts index a089b65..d9627b8 100644 --- a/packages/ollama-llm-bridge/src/utils/error-handler.ts +++ b/packages/ollama-llm-bridge/src/utils/error-handler.ts @@ -11,7 +11,7 @@ import { TimeoutError, } from 'llm-bridge-spec'; import { ZodError } from 'zod'; -import { ALL_SUPPORTED_MODELS } from '../models'; +import { ALL_SUPPORTED_MODELS } from '../models/index'; // Type guards for error handling function hasCause(error: unknown): error is { cause: unknown } { diff --git a/packages/ollama-llm-bridge/tsconfig.esm.json b/packages/ollama-llm-bridge/tsconfig.esm.json index 50e472a..80354a1 100644 --- a/packages/ollama-llm-bridge/tsconfig.esm.json +++ b/packages/ollama-llm-bridge/tsconfig.esm.json @@ -5,8 +5,11 @@ "rootDir": "./src", "module": "ESNext", "target": "ES2020", - "declaration": true, - "declarationMap": true, - "sourceMap": true + "declaration": false, + "declarationMap": false, + "sourceMap": true, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/packages/openai-embedding-bridge/package.json b/packages/openai-embedding-bridge/package.json index 38c2024..863b134 100644 --- a/packages/openai-embedding-bridge/package.json +++ b/packages/openai-embedding-bridge/package.json @@ -19,7 +19,7 @@ ], "sideEffects": false, "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -27,7 +27,9 @@ "test:coverage": "vitest run --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs" }, "dependencies": {}, "devDependencies": { diff --git a/packages/openai-embedding-bridge/tsconfig.esm.json b/packages/openai-embedding-bridge/tsconfig.esm.json index 5975a67..d428b6c 100644 --- a/packages/openai-embedding-bridge/tsconfig.esm.json +++ b/packages/openai-embedding-bridge/tsconfig.esm.json @@ -4,6 +4,11 @@ "module": "ESNext", "moduleResolution": "node", "outDir": "./esm", - "target": "ES2020" + "target": "ES2020", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/packages/openai-like-llm-bridge/package.json b/packages/openai-like-llm-bridge/package.json index 8c90ee3..5123f7f 100644 --- a/packages/openai-like-llm-bridge/package.json +++ b/packages/openai-like-llm-bridge/package.json @@ -19,7 +19,7 @@ ], "sideEffects": false, "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -27,7 +27,9 @@ "test:coverage": "vitest run --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs" }, "dependencies": {}, "devDependencies": { diff --git a/packages/openai-like-llm-bridge/tsconfig.esm.json b/packages/openai-like-llm-bridge/tsconfig.esm.json index 5975a67..d428b6c 100644 --- a/packages/openai-like-llm-bridge/tsconfig.esm.json +++ b/packages/openai-like-llm-bridge/tsconfig.esm.json @@ -4,6 +4,11 @@ "module": "ESNext", "moduleResolution": "node", "outDir": "./esm", - "target": "ES2020" + "target": "ES2020", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/packages/openai-llm-bridge/package.json b/packages/openai-llm-bridge/package.json index bbd109a..9686175 100644 --- a/packages/openai-llm-bridge/package.json +++ b/packages/openai-llm-bridge/package.json @@ -19,7 +19,7 @@ ], "sideEffects": false, "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -27,7 +27,9 @@ "test:coverage": "vitest run --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs" }, "dependencies": {}, "devDependencies": { diff --git a/packages/openai-llm-bridge/tsconfig.esm.json b/packages/openai-llm-bridge/tsconfig.esm.json index 5975a67..d428b6c 100644 --- a/packages/openai-llm-bridge/tsconfig.esm.json +++ b/packages/openai-llm-bridge/tsconfig.esm.json @@ -4,6 +4,11 @@ "module": "ESNext", "moduleResolution": "node", "outDir": "./esm", - "target": "ES2020" + "target": "ES2020", + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/packages/xai-grok-llm-bridge/package.json b/packages/xai-grok-llm-bridge/package.json index 676d608..9670e9a 100644 --- a/packages/xai-grok-llm-bridge/package.json +++ b/packages/xai-grok-llm-bridge/package.json @@ -19,7 +19,7 @@ ], "sideEffects": false, "scripts": { - "build": "pnpm clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "dev": "tsc -p tsconfig.json", "test": "vitest run", "test:ci": "vitest run --exclude='src/**/*.e2e.test.ts'", @@ -27,7 +27,9 @@ "test:coverage": "vitest run --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "clean": "rm -rf dist && rm -rf esm" + "clean": "rimraf dist && rimraf esm", + "build:cjs": "tsc -p tsconfig.json", + "build:esm": "tsc -p tsconfig.esm.json && node ../../scripts/postbuild-esm.cjs" }, "dependencies": {}, "devDependencies": { diff --git a/packages/xai-grok-llm-bridge/tsconfig.esm.json b/packages/xai-grok-llm-bridge/tsconfig.esm.json index 265a0c3..b1e769a 100644 --- a/packages/xai-grok-llm-bridge/tsconfig.esm.json +++ b/packages/xai-grok-llm-bridge/tsconfig.esm.json @@ -4,8 +4,10 @@ "outDir": "./esm", "module": "ESNext", "moduleResolution": "node", - "declaration": true, - "declarationMap": true, - "emitDeclarationOnly": false + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "composite": false, + "incremental": false } } diff --git a/plan/esm-cjs-dual-support-plan.md b/plan/esm-cjs-dual-support-plan.md index ea8dcd7..c11d04e 100644 --- a/plan/esm-cjs-dual-support-plan.md +++ b/plan/esm-cjs-dual-support-plan.md @@ -1,89 +1,93 @@ # 듀얼 ESM/CJS 지원 고도화 계획 -## 1. Context - -- `ollama-llm-bridge`를 포함한 여러 브리지 패키지가 ESM 전용 환경에서만 동작하여 CJS 기반 소비자와의 호환성 문제가 반복 발생. -- `llm-bridge-spec`의 CJS 출력물이 `type: "module"` 선언과 불일치하여 로더(`DependencyBridgeLoader`) 사용 시 require 단계에서 예외가 보고됨. -- 프로젝트 전반에서 듀얼 패키징 패턴이 일관되지 않아 패키지별 설정/산출물 차이로 유지보수 비용이 증가. - -## 2. Success Criteria - -- `llm-bridge-spec`, `llm-bridge-loader`, 모든 브리지 패키지가 `require`/`import` 양쪽에서 문제없이 로드됨을 테스트로 검증. -- 모든 관련 `package.json`에 표준화된 `main/module/exports/types/sideEffects` 구성이 적용되고 문서화됨. -- 듀얼 패키징 정책과 검증 절차가 `docs/`에 반영되어 향후 신규 브리지 추가 시 참고 가능. -- 롤백 및 비상 대응 전략이 마련되어 PR/릴리스 시 참조 가능. - -## 3. Scope - -- **In Scope**: `llm-bridge-spec`, `llm-bridge-loader`, `packages/*-llm-bridge`, 관련 빌드 스크립트·테스트·문서. -- **Out of Scope**: 배포 자동화 스크립트 개선, 외부 소비자 프로젝트 수정, 신규 기능 개발. - -## 4. Deliverables - -- 듀얼 패키징 표준 가이드 (`docs/DUAL-PACKAGING.md` 신규 또는 기존 문서 업데이트). -- 각 패키지의 `package.json`/`tsconfig`/빌드 스크립트 수정 및 호환성 테스트 코드. -- 호환성 매트릭스 결과 및 회귀 테스트 체크리스트. -- 롤백 매뉴얼 및 릴리스 노트 초안. - -## 5. Milestones & Timeline (예상) - -| Milestone | 목표 기간 | 설명 | -| --------- | --------- | -------------------------------------------------------------- | -| M1 | 주 1 | 현황 분석 완료 및 결과 공유 | -| M2 | 주 2 | 표준 정책 수립 및 `llm-bridge-spec` 적용 | -| M3 | 주 3 | `llm-bridge-loader` 및 파일럿 브리지(`ollama-llm-bridge`) 적용 | -| M4 | 주 4 | 나머지 브리지 확산 및 통합 검증 | -| M5 | 주 5 | 문서/가이드 확정, 롤백 계획 검토, 배포 준비 | - -## 6. TODO Checklist - -- [ ] TODO 1: **현황 분석 정리** - - 각 패키지의 `package.json`(`main`, `module`, `exports`, `types`, `sideEffects`)과 산출물 확장자(`.js`, `.cjs`, `.mjs`) 조사. - - 빌드 스크립트(`pnpm build`, `tsconfig.*.json`) 차이점 표로 정리. - - 듀얼 지원이 이미 적용된 사례/미흡한 사례 목록 작성. -- [ ] TODO 2: **공통 듀얼 패키징 정책 수립** - - 표준 `exports` 맵, 산출물 구조, 타입 정의 위치, `sideEffects` 기본값 명시. - - 개발자용 가이드 초안(`docs/DUAL-PACKAGING.md` 또는 기존 문서) 작성. - - 로컬 검증 스크립트/명령어 템플릿 설계. -- [ ] TODO 3: **`llm-bridge-spec` 개편** - - CJS 산출물(`dist/index.cjs`) 생성 및 `exports.require` 경로와 연동. - - 타입 출력(`.d.ts`) 경로 통합 및 `exports.types` 업데이트. - - `require`/`import` Smoke 테스트 추가, Breaking change 영향 평가. -- [ ] TODO 4: **`llm-bridge-loader` 호환성 강화** - - 동적 로딩 경로를 `await import()` 기반으로 통일하고, CJS 번들에서 동일하게 동작하도록 컴파일 결과 확인. - - `exports` 조건부 진입점 재구성 및 `.d.ts`/`sideEffects` 검토. - - CJS/ESM 각각에서 브리지 로딩을 검증하는 테스트 케이스 추가. -- [ ] TODO 5: **파일럿 브리지 적용 (`ollama-llm-bridge`)** - - 표준 정책을 반영한 `package.json`/빌드 스크립트 수정. - - `DependencyBridgeLoader`를 통한 Smoke 테스트(`invoke`, `getMetadata`) 작성. - - 릴리스 노트 템플릿에 ESM/CJS 예제 추가. -- [ ] TODO 6: **전체 브리지 확산** - - 우선순위(사용 빈도, 의존성)를 기준으로 나머지 브리지에 순차 적용. - - 공통 패턴/스크립트 활용 및 TODO 완료 여부 문서화. - - CI 파이프라인에 변경된 빌드/테스트 명령 반영 여부 확인. -- [ ] TODO 7: **통합 검증 & 호환성 매트릭스 실행** - - Node 16/18/20, 번들러(Webpack/Vite/esbuild), 런타임(Node CJS/ESM, 브라우저) 조합으로 매트릭스 테스트 수행. - - `pnpm build`, `pnpm test`, `pnpm test:ci`, `apps/gui` E2E(`E2E_OLLAMA=true ...`) 결과 기록. - - `pnpm test:dual` 등 로컬 검증 스크립트 동작 확인. -- [ ] TODO 8: **문서화 & 롤백 전략 정리** - - CHANGELOG/README/Docs 업데이트 및 듀얼 패키징 사용 예시 추가. - - 롤백 절차 (dist-tag, Git 태그, 비상 패치) 문서화. - - 주요 소비자 알림 계획 및 후속 모니터링 항목 정리. - -## 7. Risks & Mitigations - -- **Breaking change 노출**: 기존 소비자가 특정 경로를 직접 import 하고 있을 가능성 → `exports` 재구성 시 마지막 단계에서 호환성 확인 및 명확한 마이그레이션 가이드 제공. -- **런타임별 테스트 비용 증가**: 매트릭스 테스트 자동화가 미흡할 수 있음 → 우선 수동/스크립트 기반으로 검증 후 CI 자동화 범위 점진 확대. -- **릴리스 동기화 실패**: 패키지별 릴리스 순서 어긋날 위험 → `llm-bridge-spec` → `llm-bridge-loader` → 브리지 순서의 버전 전략 문서화 및 체크리스트 운영. - -## 8. Dependencies & Open Questions - -- Node 16 지원을 계속 유지할지 여부(듀얼 패키징 적용 시 지원 범위 재검토 필요). -- 번들러별 테스트 환경을 어떤 수준까지 자동화할지 결정 필요. -- 향후 신규 브리지 추가 시 템플릿/스캐폴드 자동화 여부. - -## 9. References - -- Issue & 피드백: ESM/CJS 호환성 문제 보고 스레드, Ollama 브리지 로딩 실패 로그. -- 기존 문서: `docs/GIT_WORKFLOW_GUIDE.md`, `docs/DEPLOYMENT_GUIDE.md`. -- 관련 패키지: `packages/llm-bridge-spec`, `packages/llm-bridge-loader`, `packages/ollama-llm-bridge` 등. +## 0. 현황 분석 + +- 각 패키지(`llm-bridge-spec`, `llm-bridge-loader`, 개별 브리지)의 `package.json` 내 `exports`, `main`, `module`, `types`, `sideEffects` 필드 구조와 현재 산출물 확장자(`.js`, `.cjs`, `.mjs`)를 조사해 표로 정리. +- 빌드 스크립트와 TypeScript 설정(`tsconfig.*.json`)을 확인해 공통 옵션과 패키지별 차이를 파악, 중복/불일치 지점을 기록. +- 이미 듀얼 지원이 부분적으로 구현된 패키지를 식별하고, 재사용 가능한 패턴과 개선이 필요한 사례를 문서화. +- 조사 결과를 Confluence/문서(또는 `docs/` 내)로 정리하여 이후 단계의 기준 자료로 활용. + +## 1. 공통 정책 수립 + +- 모든 패키지에 적용할 듀얼 패키징 규칙 정의: 출력 디렉터리(`dist/`=CJS, `esm/`=ESM), 파일 확장자(`index.cjs`, `index.js`), 타입 정의 위치(`dist/index.d.ts`), `sideEffects` 플래그 등. +- `package.json` 표준 예시: + ```json + { + "main": "./dist/index.cjs", + "module": "./esm/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./esm/index.js", + "require": "./dist/index.cjs" + } + }, + "sideEffects": false + } + ``` +- 빌드 스크립트와 `tsconfig` 템플릿을 표준화하여 패키지별로 동일한 명령으로 듀얼 산출물이 생성되도록 조정. +- 루트 공통 스크립트(`scripts/postbuild-esm.cjs`)로 `esm/package.json`(`"type": "module"`)을 생성해 모든 패키지에서 재사용. +- 개발자가 참고할 공통 가이드(예: `docs/DUAL-PACKAGING.md`) 초안을 작성해 향후 패키지 추가 시 활용 가능하도록 준비. + +## 2. `llm-bridge-spec` 정비 + +- CJS 산출물을 `dist/index.cjs`로 출력하도록 빌드 파이프라인 수정 (`tsconfig.cjs.json`의 `outFile`/`outDir` 점검 및 확장자 변환 스텝 추가). +- `package.json` `exports`를 위 표준 규칙에 맞춰 정리하고, 타입 정의(`dist/types/index.d.ts` → `dist/index.d.ts` 여부) 재배치 검토. +- `.d.ts` 파일 경로와 실제 산출물이 일치하는지 확인하고, 필요 시 `tsconfig.types.json` 수정. +- Smoke 테스트 추가: + - Node CJS: `node -e "const spec = require('llm-bridge-spec');"` + - Node ESM: `node -e "import('llm-bridge-spec').then(m => console.log(typeof m));" --input-type=module` +- Breaking change 여부를 판단해 메이저/마이너 버전 전략 수립. + +## 3. `llm-bridge-loader` 호환성 강화 + +- CJS 번들(`dist/`)에서 동적 로드 시에도 ESM 브리지를 다룰 수 있도록 코드 수정: + ```ts + export async function loadModule(pkg: string) { + if (typeof require !== 'undefined') { + return import(pkg); // Node 20+ CJS에서도 동작 + } + return import(pkg); + } + ``` + 또는 TypeScript 단계에서 `await import(pkg)`를 사용하도록 정의하고, TS 컴파일이 `.cjs` 출력에서도 동일 패턴을 생성하도록 확인. +- `exports` 조건부 진입점에 `./dist/index.cjs`, `./esm/index.js` 명시. +- 테스트 확장: + - CJS 환경에서 `require('llm-bridge-loader')` 후 `load('ollama-llm-bridge')` 실행. + - ESM 환경에서 `import { DependencyBridgeLoader }` 후 동일 시나리오 검증. +- `.d.ts` 매핑과 `sideEffects` 설정을 재확인하고, 번들 크기/트리쉐이킹 영향 검토. + +## 4. 개별 브리지 패키지 적용 (파일럿 → 전체 전개) + +- 파일럿 대상으로 `ollama-llm-bridge` 선정: 현 구조 분석 → 표준 정책 적용 → 빌드/테스트 → 사용 예제 업데이트. +- 파일럿 결과를 템플릿으로 정리한 뒤, 우선순위(사용량, 의존도)를 기준으로 나머지 브리지(`openai`, `anthropic`, `bedrock`, `xai-grok`, …)에 순차 적용. +- 각 브리지의 `package.json`, 빌드 스크립트, TS 설정을 표준에 맞게 정리하고, `default` export 및 manifest/factory 노출 패턴을 확인. +- 로더를 통한 Smoke 테스트(동적 로드 후 `invoke`, `getMetadata`, `getCapabilities`)를 작성해 공통 활용. + +## 5. 통합 검증 및 품질 보증 + +- 루트에서 `pnpm build`, `pnpm test`, `pnpm test:ci` 실행 후 로그를 공유하고, 실패 시 원인 분석 절차 문서화. +- 호환성 매트릭스 정의 및 검증: + - Node 버전: 16.x, 18.x, 20.x 이상 (각각 CJS/ESM 모드) + - 번들러: Webpack, Vite, esbuild (샘플 프로젝트 또는 스크립트로 테스트) + - 런타임: Node CLI, 브라우저 번들 (가능 시) +- 로컬 검증 스크립트 마련: `pnpm test:dual` (CJS/ESM 로딩 체커), `pnpm sandbox:esm-loader` 등. +- 실 사용 시나리오 확인: `apps/gui`에서 ESM/CJS 환경별 실행, `E2E_OLLAMA=true pnpm --dir apps/gui test:e2e -- --grep "Ollama 브리지가"` 수행. + +## 6. 롤백 및 리스크 대응 + +- 듀얼 패키징 전환이 미칠 잠재적 Breaking change를 사전 검토하고, 필요한 경우 메이저 버전 업 또는 프리릴리스 태그(beta) 운영. +- 문제 발생 시 롤백 절차: + 1. npm에 이전 버전 `dist-tag` 유지 + 2. Git 태그 및 브랜치 복구 전략 문서화 + 3. 긴급 패치 릴리스 프로세스 정의 +- 영향 범위를 줄이기 위해 주요 소비자(내부 앱, 파트너 프로젝트)와 사전 커뮤니케이션 채널 확보. + +## 7. 문서화 및 배포 전략 + +- CHANGELOG, README, `docs/` 문서에 듀얼 지원 변경 사항과 import/require 예제를 추가. +- 타입 정의 위치(`dist/index.d.ts`)와 `exports.types` 매핑을 명시하여 소비자가 바로 활용 가능하도록 안내. +- CI/CD(GitHub Actions 등)에 듀얼 빌드/테스트 명령 추가 및 캐싱 전략 업데이트. +- 릴리스 순서: `llm-bridge-spec` → `llm-bridge-loader` → 개별 브리지. 각 단계별 품질 체크 후 다음 단계 진행. +- 배포 완료 후 회귀 테스트 계획 수립, 주요 지표(다운스트림 호환성 보고) 모니터링. diff --git a/scripts/postbuild-esm.cjs b/scripts/postbuild-esm.cjs new file mode 100644 index 0000000..cfc246a --- /dev/null +++ b/scripts/postbuild-esm.cjs @@ -0,0 +1,17 @@ +const fs = require('node:fs/promises'); +const path = require('node:path'); + +async function ensureEsmPackageJson(rootDir = process.cwd()) { + const targetDir = path.join(rootDir, 'esm'); + const manifestPath = path.join(targetDir, 'package.json'); + const content = JSON.stringify({ type: 'module' }, null, 2) + '\n'; + + await fs.mkdir(targetDir, { recursive: true }); + await fs.writeFile(manifestPath, content, 'utf8'); +} + +ensureEsmPackageJson().catch(err => { + console.error('[postbuild-esm] Failed to write esm/package.json'); + console.error(err); + process.exitCode = 1; +});