create-seedcord 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-seedcord",
3
3
  "type": "module",
4
- "version": "0.4.2",
4
+ "version": "0.5.0",
5
5
  "description": "Scaffold a new seedcord bot",
6
6
  "keywords": [
7
7
  "discord",
@@ -12,7 +12,7 @@
12
12
  "starter"
13
13
  ],
14
14
  "engines": {
15
- "node": ">=24.11",
15
+ "node": "^24.22.1 || >=26.12.1",
16
16
  "bun": ">=1.4.2"
17
17
  },
18
18
  "bin": {
@@ -40,15 +40,15 @@
40
40
  "LICENSE"
41
41
  ],
42
42
  "devDependencies": {
43
- "@clack/prompts": "^1.7.0",
44
- "@seedcord/errors": "^0.13.0",
45
- "@seedcord/eslint-config": "^2.5.0",
46
- "@seedcord/tsconfig": "^2.0.6",
43
+ "@seedcord/cli-prompts": "^0.0.2",
44
+ "@seedcord/errors": "^0.14.0",
45
+ "@seedcord/eslint-config": "^3.0.0",
46
+ "@seedcord/tsconfig": "^3.0.0",
47
47
  "@seedcord/tsdown-config": "^2.0.1",
48
- "@seedcord/types": "^0.14.4",
48
+ "@seedcord/types": "^0.15.0",
49
49
  "@seedcord/vitest-config": "^0.0.1",
50
50
  "discord.js": "^14.27.0",
51
- "handlebars": "^4.7.9",
51
+ "handlebars": "^4.7.10",
52
52
  "package-manager-detector": "^1.8.0",
53
53
  "tsx": "^4.23.15",
54
54
  "typescript": "^6.0.3"
@@ -70,5 +70,5 @@
70
70
  "test:watch": "vitest dev",
71
71
  "coverage": "vitest run --coverage"
72
72
  },
