import { build } from "@repo/build";
await build({
entryPoints: ["./src/server.js"],
outdir: "dist",
});
import { loadProtos } from "@repo/common/load.protos"; // 제거
loadProtos // 제거
await server.start(); // 서버 시작 시 await 추가
{
"name": "서버명",
"private": true,
"main": "src/server.js",
"license": "MIT",
"type": "module",
"dependencies": {
"@repo/common": "*",
"@repo/build": "*"
},
"scripts": {
"build": "yarn && node build.js",
"start:서버명": "node dist/server.js"
}
}
// import 부분만 변경
//import TcpServer from "@repo/common/classes/models/server.class.js"; 제거
//import { config } from "@repo/common/config/config.js"; 제거
//import { deserialize } from "@repo/common/utils/deserialize/deserialize.js"; 제거
import { TcpServer } from "@repo/common/classes";
import { config } from "@repo/common/config";
import { createServerInfoNotification, deserialize } from "@repo/common/utils";
{
"name": "super-convergence-msa-server",
"private": true,
"scripts": {
"build": "turbo build",
"start": "turbo run start:ice --no-daemon", // turbo.json 설정에 따라 실행
"dev": "turbo dev",
"lint": "turbo lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"devDependencies": {
"turbo": "^2.3.0"
},
"packageManager": "yarn@1.22.22",
"workspaces": [
"apps/*",
"packages/*"
]
}
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": { // 빌드 -> distributor 시작 -> gate 시작 -> ice 시작 순서로 실행
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"start:distributor": {
"dependsOn": ["build"],
"cache": false
},
"start:gate": {
"dependsOn": ["start:distributor"],
"cache": false
},
"start:ice": {
"dependsOn": ["start:gate"],
"cache": false
}
}
}
// common/utils/index.js
export { deserialize } from "./deserialize/deserialize.js";
export { createServerInfoNotification } from "./notifications/distributor.notification.js";
export { packetParser } from "./packets/packet.parser.js";
export { serialize } from "./serialize/serialize.js";
// common/config/index.js
export { config } from "./config.js";
export { logger } from "./logger/winston.config.js";
// common/classes/index.js
export { default as TcpClient } from "./models/client.class.js";
export { default as TcpServer } from "./models/server.class.js";
export { default as IntervalManager } from "./managers/interval.manager.js";
// 사용예: import { TcpClient, TcpServer } from "@repo/common/classes";
단일 파일은 그 파일 통째로 내보냄{
"name": "@repo/common",
"version": "0.0.0",
"type": "module",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "yarn"
},
"dependencies": {
"protobufjs": "^7.4.0",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^5.0.0"
},
"exports": {
"./utils": "./utils/index.js",
"./classes": "./classes/index.js",
"./config": "./config/index.js",
"./header": "./constants/header.js",
"./handlers": "./handlers/index.js",
"./load.protos": "./init/load.protos.js",
"./protobuf": "./protobuf"
},
"files": [
"utils/**/*",
"classes/**/*",
"config/**/*",
"constants/**/*",
"handlers/**/*",
"init/**/*",
"protobuf/**/*.proto"
]
}
import * as esbuild from "esbuild";
export const build = async (options) => {
const defaultOptions = {
bundle: true,
platform: "node",
format: "esm",
minify: true,
sourcemap: false,
target: "node20",
external: [
"util",
"net",
"fs",
"path",
"url",
"protobufjs",
"winston",
"winston-daily-rotate-file",
"@repo/common", // common도 번들링 제외
],
define: {
"process.env.NODE_ENV": '"production"',
},
loader: {
".proto": "file", // .proto도 파일로 처리
},
};
await esbuild.build({
...defaultOptions,
...options,
});
};
// package.json
{
"name": "@repo/build",
"version": "0.0.0",
"main": "./build.js",
"type": "module",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"esbuild": "^0.24.0"
},
"exports": {
".": "./build.js"
},
"files": [
"*.js"
]
}
// 모든 import 경로 @repo/common을 참조하도록 변경
import { TcpClient } from "@repo/common/classes";
import { getProtoMessages, loadProtos } from "@repo/common/load.protos";
import {
createServerInfoNotification,
packetParser,
deserialize,
} from "@repo/common/utils";
import { config } from "@repo/common/config";
_protoMessages = getProtoMessages(); -> _protoMessages = null;
// 메소드 추가
async initialize() {
await loadProtos();
this._protoMessages = getProtoMessages();
}
// start 메소드 내부에 추가 + async
await this.initialize();
// connectToDistributor 메소드에 추가 + async
if (!this._protoMessages) {
await this.initialize();
}
const packet...
문제 정의
사실 수집
Protobuf 파일 로드 중 오류가 발생했습니다: Error: ENOENT: no such file or directory, scandir 'D:\github\super-convergenc
│ e-msa-server\apps\ice\protobuf'
│ at Object.readdirSync (node:fs:1506:26)
│ at O (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:820)
│ at L (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:967)
│ at C.initialize (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:6020)
│ at C.start (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:6075)
│ at file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:8:436
│ at ModuleJob.run (node:internal/modules/esm/module_job:234:25)
│ at async ModuleLoader.import (node:internal/modules/esm/loader:473:24)
│ at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5) {
│ errno: -4058,
│ code: 'ENOENT',
│ syscall: 'scandir',
│ path: 'D:\\github\\super-convergence-msa-server\\apps\\ice\\protobuf'
│ }
원인 추론
해결 방안
a. proto 파일도 같이 빌드 시킨다.
→ 컨테이너로 배포할 때 결국 포함시켜야 함. → 시도
b. 상위에 있는 proto 파일을 읽어오게 경로를 수정한다.
→ 컨테이너로 배포할 때 결국 수정해야 함. → 기각
해결 시도
// 빌드 때 protobuf로 복사해서 생성시킨다.
export const buildServiceWithProto = async (options) => {
const srcProtoDir = path.resolve(
process.cwd(),
"../../packages/common/protobuf"
);
const destProtoDir = path.resolve(process.cwd(), "./dist/protobuf");
try {
await fs.mkdir(destProtoDir, { recursive: true });
await fs.cp(srcProtoDir, destProtoDir, { recursive: true });
await buildService(options);
} catch (error) {
console.error("Build failed:", error);
process.exit(1);
}
};
$ node dist/server.js
│ Protobuf initialized : 14
│ distributor server listening on port 9000
│ [ _onConnection ] distributor server : => ::ffff:127.0.0.1 : 5591
│ [ _onCreate ] ::ffff:127.0.0.1 5591
│ [ createServerInfoNotification ] payload ===>> {
│ params: [
│ {
│ name: 'distributor',
│ number: 1,
│ host: 'localhost',
│ port: '9000',
│ types: []
│ }
│ ]
│ }
│ [ serialize ] payload ===>>> {
│ serverInfoNotification: S2S_ServerInfoNotification { params: [ [Object] ] }
│ }
문제는 해결되었으나 배포 때 결국 수정해야 함 → 상위 경로를 참조하기 때문
{
"name": "@repo/common",
"version": "0.0.0",
"type": "module",
"private": true,
"license": "MIT",
"exports": {
"./utils": "./utils/index.js",
"./classes": "./classes/index.js",
"./config": "./config/index.js",
"./header": "./constants/header.js",
"./handlers": "./handlers/index.js",
"./load.protos": "./init/load.protos.js",
"./protobuf": "./protobuf" // protobuf 명으로 사용
},
"files": [
"utils/**/*",
"classes/**/*",
"config/**/*",
"constants/**/*",
"handlers/**/*",
"init/**/*",
"protobuf/**/*.proto" // proto 파일들을 포함
]
}
해결
// nodemodules 트리 구조
node_modules/@repo/common/
├── init/
│ └── load.protos.js # __dirname은 이 파일의 위치
├── protobuf/ # "../protobuf"는 이 디렉토리를 가리킴
│ ├── distributor/
│ │ └── packet.proto
│ └── games/
│ └── ice/
│ └── ice.proto
└── package.json
컨테이너로 배포 시에도 같은 node_modules 폴더의 protobuf를 참조하기 때문에 문제 발생 x