create-seedcord 0.5.0 → 0.6.0-next.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.5.0",
4
+ "version": "0.6.0-next.0",
5
5
  "description": "Scaffold a new seedcord bot",
6
6
  "keywords": [
7
7
  "discord",
@@ -34,18 +34,22 @@
34
34
  "files": [
35
35
  "bin",
36
36
  "dist",
37
+ "starters",
37
38
  "templates",
38
39
  "package.json",
39
40
  "README.md",
40
41
  "LICENSE"
41
42
  ],
42
43
  "devDependencies": {
43
- "@seedcord/cli-prompts": "^0.0.2",
44
- "@seedcord/errors": "^0.14.0",
45
- "@seedcord/eslint-config": "^3.0.0",
44
+ "@seedcord/brand": "^0.0.0",
45
+ "@seedcord/cli-prompts": "^0.0.3-next.0",
46
+ "@seedcord/cli-templates": "^0.0.1-next.0",
47
+ "@seedcord/core": "^0.12.0-next.0",
48
+ "@seedcord/errors": "^0.15.0-next.0",
49
+ "@seedcord/eslint-config": "^3.0.1-next.0",
46
50
  "@seedcord/tsconfig": "^3.0.0",
47
51
  "@seedcord/tsdown-config": "^2.0.1",
48
- "@seedcord/types": "^0.15.0",
52
+ "@seedcord/types": "^0.16.0-next.0",
49
53
  "@seedcord/vitest-config": "^0.0.1",
50
54
  "discord.js": "^14.27.0",
51
55
  "handlebars": "^4.7.10",
@@ -0,0 +1,9 @@
1
+ import { EventHandler, RegisterEvent } from '{{ transport }}';
2
+ import { Events } from 'discord.js';
3
+
4
+ @RegisterEvent([Events.{{ event }}])
5
+ export class {{ pascal name }} extends EventHandler<Events.{{ event }}> {
6
+ public async execute(): Promise<void> {
7
+ this.logger.info('{{ pascal name }} ran');
8
+ }
9
+ }
@@ -0,0 +1,18 @@
1
+ import { ButtonBuilder } from '@discordjs/builders';
2
+ import { CustomId, RowComponent } from '{{ transport }}';
3
+ import { ButtonStyle } from 'discord-api-types/v10';
4
+
5
+ export const {{ pascal name }}Id = new CustomId('{{ kebab name }}');
6
+
7
+ export class {{ pascal name }}Row extends RowComponent<'button'> {
8
+ constructor() {
9
+ super('button');
10
+
11
+ this.instance.addComponents(
12
+ new ButtonBuilder()
13
+ .setCustomId({{ pascal name }}Id.encode({}))
14
+ .setLabel({{ quote label }})
15
+ .setStyle(ButtonStyle.Primary)
16
+ );
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ import { ButtonHandler, ButtonRoute } from '{{ transport }}';
2
+
3
+ import { {{ pascal name }}Id } from '{{ importOf "components" }}';
4
+
5
+ @ButtonRoute({{ pascal name }}Id)
6
+ export class {{ pascal name }}Button extends ButtonHandler<[typeof {{ pascal name }}Id]> {
7
+ public async execute(): Promise<void> {
8
+ await this.reply({{ quote reply }});
9
+ }
10
+ }
@@ -0,0 +1,19 @@
1
+ import { BuilderComponent, CustomId } from '{{ transport }}';
2
+ import { TextInputStyle } from 'discord-api-types/v10';
3
+
4
+ export const {{ pascal name }}Id = new CustomId('{{ kebab name }}');
5
+
6
+ export class {{ pascal name }}Modal extends BuilderComponent<'modal'> {
7
+ constructor() {
8
+ super('modal');
9
+
10
+ this.instance
11
+ .setCustomId({{ pascal name }}Id.encode({}))
12
+ .setTitle({{ quote title }})
13
+ .addLabelComponents((label) =>
14
+ label
15
+ .setLabel({{ quote question }})
16
+ .setTextInputComponent((input) => input.setCustomId('answer').setStyle(TextInputStyle.Short))
17
+ );
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ import { ModalHandler, ModalRoute } from '{{ transport }}';
2
+
3
+ import { {{ pascal name }}Id } from '{{ importOf "components" }}';
4
+
5
+ @ModalRoute({{ pascal name }}Id)
6
+ export class {{ pascal name }}Submit extends ModalHandler<[typeof {{ pascal name }}Id]> {
7
+ public async execute(): Promise<void> {
8
+ await this.reply(this.fields.getTextInputValue('answer'));
9
+ }
10
+ }
@@ -0,0 +1,16 @@
1
+ import { StringSelectMenuBuilder } from '@discordjs/builders';
2
+ import { CustomId, RowComponent } from '{{ transport }}';
3
+
4
+ export const {{ pascal name }}Id = new CustomId('{{ kebab name }}');
5
+
6
+ export class {{ pascal name }}Menu extends RowComponent<'menu_string'> {
7
+ constructor() {
8
+ super('menu_string');
9
+
10
+ this.instance.addComponents(
11
+ new StringSelectMenuBuilder()
12
+ .setCustomId({{ pascal name }}Id.encode({}))
13
+ .addOptions({{#each choices}}{ label: {{ quote this }}, value: '{{ kebab this }}' }{{#unless @last}}, {{/unless}}{{/each}})
14
+ );
15
+ }
16
+ }
@@ -0,0 +1,10 @@
1
+ import { StringMenuHandler, StringMenuRoute } from '{{ transport }}';
2
+
3
+ import { {{ pascal name }}Id } from '{{ importOf "components" }}';
4
+
5
+ @StringMenuRoute({{ pascal name }}Id)
6
+ export class {{ pascal name }}Select extends StringMenuHandler<[typeof {{ pascal name }}Id]> {
7
+ public async execute(): Promise<void> {
8
+ await this.reply(this.values.join(', '));
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ import { BuilderComponent, RegisterCommand } from '{{ transport }}';
2
+
3
+ @RegisterCommand()
4
+ export class {{ pascal name }} extends BuilderComponent<'command'> {
5
+ constructor() {
6
+ super('command');
7
+
8
+ this.instance.setName('{{ kebab name }}').setDescription({{ quote description }});
9
+ }
10
+ }
@@ -0,0 +1,8 @@
1
+ import { SlashHandler, SlashRoute } from '{{ transport }}';
2
+
3
+ @SlashRoute('{{ kebab name }}')
4
+ export class {{ pascal name }} extends SlashHandler<'{{ kebab name }}'> {
5
+ public async execute(): Promise<void> {
6
+ await this.reply({{ quote reply }});
7
+ }
8
+ }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "files.watcherExclude": {
3
- "**/seedcord-gen.d.ts": true,
3
+ "**/seedcord-gen.ts": true,
4
4
  "**/.seedcord/**": true
5
5
  },
6
6
  "search.exclude": {
7
- "**/seedcord-gen.d.ts": true
7
+ "**/seedcord-gen.ts": true
8
8
  },
9
9
  "files.readonlyInclude": {
10
- "**/seedcord-gen.d.ts": true,
10
+ "**/seedcord-gen.ts": true,
11
11
  "**/.seedcord/**": true
12
12
  }
13
13
  }
@@ -10,13 +10,13 @@ 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{{#if isEdge}} on Cloudflare Workers{{/if}} and imports everything from `{{ transportPackage }}`. A page marks anything that differs between the two transports.
13
+ This project runs on the {{#if isGateway}}gateway{{else}}http{{/if}} transport{{#if isEdge}} on Cloudflare Workers{{/if}} and imports everything from `{{ transport }}`. A page marks anything that differs between the two transports.
14
14
 
15
15
  ## How a command works here
16
16
 
17
17
  A command takes two files. One declares what Discord shows people. The other answers it, as a handler class carrying a route decorator. seedcord reads those decorators at startup and dispatches every interaction itself.
18
18
 
19
- Run `{{ pm.run }} codegen` after you add a command or change its options. It writes `src/seedcord-gen.d.ts`, which types `this.options`. That file is committed.
19
+ Run `{{ pm.run }} codegen` after you add a command or change its options. It writes `src/seedcord-gen.ts`, which types `this.options`. That file is committed.
20
20
 
21
21
  ## Where things go
22
22
 
@@ -30,6 +30,7 @@ Run `{{ pm.run }} codegen` after you add a command or change its options. It wri
30
30
  | `src/events/` | handlers for things happening in a server |
31
31
  {{/if}}
32
32
  | `src/bot.ts` | the config |
33
+ | `templates/` | a template for each kind of file, like a command or a button. Copy its shape when you add one |
33
34
 
34
35
  <!-- prettier-ignore-end -->
35
36
 
@@ -41,6 +42,10 @@ Anything that holds a connection, keeps state between interactions, or runs in t
41
42
 
42
43
  Never import from a seedcord path ending in `/internal`, like `@seedcord/core/internal`. Those paths wire seedcord's own packages together and change without notice. If the only way to do something is through one of those imports, tell the user so they can open an issue at <https://github.com/seedcord/seedcord/issues>.
43
44
 
45
+ ## Trying a change
46
+
47
+ `{{ pm.run }} dev` runs the bot against Discord and swaps in a handler when you save it. When you start it without a terminal, it prints each log line as JSON and takes commands on stdin, like `{"do":"restart"}`. Run it in the background, ask the user to run the command in Discord, then read the lines it logged. <https://seedcord.org/guide/tooling/dev.md> lists the commands.
48
+
44
49
  ## Before you call it done
45
50
 
46
- Run `{{ pm.run }} tc` and `{{ pm.run }} lint`. A wrong route or a wrong option name is a compile error, so a green type-check catches most of what an invented API gets wrong.
51
+ Run `{{ pm.run }} tc` and `{{ pm.run }} lint`. A wrong route or a wrong option name is a compile error, so a green type-check catches most of what an invented API gets wrong. If the change alters what the bot sends or keeps, try it under `{{ pm.run }} dev` too.
@@ -24,7 +24,8 @@ That starts the dev server with hot reload{{#unless isGateway}} and opens a tunn
24
24
  | `{{ pm.run }} build` | compile to `dist/` |
25
25
  | `{{ pm.run }} start` | run the compiled build |
26
26
  {{/if}}
27
- | `{{ pm.run }} codegen` | regenerate `src/seedcord-gen.d.ts` |
27
+ | `{{ pm.run }} codegen` | regenerate `src/seedcord-gen.ts` |
28
+ | `{{ pm.run }} make` | write a new command, button, select menu{{#if isGateway}}, modal, or event{{else}}, or modal{{/if}} from `templates/` |
28
29
  {{#if isEdge}}
29
30
  | `{{ pm.run }} types` | regenerate the binding types in `.cloudflare/types/` |
30
31
  {{/if}}
@@ -78,6 +79,7 @@ After that, `{{ pm.run }} deploy` is enough. `TESTING_GUILD_IDS` can stay empty
78
79
  - `src/events/` holds event handlers for the things happening in a server.
79
80
  {{/if}}
80
81
  - `src/bot.ts` is the config.
82
+ - `templates/` holds the templates `{{ pm.run }} make` writes new files from. [Templates](https://seedcord.org/guide/tooling/make) shows how to change them or add your own.
81
83
 
82
84
  ## Docs
83
85
 
@@ -4,5 +4,5 @@ export default createConfig({
4
4
  tsconfigRootDir: import.meta.dirname,
5
5
  registerDiscordjsPlugin: true,
6
6
  registerSeedcordPlugin: true,
7
- generalIgnores: ['**/seedcord-gen.d.ts']
7
+ generalIgnores: ['**/seedcord-gen.ts']
8
8
  });
@@ -1,3 +1,3 @@
1
1
  * text=auto eol=lf
2
2
 
3
- seedcord-gen.d.ts linguist-generated
3
+ seedcord-gen.ts linguist-generated
@@ -15,6 +15,7 @@
15
15
  "start": "node --enable-source-maps dist/index.mjs",
16
16
  {{/if}}
17
17
  "codegen": "seedcord codegen",
18
+ "make": "seedcord make",
18
19
  {{#if isEdge}}
19
20
  "types": "cf workers types",
20
21
  {{/if}}
@@ -1 +1,2 @@
1
- seedcord-gen.d.ts
1
+ seedcord-gen.ts
2
+ templates/
@@ -1,4 +1,4 @@
1
- import { BuilderComponent, RegisterCommand } from '{{ transportPackage }}';
1
+ import { BuilderComponent, RegisterCommand } from '{{ transport }}';
2
2
 
3
3
  @RegisterCommand()
4
4
  export class Ping extends BuilderComponent<'command'> {
@@ -1,5 +1,5 @@
1
1
  {{#if hasMessages}}
2
- import { EventHandler, Gated, IgnoreBots, RegisterEvent } from '{{ transportPackage }}';
2
+ import { EventHandler, Gated, IgnoreBots, RegisterEvent } from '{{ transport }}';
3
3
  import { Events } from 'discord.js';
4
4
 
5
5
  @Gated(IgnoreBots)
@@ -1,6 +1,6 @@
1
1
  {{#if isGateway}}
2
2
  {{#unless hasMessages}}
3
- import { EventHandler, RegisterEvent } from '{{ transportPackage }}';
3
+ import { EventHandler, RegisterEvent } from '{{ transport }}';
4
4
  import { Events } from 'discord.js';
5
5
 
6
6
  @RegisterEvent([Events.ClientReady, { frequency: 'once' }])
@@ -1,4 +1,4 @@
1
- import { SlashHandler, SlashRoute, timestampFromSnowflake, toEpochSeconds } from '{{ transportPackage }}';
1
+ import { SlashHandler, SlashRoute, timestampFromSnowflake, toEpochSeconds } from '{{ transport }}';
2
2
 
3
3
  @SlashRoute('ping')
4
4
  export class Ping extends SlashHandler<'ping'> {
@@ -1,6 +1,6 @@
1
- import { Plugin } from '{{ transportPackage }}';
1
+ import { Plugin } from '{{ transport }}';
2
2
 
3
- import type { EpochMs } from '{{ transportPackage }}';
3
+ import type { EpochMs } from '{{ transport }}';
4
4
 
5
5
  export class Uptime extends Plugin {
6
6
  public startedAt = 0 as EpochMs;