Skip to content

Data files

Ship JSON, images, and other non-code files with your bot by listing their folders in files, then read them with readFiles in dev and in production.

You keep translations in src/locales/en.json and src/locales/de.json, plus a card background in assets/card.png beside src. You read them with fs.readFile. That worked under seedcord dev and broke after seedcord build. The build only keeps files something imports, so it left out the JSON and the PNG. A path built from import.meta.dirname also pointed into dist, where those files never were.

To ship the JSON and the PNG, list their folders under files in seedcord.config.ts. Those folders go into the build with your code:

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

export default defineConfig({
    root: './src',
    instance: './bot.ts',
    build: { start: './index.ts' },
    files: ['./locales']
});

'./locales' resolves against root, so it means src/locales, subfolders included. A folder beside root works too, like '../assets' for assets/card.png, as long as it's inside the folder that holds seedcord.config.ts.

Reading data files

readFiles reads those folders in dev and in a built bot alike, so you never build the path yourself. It yields one object per file, with the contents in bytes as a Buffer. @seedcord/gateway and @seedcord/http both export it:

src/bot.tshover for typestap for types, arrow keys walk the tokens
import { function basename(path: string, suffix?: string): stringbasename } from 'node:path';

import {
    function readFiles(dir: string): AsyncGenerator<ReadFile>readFiles,
    class SeedcordSeedcord,
    enum StartupPhaseStartupPhase
} from '@seedcord/gateway';
import { enum GatewayIntentBitsGatewayIntentBits } from 'discord.js';

const const bot: Seedcordbot = new new Seedcord(config: GatewayConfig): SeedcordSeedcord({
    GatewayConfig.bot: GatewayBotConfigbot: {
        GatewayBotConfig.clientOptions: ClientOptionsclientOptions: { 
ClientOptions.intents: BitFieldResolvable<
    | "Guilds"
    | "GuildMembers"
    | "GuildModeration"
    | "GuildBans"
    | "GuildExpressions"
    | "GuildEmojisAndStickers"
    | "GuildIntegrations"
    | "GuildWebhooks"
    | "GuildInvites"
    | "GuildVoiceStates"
    | "GuildPresences"
    | "GuildMessages"
    | "GuildMessageReactions"
    | "GuildMessageTyping"
    | "DirectMessages"
    | "DirectMessageReactions"
    | "DirectMessageTyping"
    | "MessageContent"
    | "GuildScheduledEvents"
    | "AutoModerationConfiguration"
    | "AutoModerationExecution"
    | "GuildMessagePolls"
    | "DirectMessagePolls",
    number
>
intents
: [enum GatewayIntentBitsGatewayIntentBits.function (enum member) GatewayIntentBits.Guilds = 1Guilds] },
BotConfig.interactions: InteractionsConfiginteractions: { path: stringpath: './handlers' }, BotConfig.commands: CommandsConfigcommands: { path: stringpath: './commands' }, GatewayBotConfig.events: EventsConfigevents: { path: nullpath: null } }, Config.subscribers: SubscribersConfigsubscribers: { path: nullpath: null } }); export const const locales: Map<string, Record<string, string>>locales = new
var Map: MapConstructor
new <string, Record<string, string>>(iterable?: Iterable<readonly [string, Record<string, string>]> | null | undefined) => Map<string, Record<string, string>> (+3 overloads)
Map
<string, type Record<K extends keyof any, T> = { [P in K]: T }Record<string, string>>();
const bot: Seedcordbot.ServerHost<"gateway">.startup: Pick<CoordinatedStartup, "addTask">startup.
function addTask(
    phase: StartupPhase,
    taskName: string,
    task: LifecycleTask["task"],
    timeoutMs?: number
): void
addTask
(
enum StartupPhaseStartupPhase.function (enum member) StartupPhase.Configuration = 1Configuration, 'load locales', async () => { for await (const const file: ReadFilefile of function readFiles(dir: string): AsyncGenerator<ReadFile>readFiles('./locales')) { const locales: Map<string, Record<string, string>>locales.
Map<string, Record<string, string>>.set(
    key: string,
    value: Record<string, string>
): Map<string, Record<string, string>>
set
(
function basename(path: string, suffix?: string): stringbasename(const file: ReadFilefile.FoundFile.fullPath: stringfullPath, '.json'), var JSON: JSONJSON.
JSON.parse(
    text: string,
    reviver?: (this: any, key: string, value: any) => any
): any
parse
(const file: ReadFilefile.ReadFile.bytes: Buffer<ArrayBufferLike>bytes.
Buffer<ArrayBufferLike>.toString(
    encoding?: BufferEncoding,
    start?: number,
    end?: number
): string
toString
())
); } } ); export default const bot: Seedcordbot;

Besides bytes and fullPath, each file has a relativePath for logs. Files come one at a time, sorted by path.

Warning

Keep the loop in code that runs after the bot starts, like a startup task, a plugin's init(), or a handler. seedcord codegen and the build's load bot step import bot.ts to read your commands without starting it, and they don't set root. A for await over readFiles('./locales') at the top of bot.ts stops both with CoreImportFailed.

When readFiles throws

readFiles throws before it reads anything if it can't use the folder you pass:

you passit throwsthe fix
a folder files doesn't list, like './other'CoreDirectoryNotListedadd it to files
a folder the build skips, like './locales/.cache'CoreDirectorySkippedmove the files out of the dot-folder
a relative path in a test or tsx runCoreDirectoryRootUnknownpass an absolute path, or run the bot through seedcord dev or the build

The build skips dotfiles and dot-folders, node_modules, outDir, and the logs folder beside seedcord.config.ts. It also skips project files there, like package.json, tsconfig.json, and your lockfile. Keep data files out of all of those.

The CLI checks files too. Every command throws CliListedFolderUnusable for a listed folder outside the config's folder, and CoreDirectorySkipped for one the build skips. seedcord build also throws CliListedFolderUnusable for a listed folder that's missing or is a file.

Code files and plugin files

You don't list code in files. The build includes every .ts, .tsx, .mts, .js, .jsx, and .mjs file under root. readFiles leaves those files out, even inside a listed folder. seedcord loads your handler folders through importFiles, which imports every code file in a folder.

A plugin you publish can't use files, because the bot that installs the plugin writes that list. Import the plugin's data files from its own code, like ./defaults.json, so the plugin's build includes them in the package. Loading a bot's files covers a plugin that reads the bot's own folders.