seedcord 0.21.4 → 0.22.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/README.md CHANGED
@@ -10,31 +10,35 @@
10
10
  <a href="https://seedcord.org">Website</a> ·
11
11
  <a href="https://seedcord.org/guide">Guide</a> ·
12
12
  <a href="https://seedcord.org/docs">Reference</a> ·
13
- <a href="https://discord.gg/DzFxY58WXf">Discord</a>
13
+ <a href="https://seedcord.org/chat/">Discord</a>
14
14
  </div>
15
15
 
16
16
  <br />
17
17
 
18
18
  <div align="center">
19
19
 
20
- [![npm](https://img.shields.io/npm/v/seedcord?style=flat-square&logo=npm&logoColor=c8341f&label=&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/seedcord) [![node](https://img.shields.io/node/v/seedcord?style=flat-square&label=node&labelColor=1f1f1f&color=4d7d33)](https://nodejs.org) [![license](https://img.shields.io/npm/l/seedcord?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)
20
+ [![npm](https://img.shields.io/npm/v/seedcord?style=flat-square&label=npm&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/seedcord) [![node](https://img.shields.io/node/v/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%2Fseedcord%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/seedcord?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)
21
21
 
22
22
  </div>
23
23
 
24
24
  ## About
25
25
 
26
- `seedcord` is the CLI a bot project runs during development and at build time. It reads `seedcord.config.ts` and gives you a dev server with hot reload, a production build, the codegen that types your commands, and a cleanup tool for stale guild commands.
26
+ `seedcord` is the CLI a bot project runs during development and at build time. It reads `seedcord.config.ts` and gives you a dev server with hot reload, a production build, the codegen that types your commands, and a way to deploy those commands to Discord.
27
27
 
28
- Until v1.0.0, minor versions can break.
28
+ It loads your bot through Vite every time, with any Vite plugin you add. Those plugins, your `.env` modes, and bundled data files you set up in dev work the same way in the build. One `seedcord build` makes a Node or Bun server, a single Bun binary, a Cloudflare Worker, or a package another app installs and starts.
29
+
30
+ seedcord is pre-1.0, so a minor version can break your bot. Each release's changelog says what changed and how to update.
29
31
 
30
32
  ## Installation
31
33
 
32
34
  `pnpm create seedcord` adds it to a new project. To add it to an existing one:
33
35
 
34
36
  ```sh
35
- pnpm add -D seedcord
37
+ pnpm add -D seedcord vite
36
38
  ```
37
39
 
40
+ Run it through your project's scripts, like `pnpm dev`. A global `seedcord` throws `CliNotProjectInstall`, because the CLI has to load the same seedcord packages your bot imports.
41
+
38
42
  ## Commands
39
43
 
40
44
  <!-- prettier-ignore-start -->
@@ -42,26 +46,64 @@ pnpm add -D seedcord
42
46
  | command | what it does |
43
47
  | --- | --- |
44
48
  | `seedcord dev` | runs the bot from the config file, reloading changed modules in place |
45
- | `seedcord build` | compiles the project from the config file |
49
+ | `seedcord build` | type checks the bot and bundles it into `outDir` with Vite. An edge bot builds into a Cloudflare worker |
46
50
  | `seedcord codegen` | writes the typed augmentations for your commands and config |
47
- | `seedcord commands` | inspects and cleans commands already deployed to Discord |
51
+ | `seedcord commands` | deploys, lists and cleans up your commands on Discord |
48
52
 
49
53
  <!-- prettier-ignore-end -->
50
54
 
51
55
  `codegen --check` verifies the committed augmentations match your source. Run it in CI.
52
56
 
53
- `commands --clean` reports guild commands that duplicate a global one. It runs as a dry run until you pass `--apply`.
57
+ `commands` reads `DISCORD_BOT_TOKEN` from your environment or `.env`:
58
+
59
+ ```sh
60
+ seedcord commands # pick what to do from a menu
61
+ seedcord commands deploy # show what changes, then ask before deploying
62
+ seedcord commands deploy --yes # deploy without asking
63
+ seedcord commands deploy --force # also deploy scopes that match the last deploy
64
+ seedcord commands list # show the commands Discord has
65
+ seedcord commands list --all-guilds # include every guild the bot is in
66
+ seedcord commands list --json # print them as JSON
67
+ seedcord commands clean # pick guilds, then delete commands that duplicate a global one
68
+ seedcord commands clean --all-guilds # list those duplicates in every guild
69
+ seedcord commands clean --all-guilds --yes # delete them
70
+ seedcord commands purge # pick where, then delete every command there
71
+ seedcord commands purge --guilds 111 222 # list every command in those guilds
72
+ seedcord commands purge --global --yes # delete every global command
73
+ ```
74
+
75
+ In CI, or anywhere without a terminal, nothing changes on Discord unless you pass `--yes`.
54
76
 
55
77
  On the http transport, `dev` also opens a cloudflared tunnel and points Discord's interactions URL at it.
56
78
 
79
+ Pass `--verbose` to any command for the resolved config and full error stacks.
80
+
57
81
  ## Config
58
82
 
59
83
  `defineConfig` types `seedcord.config.ts`:
60
84
 
61
85
  ```ts
86
+ import yaml from '@rollup/plugin-yaml';
62
87
  import { defineConfig } from 'seedcord';
63
88
 
64
89
  export default defineConfig({
65
- // ...
90
+ root: './src',
91
+ instance: './bot.ts',
92
+ build: { start: './index.ts' },
93
+ files: ['./locales'],
94
+ vitePlugins: () => [yaml()]
66
95
  });
67
96
  ```
97
+
98
+ - `instance` is the file that exports your bot.
99
+ - `build.start` is the file that starts it. Leave it out to build the bot as a package.
100
+ - `files` lists the folders of data files the build bundles. The bot reads them with `readFiles`.
101
+ - `vitePlugins` takes any Vite plugin, and each one runs in dev and in the build. With the YAML plugin above, a handler can import a `.yaml` file.
102
+
103
+ `ENV=staging pnpm build` builds in staging mode and loads your `.env.staging` files. [envapt](https://envapt.materwelon.dev) reads the same mode in your bot.
104
+
105
+ The [config guide](https://seedcord.org/guide/build/config) covers every key.
106
+
107
+ ## Edge bots
108
+
109
+ A `cloudflare.config.ts` beside `seedcord.config.ts` turns an http bot into a Cloudflare Worker. `seedcord dev` runs it in workerd, and `seedcord build` writes a worker you deploy with `cf deploy --prebuilt`. The [Workers guide](https://seedcord.org/guide/build/workers) covers the setup.
package/bin/seedcord.mjs CHANGED
@@ -5,28 +5,49 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
 
6
6
  const here = dirname(fileURLToPath(import.meta.url));
7
7
 
8
- function declaredNodeRange() {
8
+ function declaredEngines() {
9
9
  try {
10
10
  const pkg = JSON.parse(readFileSync(resolve(here, '../package.json'), 'utf8'));
11
- return pkg.engines?.node ?? '';
11
+ return pkg.engines ?? {};
12
12
  } catch {
13
- return '';
13
+ return {};
14
14
  }
15
15
  }
16
16
 
17
- // importing core's own check here would load the code this guards
18
- function unsupportedNodeRange() {
19
- const range = declaredNodeRange();
17
+ function parseVersion(text) {
18
+ const [withoutBuild = ''] = text.replace(/^v/, '').split('+');
19
+ const [release = '', ...prerelease] = withoutBuild.split('-');
20
+ const parts = release.split('.').map(Number);
21
+ return parts.every(Number.isInteger) ? { parts, prerelease: prerelease.length > 0 } : undefined;
22
+ }
23
+
24
+ // semver ranks 1.4.2-canary.1 below 1.4.2
25
+ function meetsMinimum(required, running) {
26
+ const length = Math.max(required.parts.length, running.parts.length);
27
+ for (let index = 0; index < length; index++) {
28
+ const wanted = required.parts[index] ?? 0;
29
+ const got = running.parts[index] ?? 0;
30
+ if (got !== wanted) return got > wanted;
31
+ }
32
+ return !running.prerelease;
33
+ }
34
+
35
+ // importing core's assertDeclaredRuntime here would load the code this guards
36
+ function unsupportedRuntime() {
37
+ const engines = declaredEngines();
38
+ const { bun, node } = process.versions;
39
+ const runtime =
40
+ bun === undefined
41
+ ? { name: 'Node', range: engines.node ?? '', running: node }
42
+ : { name: 'Bun', range: engines.bun ?? '', running: bun };
20
43
 
21
- const required = new RegExp(String.raw`^>=(\d+)(?:\.(\d+))?`).exec(range.replaceAll(/\s/g, ''));
22
- const current = /^v?(\d+)\.(\d+)/.exec(process.version);
23
- if (!required || !current) return null;
44
+ const range = runtime.range.replaceAll(/\s/g, '');
45
+ if (!range.startsWith('>=')) return undefined;
24
46
 
25
- const requiredMinor = required[2] ?? '0';
26
- const meets =
27
- Number(current[1]) > Number(required[1]) ||
28
- (Number(current[1]) === Number(required[1]) && Number(current[2]) >= Number(requiredMinor));
29
- return meets ? null : range;
47
+ const required = parseVersion(range.slice('>='.length));
48
+ const running = parseVersion(runtime.running);
49
+ if (!required || !running || meetsMinimum(required, running)) return undefined;
50
+ return runtime;
30
51
  }
31
52
 
32
53
  async function run() {
@@ -41,10 +62,12 @@ async function run() {
41
62
  await import(pathToFileURL(srcEntry).href);
42
63
  }
43
64
 
44
- const unsupported = unsupportedNodeRange();
65
+ const unsupported = unsupportedRuntime();
45
66
  if (unsupported) {
46
67
  // eslint-disable-next-line no-console -- the bin has no logger
47
- console.error(`seedcord requires Node ${unsupported} and this process runs ${process.version}.`);
68
+ console.error(
69
+ `seedcord requires ${unsupported.name} ${unsupported.range} but this process runs ${unsupported.running}.`
70
+ );
48
71
  process.exit(1);
49
72
  }
50
73