73
- "readme": "<div align=\"center\">\n <picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://cdn.seedcord.org/assets/wordmark-dark.webp\" />\n <img src=\"https://cdn.seedcord.org/assets/wordmark-light.webp\" alt=\"seedcord\" width=\"440\" />\n </picture>\n</div>\n\n<div align=\"center\">\n <h3>The whole Discord bot, typed end to end</h3>\n <a href=\"https://seedcord.org\">Website</a> ·\n <a href=\"https://seedcord.org/guide\">Guide</a> ·\n <a href=\"https://seedcord.org/docs\">Reference</a> ·\n <a href=\"https://discord.gg/DzFxY58WXf\">Discord</a>\n</div>\n\n<br />\n\n<div align=\"center\">\n\n[![npm](https://img.shields.io/npm/v/create-seedcord?style=flat-square&label=npm&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/create-seedcord) [![node](https://img.shields.io/node/v/create-seedcord?style=flat-square&label=node&labelColor=1f1f1f&color=4d7d33)](https://nodejs.org) [![bun](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fseedcord%2Fseedcord%2Fnext%2Fcli%2Fcreate-seedcord%2Fpackage.json&query=%24.engines.bun&style=flat-square&label=bun&labelColor=1f1f1f&color=fbf0df)](https://bun.com) [![license](https://img.shields.io/npm/l/create-seedcord?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)\n\n</div>\n\n## About\n\n`create-seedcord` scaffolds a new seedcord bot project.\n\nUntil v1.0.0, minor versions can break.\n\n## Usage\n\n```sh\npnpm create seedcord my-bot\nnpm create seedcord my-bot\nyarn create seedcord my-bot\n```\n\nIt asks where the project goes, how Discord reaches your bot, what the bot should react to, your bot token, your app public key on http, and an accent color. Then it writes the project, installs dependencies, formats, generates your command types, and makes the first commit. If a step after the write fails, you get a warning and the summary still prints.\n\nIf you don't have the token or key yet, press Enter twice on its prompt to skip it. Fill it into `.env` before you start the bot.\n\nOn Windows, run it from Windows Terminal. The prompts fall back to ASCII in `cmd.exe` making the boxes draw as `T`, `|`, and `o`.\n\n## Flags\n\nEvery question has a flag. Pass them all to skip all questions. `--help` prints the list.\n\nnpm forwards flags to the package only after a `--`:\n\n```sh\nnpm create seedcord my-bot -- --transport gateway --capabilities reactions\npnpm create seedcord my-bot --transport gateway --capabilities reactions\n```\n\n`--no-install` and `--no-git` turn off those two steps. `--no-token` and `--no-public-key` leave those keys empty without asking. `-v` prints the version and `-h` prints the flag list.\n\nWith no terminal to ask on, it reads the flags alone. It prints the flag that would supply any answer you left out.\n\n## What you get\n\nA TypeScript project holding one slash command, its handler, a sample event handler (if you picked `gateway`), `seedcord.config.ts`, eslint, prettier, and a `.env` listing every key the framework reads.\n\n`pnpm dev` starts it with hot reload. On the http transport it also opens a cloudflared tunnel and sets it up, and Discord posts interactions to that URL.\n"
73
+ "readme": "<div align=\"center\">\n <picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://cdn.seedcord.org/assets/wordmark-dark.webp\" />\n <img src=\"https://cdn.seedcord.org/assets/wordmark-light.webp\" alt=\"seedcord\" width=\"440\" />\n </picture>\n</div>\n\n<div align=\"center\">\n <h3>The whole Discord bot, typed end to end</h3>\n <a href=\"https://seedcord.org\">Website</a> ·\n <a href=\"https://seedcord.org/guide\">Guide</a> ·\n <a href=\"https://seedcord.org/docs\">Reference</a> ·\n <a href=\"https://seedcord.org/chat/\">Discord</a>\n</div>\n\n<br />\n\n<div align=\"center\">\n\n[![npm](https://img.shields.io/npm/v/create-seedcord?style=flat-square&label=npm&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/create-seedcord) [![node](https://img.shields.io/node/v/create-seedcord?style=flat-square&label=node&labelColor=1f1f1f&color=4d7d33)](https://nodejs.org) [![bun](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fseedcord%2Fseedcord%2Fnext%2Fcli%2Fcreate-seedcord%2Fpackage.json&query=%24.engines.bun&style=flat-square&label=bun&labelColor=1f1f1f&color=fbf0df)](https://bun.com) [![license](https://img.shields.io/npm/l/create-seedcord?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)\n\n</div>\n\n## About\n\n`create-seedcord` sets up a new seedcord bot. It asks a few questions, writes the project, and can install its dependencies and make the first git commit. You run it once, when you start a new bot.\n\nseedcord is pre-1.0, so a minor version can break your bot. Each release's changelog says what changed and how to update.\n\n## Usage\n\n```sh\npnpm create seedcord my-bot\n```\n\n`npm create seedcord`, `yarn create seedcord`, and `bun create seedcord` work too, and the project uses that package manager.\n\nIt asks whether your bot is a gateway or an http bot, where an http bot runs, what a gateway bot reacts to, your bot token, and the servers to test your commands in. The [first bot guide](https://seedcord.org/guide/first-bot) walks through each question.\n\nEvery question has a flag, and `--help` prints them all. npm passes flags on only after a `--`:\n\n```sh\nnpm create seedcord my-bot -- --transport gateway --capabilities reactions\n```\n\n## What you get\n\nA TypeScript project with one slash command, its handler, a sample plugin, `seedcord.config.ts`, ESLint, Prettier, and a `.env` for your token. A gateway bot also gets a sample event handler. An http bot on Cloudflare Workers also gets a `cloudflare.config.ts` and a `deploy` script.\n\n`pnpm dev` starts the bot with hot reload.\n"
74
74
  }
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "files.watcherExclude": {
3
- "**/seedcord-gen.d.ts": true
3
+ "**/seedcord-gen.d.ts": true,
4
+ "**/.seedcord/**": true
4
5
  },
5
6
  "search.exclude": {
6
7
  "**/seedcord-gen.d.ts": true
7
8
  },
