diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1de63b9..45aee7c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -59,8 +59,9 @@ jobs: username: ${{ secrets.VPS_USER }} key: ${{ secrets.VPS_SSH_KEY }} script: | - echo "DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }}" > /home/${{ secrets.VPS_USER }}/discord-bot/.env - echo "CLIENT_ID=${{ secrets.CLIENT_ID }}" >> /home/${{ secrets.VPS_USER }}/discord-bot/.env + echo "DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }}" > /home/${{ secrets.VPS_USER }}/discord-bot/.env.local + echo "CLIENT_ID=${{ secrets.CLIENT_ID }}" >> /home/${{ secrets.VPS_USER }}/discord-bot/.env.local + echo "NODE_ENV=production" >> /home/${{ secrets.VPS_USER }}/discord-bot/.env.local - name: Extract and restart bot on VPS uses: appleboy/ssh-action@v1.0.3 @@ -71,4 +72,4 @@ jobs: script: | cd /home/${{ secrets.VPS_USER }}/discord-bot/ tar xzf bot-build.tar.gz - pm2 restart bot || pm2 start ./dist/index.js --name bot \ No newline at end of file + pm2 restart bot || pm2 start ./dist/index.js --name bot --node-args="--env-file=.env.local" \ No newline at end of file diff --git a/package.json b/package.json index c66397c..eb01250 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "description": "Web Dev & Web Design discord bot", "type": "module", "scripts": { - "build:ci": "npm run build:ts && npm run build:copy", - "build:dev": "pnpm run build:ts && pnpm run build:copy", + "build:ci": "NODE_ENV=production npm run build:ts && npm run build:copy", + "build:dev": "NODE_ENV=production pnpm run build:ts && pnpm run build:copy", "build:ts": "tsup", "build:copy": "node scripts/copy-assets.js", - "start": "node dist/index.js", + "start": "NODE_ENV=production node dist/index.js", "dev": "tsx watch src/index.ts", "deploy": "tsx src/util/deploy.ts", "lint": "biome lint .", @@ -29,6 +29,7 @@ "dependencies": { "@discordjs/core": "^2.2.2", "discord.js": "^14.22.1", + "typescript": "^5.9.2", "web-features": "^3.3.0" }, "devDependencies": { @@ -37,8 +38,7 @@ "husky": "^9.1.7", "lint-staged": "^16.2.1", "tsup": "^8.5.0", - "tsx": "^4.20.6", - "typescript": "^5.9.2" + "tsx": "^4.20.6" }, "lint-staged": { "*.{ts,js}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1d5162..51e6809 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,7 +38,7 @@ importers: version: 4.20.6 typescript: specifier: ^5.9.2 - version: 5.9.2 + version: 5.9.3 packages: diff --git a/src/commands/guides/index.ts b/src/commands/guides/index.ts index 24e2e67..d8abdf7 100644 --- a/src/commands/guides/index.ts +++ b/src/commands/guides/index.ts @@ -1,13 +1,16 @@ import { ApplicationCommandOptionType, ApplicationCommandType, MessageFlags } from 'discord.js'; import { logToChannel } from '../../util/channel-logging.js'; import { createCommand } from '../../util/commands.js'; -import { loadMarkdownOptions } from '../../util/markdown.js'; - -const subjectsDir = new URL('./subjects/', import.meta.url); +import { loadMarkdownOptions, resolveAssetPath } from '../../util/markdown.js'; const subjectChoices = new Map(); const loadChoices = async (): Promise => { + const subjectsDir = resolveAssetPath( + './subjects/', + './commands/guides/subjects/', + import.meta.url + ); const choices = await loadMarkdownOptions<{ name: string }>(subjectsDir); for (const { frontmatter, content } of choices) { diff --git a/src/commands/tips/index.ts b/src/commands/tips/index.ts index cb86175..ef25d8b 100644 --- a/src/commands/tips/index.ts +++ b/src/commands/tips/index.ts @@ -1,13 +1,12 @@ import { ApplicationCommandOptionType, ApplicationCommandType, MessageFlags } from 'discord.js'; import { logToChannel } from '../../util/channel-logging.js'; import { createCommand } from '../../util/commands.js'; -import { loadMarkdownOptions } from '../../util/markdown.js'; - -const subjectsDir = new URL('./subjects/', import.meta.url); +import { loadMarkdownOptions, resolveAssetPath } from '../../util/markdown.js'; const subjectChoices = new Map(); const loadChoices = async (): Promise => { + const subjectsDir = resolveAssetPath('./subjects/', './commands/tips/subjects/', import.meta.url); const choices = await loadMarkdownOptions<{ name: string }>(subjectsDir); for (const { frontmatter, content } of choices) { diff --git a/src/env.ts b/src/env.ts index 833adcf..b464bab 100644 --- a/src/env.ts +++ b/src/env.ts @@ -16,6 +16,7 @@ export const config = { token: requireEnv('DISCORD_TOKEN'), clientId: requireEnv('CLIENT_ID'), }, + ENV: process.env.NODE_ENV, // Add more config sections as needed: // database: { // url: requireEnv('DATABASE_URL'), diff --git a/src/events/just-ask.ts b/src/events/just-ask.ts index 331859d..37dfd4f 100644 --- a/src/events/just-ask.ts +++ b/src/events/just-ask.ts @@ -1,7 +1,7 @@ import { Events } from 'discord.js'; import { MINUTE } from '../constants/time.js'; import { createEvent } from '../util/events.js'; -import { loadMarkdownOptions } from '../util/markdown.js'; +import { loadMarkdownOptions, resolveAssetPath } from '../util/markdown.js'; import { rateLimit } from '../util/rate-limit.js'; // Subject patterns (who) @@ -26,10 +26,12 @@ const askToAskPattern = new RegExp( const isAskingToAsk = (text: string) => askToAskPattern.test(text); -const [response] = await loadMarkdownOptions<{ name: string }>( - new URL('../commands/tips/subjects/', import.meta.url), - 'justask.md' +const justAskDir = resolveAssetPath( + '../commands/tips/subjects/', + './commands/tips/subjects/', + import.meta.url ); +const [response] = await loadMarkdownOptions<{ name: string }>(justAskDir, 'justask.md'); const { canRun, reset } = rateLimit(10 * MINUTE); diff --git a/src/util/markdown.ts b/src/util/markdown.ts index 13d4880..4d14784 100644 --- a/src/util/markdown.ts +++ b/src/util/markdown.ts @@ -1,5 +1,20 @@ import type { PathLike } from 'node:fs'; import { readdir, readFile } from 'node:fs/promises'; +import { config } from '../env.js'; + +/** + * Resolves a path that works in both dev (src/) and prod (dist/) environments + * + * @param devPath - Path relative to the calling file in dev (e.g., './subjects/') + * @param prodPath - Path relative to dist/index.js in prod (e.g., './commands/tips/subjects/') + * @param baseUrl - import.meta.url from the calling file + * @returns URL that works in both environments + */ +export const resolveAssetPath = (devPath: string, prodPath: string, baseUrl: string): URL => { + const isProduction = config.ENV === 'production'; + const path = isProduction ? prodPath : devPath; + return new URL(path, baseUrl); +}; /** * A simple markdown parser that extracts frontmatter and content diff --git a/tsup.config.ts b/tsup.config.ts index 08340f3..427d803 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,12 +1,21 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - bundle: false, + bundle: true, clean: true, dts: false, outDir: 'dist', format: ['esm'], target: 'esnext', - entry: ['src/**/*.ts', 'src/*.ts'], - minify: true, + entry: ['src/index.ts'], + minify: false, + external: [ + // Don't bundle discord.js - it has dynamic requires that don't work in ESM bundles + 'discord.js', + '@discordjs/core', + // Don't bundle typescript - it has dynamic requires that don't work in ESM bundles + 'typescript', + ], + treeshake: true, + shims: true, });