Skip to content

Building

Compile your bot for production with seedcord build. Covers the steps it runs, starting the output, its config keys, build failures, the runtime key, and the health server your host calls.

Node can't run your src folder in production as it is. Your handlers use decorators, and node's built-in TypeScript support stops with a parser error on decorator syntax. Running tsc yourself compiles the decorators, but node then fails on any extensionless import or path alias that tsc copied into the output as written.

seedcord build compiles the decorators and rewrites those imports, turning src into a dist folder that plain node can run. It doesn't take flags, so everything it needs comes from seedcord.config.ts.

pnpm run build

It starts by checking that your entry file exists, and a missing one throws CliEntryNotFound. After that check it runs these steps in order.

  1. Your project's own tsc compiles the project. seedcord always passes --inlineSources, and adds --sourceMap unless your tsconfig already sets inlineSourceMap. Both give you readable stack traces in production.
  2. tsc-alias rewrites every import specifier in the output to a full relative path ending in .js, since TypeScript emits each one exactly as you wrote it.
  3. seedcord writes a bootstrap file that holds a single import of the compiled entry.

seedcord expects the compiled entry under outDir at the same path it has under root, so src/index.ts becomes dist/index.js. The bootstrap file always has one fixed path, so your start script can point at it however you arrange your source.

Running what it built

pnpm run start

The scaffold sets that script to run dist/index.mjs with node's --enable-source-maps flag. Without --enable-source-maps, a stack trace points at lines in the compiled .js files, and the flag maps them back to your .ts lines.

The build keys

Your CLI config holds these under build, all of them optional.

seedcord.config.ts
import { defineConfig } from 'seedcord';

export default defineConfig({
    root: './src',
    instance: './bot.ts',
    entry: './index.ts',
    build: {
        outDir: './dist',
        tsconfig: './tsconfig.build.json',
        bootstrap: 'index.mjs'
    }
});

outDir defaults to ./dist beside your config, and tsconfig resolves from the same folder.

A relative bootstrap resolves against outDir, which turns index.mjs into dist/index.mjs. That's also the default.

With a separate tsconfig.build.json you exclude tests and scripts from the build while your editor still type-checks them. Without tsconfig, seedcord looks for tsconfig.build.json first, then tsconfig.json. When neither exists, or the path you set doesn't, it throws CliBuildTsconfigNotFound.

When the build fails

A type error stops the build. seedcord throws CliBuildFailed with the compiler's own output, shortened to its first 24,000 characters. seedcord runs the tsc from your project's own typescript, so your build uses the compiler version you pinned, and a project missing that dependency gets CliBuildFailed too.

Once tsc finishes, seedcord checks that the compiled entry exists at the path above. Sometimes the compiler reports success while that file is still missing. You get CliBuildFailed again, with the path seedcord checked.

Two things usually cause this. Your tsconfig may exclude the entry file, or its rootDir may place the output somewhere other than the path under outDir that matches root.

Where your bot runs

You set runtime on your bot's own config in src/bot.ts, beside bot and subscribers. It says which kind of deployment your bot is built for. Leaving it out means 'server'.

src/bot.ts
import { resolve } from 'node:path';

import { Seedcord } from '@seedcord/http';

export const seedcord = new Seedcord({
    bot: {
        interactions: {
            path: resolve(import.meta.dirname, './handlers')
        },
        commands: { path: null }
    },
    subscribers: { path: null },
    runtime: 'edge'
});

An edge bot has to write runtime: 'edge' itself.

Gateway and http differ

A gateway bot only accepts 'server', since its websocket needs a process that stays up. An http bot takes 'server' for a long-running node process, or 'edge' for a bundled isolate like a Cloudflare Worker.

seedcord build doesn't read runtime yet, so it compiles both kinds of bot the same way.

The health server

Most hosting platforms call an http endpoint to check that your process is still alive. A gateway bot opens a small server that answers /health on port 6967 once it reaches the Ready phase, and it does this under seedcord dev too.

src/bot.ts
import { resolve } from 'node:path';

import { Seedcord } from '@seedcord/gateway';
import { GatewayIntentBits } from 'discord.js';

export const seedcord = new Seedcord({
    bot: {
        clientOptions: { intents: [GatewayIntentBits.Guilds] },
        ...paths
    },
    subscribers: { path: null },
    healthCheck: { port: 8080, path: '/healthz' }
});

This bot answers /healthz on port 8080. All three keys are optional. Without port the server uses 6967, and without path it answers /health. Without host it listens on every network interface. healthCheck: false turns the server off.

Change port or path when your host expects its health check somewhere else, and set host when the server should only listen inside a container network. Turn it off when nothing calls it, or when port 6967 is already taken on the machine.

Gateway only

An http bot's config doesn't accept healthCheck, because an http bot already answers on a port of its own.