8
9
  "files.readonlyInclude": {
9
- "**/seedcord-gen.d.ts": true
10
+ "**/seedcord-gen.d.ts": true,
11
+ "**/.seedcord/**": true
10
12
  }
11
13
  }
@@ -10,7 +10,7 @@ Rules for an AI coding agent working in this repo.
10
10
 
11
11
  The API reference is at <https://seedcord.org/docs>, one page per symbol.
12
12
 
13
- This project runs on the {{#if isGateway}}gateway{{else}}http{{/if}} transport and imports everything from `{{ transportPackage }}`. A page marks anything that differs between the two.
13
+ This project runs on the {{#if isGateway}}gateway{{else}}http{{/if}} transport{{#if isEdge}} on Cloudflare Workers{{/if}} and imports everything from `{{ transportPackage }}`. A page marks anything that differs between the two transports.
14
14
 
15
15
  ## How a command works here
16
16
 
@@ -35,7 +35,7 @@ Run `{{ pm.run }} codegen` after you add a command or change its options. It wri
35
35
 
36
36
  ## Connections and state
37
37
 
38
- Anything that holds a connection, keeps state between interactions, or runs in the background belongs in a plugin, either an existing one or your own. Read <https://seedcord.org/guide/plugins.md> and <https://seedcord.org/guide/plugins/your-own.md> before you add one.
38
+ Anything that holds a connection, keeps state between interactions, or runs in the background belongs in a plugin, either an existing one or your own. Read <https://seedcord.org/guide/plugins.md> and <https://seedcord.org/guide/writing-plugins.md> before you add one.
39
39
 
40
40
  ## Imports
41
41
 
@@ -1,6 +1,6 @@
1
1
  # {{ projectName }}
2
2
 
3
- A Discord bot built with [seedcord](https://seedcord.org){{#if isGateway}} on the gateway transport{{else}} on the http transport{{/if}}.
3
+ A Discord bot built with [seedcord](https://seedcord.org){{#if isGateway}} on the gateway transport{{else}} on the http transport{{#if isEdge}}, running on Cloudflare Workers{{/if}}{{/if}}.
4
4
 
5
5
  ## Running it
6
6
 
@@ -17,11 +17,22 @@ That starts the dev server with hot reload{{#unless isGateway}} and opens a tunn
17
17
  | script | what it does |
18
18
  | --- | --- |
19
19
  | `{{ pm.run }} dev` | dev server with hot reload |
20
+ {{#if isEdge}}
21
+ | `{{ pm.run }} build` | build the worker into `.cloudflare/output/` |
22
+ | `{{ pm.run }} deploy` | build, then upload the worker with `cf deploy` |
23
+ {{else}}
20
24
  | `{{ pm.run }} build` | compile to `dist/` |
21
25
  | `{{ pm.run }} start` | run the compiled build |
26
+ {{/if}}
22
27
  | `{{ pm.run }} codegen` | regenerate `src/seedcord-gen.d.ts` |
23
- | `{{ pm.run }} lint` | eslint |
28
+ {{#if isEdge}}
29
+ | `{{ pm.run }} types` | regenerate the binding types in `.cloudflare/types/` |
30
+ {{/if}}
31
+ | `{{ pm.run }} commands` | list, deploy, or clean up your commands on Discord |
32
+ | `{{ pm.run }} commands:deploy` | send your commands to Discord |
24
33
  | `{{ pm.run }} tc` | type-check |
34
+ | `{{ pm.run }} lint` | eslint |
35
+ | `{{ pm.run }} check` | type-check, lint, and check formatting |
25
36
 
26
37
  <!-- prettier-ignore-end -->
27
38
 
@@ -36,16 +47,28 @@ Run `codegen` after you add a command or change its options. It writes the types
36
47
  | key | required |
37
48
  | --- | --- |
38
49
  | `DISCORD_BOT_TOKEN` | yes |
39
- {{#unless isGateway}}
40
- | `DISCORD_PUBLIC_KEY` | yes |
41
- {{/unless}}
42
50
  | `UNKNOWN_EXCEPTION_WEBHOOK_URL` | no |
43
51
  | `HANDLED_EXCEPTION_WEBHOOK_URL` | no |
52
+ | `TESTING_GUILD_IDS` | no, empty deploys your commands globally |
53
+ {{#unless isEdge}}
44
54
  | `ENV` | no, defaults to `development` |
55
+ {{/unless}}
45
56
 
46
57
  <!-- prettier-ignore-end -->
47
58
 
48
59
  The two webhook keys take Discord webhook URLs. Set them to get error reports posted to a channel.
60
+ {{#if isEdge}}
61
+
62
+ A worker gets only the keys that `cloudflare.config.ts` declares. Add a line there for each new one your bot reads. `{{ pm.run }} dev` takes their values from `.env`.
63
+
64
+ Cloudflare refuses to deploy a new worker until every declared secret has a value. Put the production values in a file like `.env.production`, then deploy the first time with:
65
+
66
+ ```sh
67
+ {{ pm.run }} build && {{ pm.exec }} cf deploy --prebuilt --secrets-file .env.production
68
+ ```
69
+
70
+ After that, `{{ pm.run }} deploy` is enough. `TESTING_GUILD_IDS` can stay empty there, since a deployed worker never deploys commands.
71
+ {{/if}}
49
72
 
50
73
  ## Where things go
51
74
 
@@ -1,4 +1,4 @@
1
- {{#if pm.isBun}}
1
+ {{#if cliOnBun}}
2
2
  [run]
3
3
  bun = true
4
4
  {{/if}}
@@ -0,0 +1,18 @@
1
+ {{#if isEdge}}
2
+ import { bindings, defineConfig } from 'cf/config';
3
+
4
+ // cf workers types calls this without a mode
5
+ export default defineConfig(({ mode = 'production' }) => ({
6
+ worker: {
7
+ name: '{{ projectName }}',
8
+ compatibilityDate: '2026-08-04',
9
+ env: {
10
+ DISCORD_BOT_TOKEN: bindings.secret(),
11
+ UNKNOWN_EXCEPTION_WEBHOOK_URL: bindings.secret(),
12
+ HANDLED_EXCEPTION_WEBHOOK_URL: bindings.secret(),
13
+ TESTING_GUILD_IDS: bindings.secret(),
14
+ ENV: bindings.text(mode)
15
+ }
16
+ }
17
+ }));
18
+ {{/if}}
package/templates/env.hbs CHANGED
@@ -1,13 +1,13 @@
1
1
  # Your bot token, from the Bot page of your app in the Discord Developer Portal
2
2
  DISCORD_BOT_TOKEN={{ token }}
3
- {{#unless isGateway}}
4
-
5
- # Public key, from the General Information page of the same app
6
- DISCORD_PUBLIC_KEY={{ publicKey }}
7
- {{/unless}}
8
3
 
9
4
  # Discord webhooks the framework posts error reports to. Leave empty to skip reporting.
10
5
  UNKNOWN_EXCEPTION_WEBHOOK_URL=
11
6
  HANDLED_EXCEPTION_WEBHOOK_URL=
12
7
 
8
+ # Comma-separated ids of the servers your commands deploy to while you build. Leave empty to deploy globally.
9
+ TESTING_GUILD_IDS={{ testingGuildIds }}
10
+ {{#unless isEdge}}
11
+
13
12
  ENV=development
13
+ {{/unless}}
@@ -0,0 +1,3 @@
1
+ * text=auto eol=lf
2
+
3
+ seedcord-gen.d.ts linguist-generated
@@ -1,12 +1,18 @@
1
1
  node_modules/
2
2
  dist/
3
- logs/
3
+ /logs/
4
4
  *.log
5
5
  *.tsbuildinfo
6
6
  .eslintcache
7
7
  .seedcord/
8
+ {{#if isEdge}}
9
+ .cloudflare/
10
+ {{/if}}
8
11
 
9
12
  .env*
10
13
  !.env.example
14
+ {{#if isEdge}}
15
+ .dev.vars*
16
+ {{/if}}
11
17
 
12
18
  .DS_Store
@@ -4,17 +4,28 @@
4
4
  "version": "0.1.0",
5
5
  "private": true,
6
6
  "engines": {
7
- {{#if pm.isBun}}"bun": "{{ engines.bun }}"{{else}}"node": "{{ engines.node }}"{{/if}}
7
+ {{#if cliOnBun}}"bun": "{{ engines.bun }}"{{else}}"node": "{{ engines.node }}"{{/if}}
8
8
  },
9
9
  "scripts": {
10
10
  "dev": "seedcord dev",
11
11
  "build": "seedcord build",
12
+ {{#if isEdge}}
13
+ "deploy": "seedcord build && cf deploy --prebuilt",
14
+ {{else}}
12
15
  "start": "node --enable-source-maps dist/index.mjs",
16
+ {{/if}}
13
17
  "codegen": "seedcord codegen",
14
- "lint": "eslint 'src/**/*.ts' --cache",
15
- "lint:fix": "eslint 'src/**/*.ts' --fix --cache",
16
- "fmt": "prettier --write 'src/**/*.{ts,json,md}' --cache",
17
- "fmt:check": "prettier --check 'src/**/*.{ts,json,md}' --cache",
18
- "tc": "tsc --noEmit"
18
+ {{#if isEdge}}
19
+ "types": "cf workers types",
20
+ {{/if}}
21
+ "commands": "seedcord commands",
22
+ "commands:deploy": "seedcord commands deploy",
23
+ "tc": "tsc --noEmit",
24
+ "lint": "eslint \"src/**/*.ts\" --cache",
25
+ "lint:fix": "eslint \"src/**/*.ts\" --fix --cache",
26
+ "fmt": "prettier --write \"src/**/*.{ts,json,md}\" --cache",
27
+ "fmt:check": "prettier --check \"src/**/*.{ts,json,md}\" --cache",
28
+ "check": "{{ pm.run }} tc && {{ pm.run }} lint && {{ pm.run }} fmt:check"{{#if isEdge}},
29
+ "postinstall": "cf workers types"{{/if}}
19
30
  }
20
31
  }
@@ -0,0 +1,7 @@
1
+ {{#if pm.isPnpm}}
2
+ allowBuilds:
3
+ unrs-resolver: false
4
+ {{#if isEdge}}
5
+ workerd: true
6
+ {{/if}}
7
+ {{/if}}
@@ -3,12 +3,11 @@ import { defineConfig } from 'seedcord';
3
3
  export default defineConfig({
4
4
  root: './src',
5
5
  instance: './bot.ts',
6
- entry: './index.ts',
6
+ {{#unless isEdge}}
7
+ build: { start: './index.ts' },
8
+ {{/unless}}
7
9
  // set false to hold the running arc and live dot still, which cuts idle redraws by ~80%
8
10
  idleAnimation: true,
9
- build: {
10
- tsconfig: './tsconfig.build.json'
11
- },
12
11
  hmr: {
13
12
  // set true to run tsc --watch alongside the bot, at the cost of a second node process
14
13
  typecheck: false
@@ -1,14 +1,12 @@
1
- import { resolve } from 'node:path';
2
-
3
1
  {{#if isGateway}}
4
2
  import { Seedcord } from '@seedcord/gateway';
5
3
  import { GatewayIntentBits{{#if partials}}, Partials{{/if}} } from 'discord.js';
6
4
  {{else}}
7
5
  import { Seedcord } from '@seedcord/http';
8
6
  {{/if}}
9
- import { Envapter } from 'envapt';
7
+ import { Converters, Envapter } from 'envapt';
10
8
 
11
- Envapter.baseDir = resolve(import.meta.dirname, '..');
9
+ import { Uptime } from '#plugins/Uptime'
12
10
 
13
11
  export const seedcord = new Seedcord({
14
12
  bot: {
@@ -23,13 +21,14 @@ export const seedcord = new Seedcord({
23
21
  },
24
22
  {{/if}}
25
23
  interactions: {
26
- path: resolve(import.meta.dirname, './handlers')
24
+ path: './handlers'
27
25
  },
28
26
  commands: {
29
- path: resolve(import.meta.dirname, './commands')
27
+ path: './commands',
28
+ guilds: Envapter.getAs('TESTING_GUILD_IDS', Converters.array(), [])
30
29
  }{{#if isGateway}},
31
30
  events: {
32
- path: resolve(import.meta.dirname, './events')
31
+ path: './events'
33
32
  }{{/if}}
34
33
  },
35
34
  subscribers: {
@@ -39,6 +38,6 @@ export const seedcord = new Seedcord({
39
38
  notifications: {
40
39
  developerUsername: '{{ developerUsername }}'
41
40
  }
42
- });
41
+ }).attach('uptime', Uptime);
43
42
 
44
43
  export default seedcord;
@@ -1,6 +1,6 @@
1
1
  import { BuilderComponent, RegisterCommand } from '{{ transportPackage }}';
2
2
 
3
- @RegisterCommand('global')
3
+ @RegisterCommand()
4
4
  export class Ping extends BuilderComponent<'command'> {
5
5
  constructor() {
6
6
  super('command');
@@ -1,4 +1,4 @@
1
- import { SlashHandler, SlashRoute, timestampFromSnowflake } from '{{ transportPackage }}';
1
+ import { SlashHandler, SlashRoute, timestampFromSnowflake, toEpochSeconds } from '{{ transportPackage }}';
2
2
 
3
3
  @SlashRoute('ping')
4
4
  export class Ping extends SlashHandler<'ping'> {
@@ -7,7 +7,7 @@ export class Ping extends SlashHandler<'ping'> {
7
7
  const lines = ['### Pong', `Roundtrip **${roundtrip}ms**`];
8
8
 
9
9
  if (this.options.getBoolean('detailed')) {
10
- lines.push(`Uptime **${Math.round(process.uptime())}s**`);
10
+ lines.push(`Up since **<t:${toEpochSeconds(this.core.uptime.startedAt)}:R>**`);
11
11
  }
12
12
 
13
13
  await this.reply(lines.join('\n'));
@@ -1,3 +1,5 @@
1
+ {{#unless isEdge}}
1
2
  import seedcord from './bot';
2
3
 
3
4
  await seedcord.start();
5
+ {{/unless}}
@@ -0,0 +1,16 @@
1
+ import { Plugin } from '{{ transportPackage }}';
2
+
3
+ import type { EpochMs } from '{{ transportPackage }}';
4
+
5
+ export class Uptime extends Plugin {
6
+ public startedAt = 0 as EpochMs;
7
+
8
+ public async init(): Promise<void> {
9
+ this.startedAt = Date.now() as EpochMs;
10
+ await Promise.resolve();
11
+ }
12
+
13
+ public elapsed(): number {
14
+ return Date.now() - this.startedAt;
15
+ }
16
+ }
@@ -3,8 +3,24 @@
3
3
  "extends": "@seedcord/tsconfig/node",
4
4
  "compilerOptions": {
5
5
  "rootDir": "./",
6
- "outDir": "./dist"
6
+ "outDir": "./dist",
7
+ "types": ["node", "vite/client"],
8
+ {{#if isEdge}}
9
+ "customConditions": ["workerd"],
10
+ {{/if}}
11
+ "paths": {
12
+ "#src/*": ["./src/*"],
13
+ "#components/*": ["./src/components/*"],
14
+ "#plugins/*": ["./src/plugins/*"]
15
+ }
7
16
  },
8
- "include": ["src/**/*.ts", "eslint.config.ts", "prettier.config.ts", "seedcord.config.ts"],
17
+ "include": [
18
+ "src/**/*.ts",
19
+ "eslint.config.ts",
20
+ "prettier.config.ts",
21
+ "seedcord.config.ts"{{#if isEdge}},
22
+ "cloudflare.config.ts",
23
+ ".cloudflare/types/index.d.ts"{{/if}}
24
+ ],
9
25
  "exclude": ["node_modules", "dist"]
10
26
  }