diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a249d88 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +.git +.github + +Dockerfile +.dockerignore + +node_modules/ +.pnpm-store/ + +build/ +dist/ +.next/ +out/ + +*.log + +.env +.env.* + +.DS_Store diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 016ae59..f7c75f6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -20,8 +20,6 @@ jobs: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v6 - with: - version: 10 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 70fc6c7..251d4d4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,10 +9,12 @@ jobs: runs-on: self-hosted steps: - name: Deploy + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} run: | set -e - echo "Release ${{ github.event.release.tag_name }} detected" + echo "Release ${RELEASE_TAG} detected" cd /home/sora81dev/discord-bot/nanase-bot git fetch --all --tags - git checkout ${{ github.event.release.tag_name }} - docker compose up -d --build \ No newline at end of file + git checkout --detach "refs/tags/${RELEASE_TAG}" + docker compose -f docker-compose.prod.yml up -d --build --remove-orphans diff --git a/.gitignore b/.gitignore index 28b7140..3dadbbe 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ build/Release # Dependency directories node_modules/ +.pnpm-store/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) diff --git a/Dockerfile b/Dockerfile index 2703280..8cd41a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,34 @@ -FROM node:24 +FROM node:24 AS base -COPY package*.json ./ -RUN npm install +ENV PNPM_HOME="/pnpm" +ENV PNPM_CONFIG_STORE_DIR="/pnpm/store" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable +WORKDIR /app +COPY package.json pnpm-lock.yaml ./ +RUN corepack install + +FROM base AS deps +RUN pnpm install --frozen-lockfile + +FROM deps AS development COPY . . -CMD ["npm", "run", "dev"] +CMD ["pnpm", "dev"] + +FROM deps AS builder +COPY . . +RUN pnpm run build + +FROM base AS production +ENV NODE_ENV=production + +RUN pnpm install --frozen-lockfile --prod + +# NOTE: 書き込みが発生するなら変更する必要がある +COPY --from=builder /app/build ./build + +USER node +EXPOSE 3000 + +CMD ["node", "./build/src/index.js"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..45414df --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,10 @@ +services: + nanase-bot: + container_name: nanase-bot + build: + context: . + dockerfile: ./Dockerfile + target: production + env_file: + - .env + restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml index b795a75..f30d7a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,5 +4,14 @@ services: stdin_open: true tty: true build: - dockerfile: ./Dockerfile context: . + dockerfile: ./Dockerfile + target: development + volumes: + - .:/app + - node_modules:/app/node_modules + - pnpm_store:/pnpm/store + +volumes: + node_modules: + pnpm_store: diff --git a/package.json b/package.json index cc76c11..a8e9bac 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,34 @@ { - "name": "nanase-bot", - "version": "1.0.0", - "description": "", - "license": "ISC", - "author": "", - "main": "src/index.ts", - "scripts": { - "dev": "ts-node src/index.ts", - "build": "tsc", - "start": "node -r tsconfig-paths/register ./build/index.js", - "// linters //": "", - "typecheck": "tsc --noEmit", - "lint": "biome lint .", - "format": "biome format . --write", - "check": "biome check .", - "fix": "biome check . --write" - }, - "devDependencies": { - "@biomejs/biome": "2.5.3", - "@types/node": "^25.0.3", - "ts-node": "^10.9.2", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.9.3" - }, - "dependencies": { - "discord.js": "^14.26.5", - "dotenv": "^17.2.3" - }, - "overrides": { - "axios": "1.16.0" - } + "name": "nanase-bot", + "version": "1.0.0", + "description": "", + "license": "ISC", + "author": "", + "main": "src/index.ts", + "packageManager": "pnpm@11.9.0", + "scripts": { + "dev": "ts-node src/index.ts", + "build": "tsc", + "start": "node ./build/src/index.js", + "// linters //": "", + "typecheck": "tsc --noEmit", + "lint": "biome lint .", + "format": "biome format . --write", + "check": "biome check .", + "fix": "biome check . --write" + }, + "devDependencies": { + "@biomejs/biome": "2.5.3", + "@types/node": "^25.0.3", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.9.3" + }, + "dependencies": { + "discord.js": "^14.26.5", + "dotenv": "^17.2.3" + }, + "overrides": { + "axios": "1.16.0" + } } diff --git a/src/index.ts b/src/index.ts index 77f8261..23428bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,10 +22,11 @@ import { loadActions, loadCommands } from "./utils/loader"; dotenv.config({ path: ".env" }); -// 実行環境に応じてファイルタイプとディレクトリを決定 -const FILE_TYPE: string = process.argv[2] === "js" ? ".js" : ".ts"; -const IS_PRODUCTION = FILE_TYPE === ".js"; -const BASE_DIR = IS_PRODUCTION ? "./dist" : "./src"; +// 実行中のファイルの配置から、読み込むモジュールの種類と基準ディレクトリを決定する。 +// ts-node では src/index.ts、コンパイル後の Node.js では build/src/index.js になるため、 +// 起動引数や出力ディレクトリ名に依存しない。 +const FILE_TYPE = __filename.endsWith(".js") ? ".js" : ".ts"; +const BASE_DIR = __dirname; const CLIENT_INTENTS: GatewayIntentBits[] = [ GatewayIntentBits.Guilds, diff --git a/src/utils/loader.ts b/src/utils/loader.ts index 74c7a94..610b3fb 100644 --- a/src/utils/loader.ts +++ b/src/utils/loader.ts @@ -31,13 +31,17 @@ export function loadActions(BASE_DIR: string, FILE_TYPE: string): Actions { const folders = ["button", "modal"]; for (const folder of folders) { - const actionFiles = fs - .readdirSync(path.resolve(BASE_DIR, "handlers", folder)) - .filter((file) => file.endsWith(FILE_TYPE)); + const actionDir = path.resolve(BASE_DIR, "handlers", folder); + if (!fs.existsSync(actionDir)) { + console.log(` Handler Type: ${folder} (none)`); + continue; + } + + const actionFiles = fs.readdirSync(actionDir).filter((file) => file.endsWith(FILE_TYPE)); console.log(` Handler Type: ${folder}`); for (const file of actionFiles) { - const reqPath = path.resolve(BASE_DIR, "handlers", folder, file); + const reqPath = path.resolve(actionDir, file); const action = require(reqPath).default as Action; console.log(` Load: ${action.data.action}`); actions[folder][action.data.action] = action;