meocord 4.0.0-beta.0 → 4.0.0-beta.2
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/CHANGELOG.md +56 -0
- package/README.md +2 -2
- package/dist/cjs/_shared/theme-Dqwlnz0m.cjs +124 -0
- package/dist/cjs/common/index.cjs +2 -2
- package/dist/cjs/core/index.cjs +27 -25
- package/dist/esm/bin/app-template/src/main.ts.template +5 -1
- package/dist/esm/bin/app-template/tsconfig.json.template +2 -2
- package/dist/esm/bin/builder-template/controller/message.controller.template +2 -2
- package/dist/esm/bin/builder-template/controller/reaction.controller.template +5 -5
- package/dist/esm/bin/builder-template/guard.template +2 -2
- package/dist/esm/bin/meocord.js +6 -3
- package/dist/esm/build/native-addons.js +49 -10
- package/dist/esm/build/rsbuild-config.js +8 -1
- package/dist/esm/core/meocord-factory.js +1 -1
- package/dist/esm/core/meocord.app.js +24 -22
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/util/common.util.js +3 -3
- package/dist/esm/util/meocord-config-loader.util.js +20 -64
- package/dist/esm/util/meocord-source-config.util.js +55 -0
- package/dist/types/common/index.d.cts +53 -0
- package/dist/types/controller.enum-DYfhYaat.d.cts +36 -0
- package/dist/types/core/index.d.cts +131 -0
- package/dist/types/core/index.d.ts +7 -0
- package/dist/types/decorator/index.d.cts +340 -0
- package/dist/types/enum/index.d.cts +40 -0
- package/dist/types/interface/index.d.cts +170 -0
- package/dist/types/testing/index.d.cts +274 -0
- package/meocord.eslint.d.cts +13 -0
- package/package.json +59 -23
- package/dist/cjs/_shared/theme-BXdSoifk.cjs +0 -168
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# meocord
|
|
2
2
|
|
|
3
|
+
## 4.0.0-beta.2
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#36](https://github.com/l7aromeo/meocord/pull/36) [`45e564e`](https://github.com/l7aromeo/meocord/commit/45e564e6baff17313037372ec56baa1711a3584c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - **Breaking:** `app.start()` rejects when the login fails. It logged the error and resolved, so a bot
|
|
8
|
+
with an invalid token went on to log "Application started" and exit with code 0, which Docker's
|
|
9
|
+
`restart: on-failure`, systemd and CI all read as success. New applications' `main.ts` sets
|
|
10
|
+
`process.exitCode = 1` when startup fails; add the same to an existing entry point's `catch`. See the
|
|
11
|
+
[migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#4-build-and-start).
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#37](https://github.com/l7aromeo/meocord/pull/37) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Type the CommonJS build as CommonJS. Every entry point's `require` condition resolved to the ESM
|
|
16
|
+
declarations, so a CommonJS TypeScript project was told `meocord/core` is an ES module it cannot
|
|
17
|
+
`require`, even with `skipLibCheck`. Each entry now ships `.d.cts` declarations for `require`, and
|
|
18
|
+
`meocord/eslint` types its `module.exports` array as what `require` returns.
|
|
19
|
+
|
|
20
|
+
- [#35](https://github.com/l7aromeo/meocord/pull/35) [`b93a771`](https://github.com/l7aromeo/meocord/commit/b93a771b19664345fec4222d5a2c29dcf5b31a0d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Generate code that passes a new application's own `lint`. A generated guard failed `tsc` and ESLint
|
|
21
|
+
— `GuardInterface` imported as a value, and an unused `context` parameter — and generated message and
|
|
22
|
+
reaction controllers imported names they never used, which only the application's ESLint, run in the
|
|
23
|
+
background after generating, removed. New applications also typecheck `meocord.config.ts`: the
|
|
24
|
+
template's `tsconfig.json` includes it instead of excluding it, so a type error in the config fails
|
|
25
|
+
`lint`, and editors resolve `paths` aliases imported there. `noEmit` stays on, so `tsc` writes nothing
|
|
26
|
+
beside it.
|
|
27
|
+
|
|
28
|
+
## 4.0.0-beta.1
|
|
29
|
+
|
|
30
|
+
### Major Changes
|
|
31
|
+
|
|
32
|
+
- [#33](https://github.com/l7aromeo/meocord/pull/33) [`21b06ae`](https://github.com/l7aromeo/meocord/commit/21b06ae6810cfe203abadcadc6f7b7247c7b305b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Require Node.js 22.13 or newer, up from 22.0. A built bot loads its compiled config with `require()`
|
|
33
|
+
of an ES module. Node 22.12 runs that without a flag but still warns on every start and crashes on a
|
|
34
|
+
config that throws; 22.13 is the first 22 release that does neither. Bun is unaffected. See the
|
|
35
|
+
[migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#before-you-start-nodejs-2213).
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- [#33](https://github.com/l7aromeo/meocord/pull/33) [`21b06ae`](https://github.com/l7aromeo/meocord/commit/21b06ae6810cfe203abadcadc6f7b7247c7b305b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Keep jiti out of bots built with `bundleDependencies`. A built bot loaded `dist/meocord.config.mjs`,
|
|
40
|
+
already compiled JavaScript, through jiti, and the logger and the factory both reach that loader,
|
|
41
|
+
so jiti was bundled into every bot: 190 KB, 89% of a minimal bot's `main.js`, and a
|
|
42
|
+
`Critical dependency` warning on every build. The compiled config is now loaded with `require()`,
|
|
43
|
+
and jiti is only used by the CLI to read `meocord.config.ts`. A minimal bot bundles to 22 KB.
|
|
44
|
+
|
|
45
|
+
A built bot no longer falls back to reading `meocord.config.ts` when `dist/meocord.config.mjs` is
|
|
46
|
+
missing, and `meocord build` now fails when the config does not compile, instead of warning and
|
|
47
|
+
producing a bot that cannot start.
|
|
48
|
+
|
|
49
|
+
- [#31](https://github.com/l7aromeo/meocord/pull/31) [`f50bafc`](https://github.com/l7aromeo/meocord/commit/f50bafce5bcf04b555a7638fedeb6986cf7ea9d5) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Resolve asset imports to files under `dist` in development builds too. `meocord start --dev` gave
|
|
50
|
+
`import logo from './logo.png'` the path `/assets/logo.png`, at the root of the filesystem, so a bot
|
|
51
|
+
reading an imported font or image failed in development while production worked.
|
|
52
|
+
|
|
53
|
+
- [#32](https://github.com/l7aromeo/meocord/pull/32) [`72b228c`](https://github.com/l7aromeo/meocord/commit/72b228ceaca2dca33ed660aff0ec1bc2aa59bfc2) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Pack only the build platform's native binaries with `bundleDependencies`. Every installed platform
|
|
54
|
+
package was copied into `dist/node_modules`, and bun installs both the glibc and the musl build on
|
|
55
|
+
Linux, so a glibc build of a bot using sharp carried about 19 MB of musl binaries it could never
|
|
56
|
+
load. A package whose `os`, `cpu` or `libc` does not match the platform building is now left out,
|
|
57
|
+
whichever package manager installed it.
|
|
58
|
+
|
|
3
59
|
## 4.0.0-beta.0
|
|
4
60
|
|
|
5
61
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
### Prerequisites
|
|
55
55
|
|
|
56
|
-
- **Runtime**: Node.js 22 or newer, or Bun 1.x+
|
|
56
|
+
- **Runtime**: Node.js 22.13 or newer, or Bun 1.x+
|
|
57
57
|
- **TypeScript**: 5.0+ with `skipLibCheck` enabled, as generated apps have it; 5.8+ with it off
|
|
58
58
|
- **Package manager**: npm, yarn, pnpm, or bun
|
|
59
59
|
- **Peer dependencies**: `discord.js` 14 and `dotenv` 18 — `meocord create` installs both
|
|
@@ -1055,7 +1055,7 @@ dist/
|
|
|
1055
1055
|
└── meocord.platform.json (if there are native addons)
|
|
1056
1056
|
```
|
|
1057
1057
|
|
|
1058
|
-
Plain JavaScript dependencies are bundled into `main.js`. **Native addons** — packages that ship a compiled `.node` binary, like `sharp`, canvas bindings or database drivers — cannot be inlined into JavaScript, so MeoCord finds them itself while building, keeps them out of the bundle, and copies each one, with its platform binary and what it needs at runtime, into `dist/node_modules`. There is nothing to list: the build tells you which it packed.
|
|
1058
|
+
Plain JavaScript dependencies are bundled into `main.js`. **Native addons** — packages that ship a compiled `.node` binary, like `sharp`, canvas bindings or database drivers — cannot be inlined into JavaScript, so MeoCord finds them itself while building, keeps them out of the bundle, and copies each one, with its platform binary and what it needs at runtime, into `dist/node_modules`. There is nothing to list: the build tells you which it packed. Only binaries for the platform building are copied, going by the `os`, `cpu` and `libc` each platform package declares — so a glibc build carries no musl binaries even where the package manager installed both, as bun does.
|
|
1059
1059
|
|
|
1060
1060
|
```
|
|
1061
1061
|
Native addons packed into dist: meo-canvas, sharp
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_util = require('node:util');
|
|
4
|
+
var dayjs = require('dayjs');
|
|
5
|
+
var utc = require('dayjs/plugin/utc.js');
|
|
6
|
+
var timezone = require('dayjs/plugin/timezone.js');
|
|
7
|
+
var path = require('path');
|
|
8
|
+
var fs = require('fs');
|
|
9
|
+
var module$1 = require('module');
|
|
10
|
+
var chalk = require('chalk');
|
|
11
|
+
|
|
12
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
|
+
let cachedConfig;
|
|
14
|
+
let configLoaded = false;
|
|
15
|
+
/**
|
|
16
|
+
* Loads the configuration a built application runs with: `dist/meocord.config.mjs`, which
|
|
17
|
+
* `meocord build` compiles from `meocord.config.ts`.
|
|
18
|
+
*
|
|
19
|
+
* Reads only the compiled file, which is plain JavaScript and needs no transpiler, tsconfig or
|
|
20
|
+
* source. Reading `meocord.config.ts` is {@link loadMeoCordSourceConfig}'s job, and it lives in its
|
|
21
|
+
* own module because it needs jiti: this one is imported by the logger and the factory, so a
|
|
22
|
+
* bot built with `bundleDependencies` bundles whatever it imports.
|
|
23
|
+
*
|
|
24
|
+
* The result is cached after the first load.
|
|
25
|
+
*
|
|
26
|
+
* @returns The configuration, or undefined when there is no compiled config or it fails to load.
|
|
27
|
+
*/ function loadMeoCordConfig() {
|
|
28
|
+
if (configLoaded) return cachedConfig;
|
|
29
|
+
configLoaded = true;
|
|
30
|
+
cachedConfig = loadCompiledConfig();
|
|
31
|
+
return cachedConfig;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `require` of an ES module, which Node supports cleanly from 22.13 and bun always has.
|
|
35
|
+
* Synchronous, like the logger and the factory that call it.
|
|
36
|
+
*/ function loadCompiledConfig() {
|
|
37
|
+
const compiledPath = path.resolve(process.cwd(), 'dist', 'meocord.config.mjs');
|
|
38
|
+
if (!fs.existsSync(compiledPath)) return undefined;
|
|
39
|
+
try {
|
|
40
|
+
// Called through a variable so a bundler does not try to resolve the path while building the
|
|
41
|
+
// application; the file is only there once the build is done.
|
|
42
|
+
const load = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/theme-Dqwlnz0m.cjs', document.baseURI).href)));
|
|
43
|
+
const loaded = load(compiledPath);
|
|
44
|
+
return loaded.default ?? loaded;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`[MeoCord] Failed to load dist/meocord.config.mjs: ${error instanceof Error ? error.message : error}`);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
dayjs.extend(utc);
|
|
52
|
+
dayjs.extend(timezone);
|
|
53
|
+
class Logger {
|
|
54
|
+
log(...args) {
|
|
55
|
+
this.logWithContext('log', args);
|
|
56
|
+
}
|
|
57
|
+
info(...args) {
|
|
58
|
+
this.logWithContext('log', args);
|
|
59
|
+
}
|
|
60
|
+
warn(...args) {
|
|
61
|
+
this.logWithContext('warn', args);
|
|
62
|
+
}
|
|
63
|
+
error(...args) {
|
|
64
|
+
this.logWithContext('error', args);
|
|
65
|
+
}
|
|
66
|
+
debug(...args) {
|
|
67
|
+
this.logWithContext('debug', args);
|
|
68
|
+
}
|
|
69
|
+
verbose(...args) {
|
|
70
|
+
this.logWithContext('log', args);
|
|
71
|
+
}
|
|
72
|
+
formatMessage(message, logType) {
|
|
73
|
+
if (typeof message === 'object' && message !== null) {
|
|
74
|
+
return node_util.inspect(message, {
|
|
75
|
+
showHidden: true,
|
|
76
|
+
depth: null,
|
|
77
|
+
colors: true,
|
|
78
|
+
compact: false,
|
|
79
|
+
showProxy: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return (this.colorMap[logType] || ((msg)=>msg))(message);
|
|
83
|
+
}
|
|
84
|
+
logWithContext(logLevel, messages) {
|
|
85
|
+
if (messages.length === 0) return;
|
|
86
|
+
const config = loadMeoCordConfig();
|
|
87
|
+
const logType = logLevel.toUpperCase();
|
|
88
|
+
const applyColor = this.colorMap[logType] || ((msg)=>msg);
|
|
89
|
+
const formattedMessages = messages.map((message)=>this.formatMessage(message, logType));
|
|
90
|
+
const coloredAppName = config?.appName ? applyColor(chalk.bold(`[${config.appName}]`)) : undefined;
|
|
91
|
+
const timestamp = chalk.bold(dayjs().format('dddd, MMMM D, YYYY HH:mm:ss [UTC]Z'));
|
|
92
|
+
const coloredLogLevel = applyColor(chalk.bold(`[${logType}]`));
|
|
93
|
+
const coloredContext = this.context ? chalk.yellow.bold(`[${this.context}]`) : '';
|
|
94
|
+
const logTexts = [
|
|
95
|
+
coloredAppName,
|
|
96
|
+
timestamp,
|
|
97
|
+
coloredLogLevel,
|
|
98
|
+
coloredContext,
|
|
99
|
+
...formattedMessages
|
|
100
|
+
].filter((log)=>!!log);
|
|
101
|
+
console[logLevel](...logTexts);
|
|
102
|
+
}
|
|
103
|
+
constructor(context){
|
|
104
|
+
this.context = context;
|
|
105
|
+
this.colorMap = {
|
|
106
|
+
LOG: chalk.green,
|
|
107
|
+
INFO: chalk.cyan,
|
|
108
|
+
WARN: chalk.yellow,
|
|
109
|
+
ERROR: chalk.red,
|
|
110
|
+
DEBUG: chalk.magenta
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class Theme {
|
|
116
|
+
}
|
|
117
|
+
Theme.successColor = '#28A745';
|
|
118
|
+
Theme.infoColor = '#17A2B8';
|
|
119
|
+
Theme.errorColor = '#DC3545';
|
|
120
|
+
Theme.warningColor = '#FFC107';
|
|
121
|
+
|
|
122
|
+
exports.Logger = Logger;
|
|
123
|
+
exports.Theme = Theme;
|
|
124
|
+
exports.loadMeoCordConfig = loadMeoCordConfig;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var theme = require('../_shared/theme-
|
|
3
|
+
var theme = require('../_shared/theme-Dqwlnz0m.cjs');
|
|
4
4
|
require('node:util');
|
|
5
5
|
require('dayjs');
|
|
6
6
|
require('dayjs/plugin/utc.js');
|
|
7
7
|
require('dayjs/plugin/timezone.js');
|
|
8
8
|
require('path');
|
|
9
9
|
require('fs');
|
|
10
|
-
require('
|
|
10
|
+
require('module');
|
|
11
11
|
require('chalk');
|
|
12
12
|
|
|
13
13
|
/**
|
package/dist/cjs/core/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require('reflect-metadata');
|
|
4
4
|
var inversify = require('inversify');
|
|
5
5
|
var discord_js = require('discord.js');
|
|
6
|
-
var theme = require('../_shared/theme-
|
|
6
|
+
var theme = require('../_shared/theme-Dqwlnz0m.cjs');
|
|
7
7
|
var controller_decorator = require('../_shared/controller.decorator-9nIxOp9y.cjs');
|
|
8
8
|
var lodashEs = require('lodash-es');
|
|
9
9
|
var enum_index = require('../enum/index.cjs');
|
|
@@ -15,7 +15,7 @@ require('node:util');
|
|
|
15
15
|
require('dayjs');
|
|
16
16
|
require('dayjs/plugin/utc.js');
|
|
17
17
|
require('dayjs/plugin/timezone.js');
|
|
18
|
-
require('
|
|
18
|
+
require('module');
|
|
19
19
|
require('chalk');
|
|
20
20
|
|
|
21
21
|
const createErrorEmbed = (description)=>{
|
|
@@ -106,28 +106,30 @@ class MeoCordApp {
|
|
|
106
106
|
}
|
|
107
107
|
return this.controllerInstancesCache.get(controllerClass);
|
|
108
108
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Registers the Discord event handlers and logs in.
|
|
111
|
+
*
|
|
112
|
+
* Rejects when the login fails -- an invalid token, or Discord unreachable -- so the caller can
|
|
113
|
+
* stop with a non-zero exit code. A bot that never came online is a failed start, and a
|
|
114
|
+
* supervisor such as Docker's `restart: on-failure` or systemd can only tell if the process says so.
|
|
115
|
+
*/ async start() {
|
|
116
|
+
this.logger.log('Starting bot...');
|
|
117
|
+
this.bot.on('clientReady', ()=>this.runListener('clientReady', async ()=>{
|
|
118
|
+
this.activityInterval = setInterval(()=>this.updateActivity(), 10000);
|
|
119
|
+
await this.registerCommands();
|
|
120
|
+
}));
|
|
121
|
+
this.bot.on('interactionCreate', (interaction)=>this.runListener('interactionCreate', ()=>this.handleInteraction(interaction)));
|
|
122
|
+
this.bot.on('messageCreate', (message)=>this.runListener('messageCreate', ()=>this.handleMessage(message)));
|
|
123
|
+
this.bot.on('messageReactionAdd', (reaction, user)=>this.runListener('messageReactionAdd', ()=>this.handleReaction(reaction, {
|
|
124
|
+
user,
|
|
125
|
+
action: enum_index.ReactionHandlerAction.ADD
|
|
126
|
+
})));
|
|
127
|
+
this.bot.on('messageReactionRemove', (reaction, user)=>this.runListener('messageReactionRemove', ()=>this.handleReaction(reaction, {
|
|
128
|
+
user,
|
|
129
|
+
action: enum_index.ReactionHandlerAction.REMOVE
|
|
130
|
+
})));
|
|
131
|
+
await this.bot.login(this.discordToken);
|
|
132
|
+
this.logger.log('Bot is online!');
|
|
131
133
|
}
|
|
132
134
|
async registerCommands() {
|
|
133
135
|
// Keyed by type and name together, because that pair is what Discord treats as one
|
|
@@ -593,7 +595,7 @@ class MeoCordFactory {
|
|
|
593
595
|
}
|
|
594
596
|
const meocordConfig = theme.loadMeoCordConfig();
|
|
595
597
|
if (!meocordConfig) {
|
|
596
|
-
throw new Error('MeoCord config not found
|
|
598
|
+
throw new Error('MeoCord config not found: dist/meocord.config.mjs is missing or failed to load. Run `meocord build`.');
|
|
597
599
|
}
|
|
598
600
|
// Before anything is resolved: a controller or service is what first loads a native addon, and
|
|
599
601
|
// one built for another platform would otherwise fail there with a linker error.
|
|
@@ -11,4 +11,8 @@ async function bootstrap() {
|
|
|
11
11
|
logger.log('Application started')
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
bootstrap().catch(error =>
|
|
14
|
+
bootstrap().catch(error => {
|
|
15
|
+
logger.error('Error during startup:', error)
|
|
16
|
+
// A bot that never came online is a failed start: exit non-zero, so Docker, systemd or CI see it.
|
|
17
|
+
process.exitCode = 1
|
|
18
|
+
})
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
},
|
|
26
26
|
"types": ["node"]
|
|
27
27
|
},
|
|
28
|
-
"include": ["src/**/*.ts"],
|
|
29
|
-
"exclude": ["
|
|
28
|
+
"include": ["src/**/*.ts", "meocord.config.ts"],
|
|
29
|
+
"exclude": ["dist", "vitest.config.ts", "node_modules", "src/**/*.spec.ts"]
|
|
30
30
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MessageHandler, Controller
|
|
2
|
-
import {
|
|
1
|
+
import { MessageHandler, Controller } from 'meocord/decorator'
|
|
2
|
+
import { Message } from 'discord.js'
|
|
3
3
|
import { Logger } from 'meocord/common'
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { MessageReaction
|
|
1
|
+
import { MessageReaction } from 'discord.js'
|
|
2
2
|
import { Controller, ReactionHandler } from 'meocord/decorator'
|
|
3
3
|
import { Logger } from 'meocord/common'
|
|
4
|
-
import {
|
|
4
|
+
import { ReactionHandlerAction } from 'meocord/enum'
|
|
5
5
|
import { type ReactionHandlerOptions } from 'meocord/interface'
|
|
6
6
|
|
|
7
7
|
@Controller()
|
|
@@ -10,12 +10,12 @@ export class {{className}}ReactionController {
|
|
|
10
10
|
|
|
11
11
|
@ReactionHandler('😋')
|
|
12
12
|
async handleReaction(reaction: MessageReaction, { user, action }: ReactionHandlerOptions) {
|
|
13
|
-
this.logger.log(`Reaction 😋 ${action === ReactionHandlerAction.ADD ? 'added' : 'removed'}.`)
|
|
13
|
+
this.logger.log(`Reaction 😋 ${action === ReactionHandlerAction.ADD ? 'added' : 'removed'}.`)
|
|
14
14
|
|
|
15
|
-
if (!reaction.message) return
|
|
15
|
+
if (!reaction.message) return
|
|
16
16
|
|
|
17
17
|
if (action === ReactionHandlerAction.ADD) {
|
|
18
|
-
await reaction.message.reply(`${user.username} reacted with 😋!`)
|
|
18
|
+
await reaction.message.reply(`${user.username} reacted with 😋!`)
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Guard } from 'meocord/decorator'
|
|
2
|
-
import { GuardInterface } from 'meocord/interface'
|
|
2
|
+
import { type GuardInterface } from 'meocord/interface'
|
|
3
3
|
import { BaseInteraction, Message, MessageReaction } from 'discord.js'
|
|
4
4
|
|
|
5
5
|
@Guard()
|
|
6
6
|
export class {{className}}Guard implements GuardInterface {
|
|
7
|
-
async canActivate(
|
|
7
|
+
async canActivate(_context: BaseInteraction | Message | MessageReaction): Promise<boolean> {
|
|
8
8
|
// TODO: Implement the guard logic to determine if the interaction is allowed
|
|
9
9
|
return true
|
|
10
10
|
}
|
package/dist/esm/bin/meocord.js
CHANGED
|
@@ -23,7 +23,7 @@ import { fileURLToPath } from 'url';
|
|
|
23
23
|
import { assertNoWebpackHook, createRsbuildConfig, DISCORD_OPTIONAL_NATIVES } from '../build/rsbuild-config.js';
|
|
24
24
|
import { createNativeExternals, nativeCarrier, copyPackagesInto, bundledModuleFiles, assertNoBundledNativeAddons, findBundledNativeAddons } from '../build/native-addons.js';
|
|
25
25
|
import { writePlatformManifest, PLATFORM_MANIFEST } from '../util/platform.util.js';
|
|
26
|
-
import { loadMeoCordSourceConfig } from '../util/meocord-config
|
|
26
|
+
import { loadMeoCordSourceConfig } from '../util/meocord-source-config.util.js';
|
|
27
27
|
|
|
28
28
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
29
29
|
const __dirname$1 = path.dirname(__filename$1);
|
|
@@ -380,8 +380,11 @@ copies or substantial portions of the Software.
|
|
|
380
380
|
});
|
|
381
381
|
await rsbuild.build();
|
|
382
382
|
this.logger.info('Config compiled to dist/meocord.config.mjs');
|
|
383
|
-
} catch
|
|
384
|
-
|
|
383
|
+
} catch (error) {
|
|
384
|
+
// The built application reads only the compiled config, so without it the bot cannot start.
|
|
385
|
+
this.logger.error(`Failed to compile meocord.config.ts: ${error instanceof Error ? error.message : error}`);
|
|
386
|
+
await wait(100);
|
|
387
|
+
process.exit(1);
|
|
385
388
|
}
|
|
386
389
|
}
|
|
387
390
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, existsSync, cpSync } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { currentPlatform } from '../util/platform.util.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The package a resolved module file belongs to, and the directory it was installed in.
|
|
@@ -70,8 +71,9 @@ import path from 'path';
|
|
|
70
71
|
* Where an optional dependency of the package in `from` was installed, if it was.
|
|
71
72
|
*
|
|
72
73
|
* Nested under the package, beside it (pnpm's layout, and npm's hoisting within a scope), or
|
|
73
|
-
* hoisted to the project root.
|
|
74
|
-
*
|
|
74
|
+
* hoisted to the project root. Being installed does not make it this platform's: some package
|
|
75
|
+
* managers install platform packages for other C libraries too, so {@link copyPackagesInto} also
|
|
76
|
+
* checks what each one declares it is built for.
|
|
75
77
|
*/ function resolveDependencyDir(name, from, root) {
|
|
76
78
|
const candidates = [
|
|
77
79
|
path.join(from, 'node_modules', name),
|
|
@@ -176,28 +178,65 @@ import path from 'path';
|
|
|
176
178
|
found
|
|
177
179
|
};
|
|
178
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Whether a value passes a package.json `os`, `cpu` or `libc` list, the way npm reads one: a `!`
|
|
183
|
+
* entry excludes its value, and a list with plain entries admits only those.
|
|
184
|
+
*/ function allowedBy(list, value) {
|
|
185
|
+
if (!Array.isArray(list) || list.length === 0) return true;
|
|
186
|
+
const entries = list.filter((entry)=>typeof entry === 'string');
|
|
187
|
+
if (entries.includes(`!${value}`)) return false;
|
|
188
|
+
const admitted = entries.filter((entry)=>!entry.startsWith('!'));
|
|
189
|
+
return admitted.length === 0 || admitted.includes(value);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Whether an installed package is built for the platform, going by the `os`, `cpu` and `libc` its
|
|
193
|
+
* package.json declares. A package that declares none of them runs anywhere.
|
|
194
|
+
*
|
|
195
|
+
* `libc` is compared only when the build platform's C library is known, so a runtime that cannot
|
|
196
|
+
* report it keeps the package rather than dropping it on a guess.
|
|
197
|
+
*/ function isBuiltFor(dir, platform) {
|
|
198
|
+
let pkg;
|
|
199
|
+
try {
|
|
200
|
+
pkg = JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
201
|
+
} catch {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (!allowedBy(pkg.os, platform.platform) || !allowedBy(pkg.cpu, platform.arch)) return false;
|
|
205
|
+
return !platform.libc || allowedBy(pkg.libc, platform.libc);
|
|
206
|
+
}
|
|
207
|
+
/** Whether a path is a package inside a `node_modules` directory, declared for another platform. */ function isOtherPlatformPackage(source, platform) {
|
|
208
|
+
const parent = path.basename(path.dirname(source));
|
|
209
|
+
const scopeParent = path.basename(path.dirname(path.dirname(source)));
|
|
210
|
+
const isPackageDir = parent === 'node_modules' || parent.startsWith('@') && scopeParent === 'node_modules';
|
|
211
|
+
return isPackageDir && existsSync(path.join(source, 'package.json')) && !isBuiltFor(source, platform);
|
|
212
|
+
}
|
|
179
213
|
/**
|
|
180
214
|
* Copies packages, with everything they need at runtime, into `<outDir>/node_modules`.
|
|
181
215
|
*
|
|
182
216
|
* This is what lets a bundled application run with nothing installed beside it: the packages that
|
|
183
217
|
* could not be bundled travel inside the output directory, where Node finds them by walking up
|
|
184
218
|
* from the bundle. Each package's installed dependencies and optional dependencies are followed --
|
|
185
|
-
* that is where a platform binary lives
|
|
186
|
-
*
|
|
187
|
-
* and
|
|
219
|
+
* that is where a platform binary lives. A dependency is copied only when its `os`, `cpu` and `libc`
|
|
220
|
+
* admit the build platform: package managers differ in which of those they filter
|
|
221
|
+
* installs on -- bun installs both the glibc and the musl build on Linux -- and the output has to
|
|
222
|
+
* carry only binaries the platform check in `dist` would let load. Type-only `@types` packages are
|
|
223
|
+
* left behind; some packages list them as runtime dependencies, and they only add weight.
|
|
188
224
|
*
|
|
225
|
+
* @param platform - What the binaries are for. The platform building, which is what `dist`
|
|
226
|
+
* records in its platform manifest.
|
|
189
227
|
* @returns The names copied.
|
|
190
|
-
*/ function copyPackagesInto(packages, root, outDir) {
|
|
228
|
+
*/ function copyPackagesInto(packages, root, outDir, platform = currentPlatform()) {
|
|
191
229
|
const copied = new Set();
|
|
192
230
|
const copy = (name, dir)=>{
|
|
193
231
|
if (copied.has(name) || name.startsWith('@types/')) return;
|
|
194
232
|
copied.add(name);
|
|
195
233
|
const target = path.join(outDir, 'node_modules', name);
|
|
196
234
|
// A package's own nested node_modules comes along with it, so only hoisted dependencies
|
|
197
|
-
// need finding separately.
|
|
235
|
+
// need finding separately. The nested ones get the same platform check on the way.
|
|
198
236
|
cpSync(dir, target, {
|
|
199
237
|
recursive: true,
|
|
200
|
-
dereference: true
|
|
238
|
+
dereference: true,
|
|
239
|
+
filter: (source)=>source === dir || !isOtherPlatformPackage(source, platform)
|
|
201
240
|
});
|
|
202
241
|
const { dependencies, optional } = readDependencies(dir);
|
|
203
242
|
for (const dependency of [
|
|
@@ -206,7 +245,7 @@ import path from 'path';
|
|
|
206
245
|
]){
|
|
207
246
|
if (existsSync(path.join(dir, 'node_modules', dependency, 'package.json'))) continue;
|
|
208
247
|
const dependencyDir = resolveDependencyDir(dependency, dir, root);
|
|
209
|
-
if (dependencyDir) copy(dependency, dependencyDir);
|
|
248
|
+
if (dependencyDir && isBuiltFor(dependencyDir, platform)) copy(dependency, dependencyDir);
|
|
210
249
|
}
|
|
211
250
|
};
|
|
212
251
|
for (const [name, dir] of packages)copy(name, dir);
|
|
@@ -232,4 +271,4 @@ import path from 'path';
|
|
|
232
271
|
throw new Error(`bundleDependencies bundled ${found.size === 1 ? 'a package that loads' : 'packages that load'} a native addon:\n` + `${lines.join('\n')}\n` + 'A native addon is a compiled binary for one platform and cannot travel inside the bundle. The build ' + 'would run on this machine and fail in production the first time the addon loads. Keep ' + `${found.size === 1 ? 'it' : 'them'} out of the bundle and install ${found.size === 1 ? 'it' : 'them'} ` + `on the server:\n\n externals: [${names}]`);
|
|
233
272
|
}
|
|
234
273
|
|
|
235
|
-
export { assertNoBundledNativeAddons, bundledModuleFiles, copyPackagesInto, createNativeExternals, findBundledNativeAddons, nativeCarrier, packageFromPath, packageNameOfRequest };
|
|
274
|
+
export { assertNoBundledNativeAddons, bundledModuleFiles, copyPackagesInto, createNativeExternals, findBundledNativeAddons, isBuiltFor, nativeCarrier, packageFromPath, packageNameOfRequest };
|
|
@@ -38,7 +38,14 @@ import { prepareModifiedTsConfig } from '../util/tsconfig.util.js';
|
|
|
38
38
|
const { mode, bundleDependencies = false, externals = [] } = options;
|
|
39
39
|
const cwd = process.cwd();
|
|
40
40
|
const entry = options.entry ?? path.resolve(cwd, 'src', 'main.ts');
|
|
41
|
+
const assetPrefix = assetPrefixFor(path.resolve(cwd, 'dist'));
|
|
41
42
|
return {
|
|
43
|
+
// Rsbuild takes the asset prefix from `dev.assetPrefix` in development and from
|
|
44
|
+
// `output.assetPrefix` in production, and the development default is `/`. Both are set, so an
|
|
45
|
+
// asset import is the same path on disk in either mode.
|
|
46
|
+
dev: {
|
|
47
|
+
assetPrefix
|
|
48
|
+
},
|
|
42
49
|
source: {
|
|
43
50
|
entry: {
|
|
44
51
|
main: entry
|
|
@@ -108,7 +115,7 @@ import { prepareModifiedTsConfig } from '../util/tsconfig.util.js';
|
|
|
108
115
|
// What `import image from './x.png'` evaluates to at runtime. A bot passes that string
|
|
109
116
|
// to fs or to a Discord attachment, so it has to be a real path on disk, which Rsbuild's
|
|
110
117
|
// web-oriented default is not.
|
|
111
|
-
assetPrefix
|
|
118
|
+
assetPrefix,
|
|
112
119
|
// Rsbuild inlines assets under 4 KB as base64 data URIs, so the same import would give a
|
|
113
120
|
// path for a large file and a `data:` string for a small one. A bot reads its assets
|
|
114
121
|
// with fs, where a data URI is ENOENT, so every asset is emitted as a file.
|
|
@@ -35,7 +35,7 @@ class MeoCordFactory {
|
|
|
35
35
|
}
|
|
36
36
|
const meocordConfig = loadMeoCordConfig();
|
|
37
37
|
if (!meocordConfig) {
|
|
38
|
-
throw new Error('MeoCord config not found
|
|
38
|
+
throw new Error('MeoCord config not found: dist/meocord.config.mjs is missing or failed to load. Run `meocord build`.');
|
|
39
39
|
}
|
|
40
40
|
// Before anything is resolved: a controller or service is what first loads a native addon, and
|
|
41
41
|
// one built for another platform would otherwise fail there with a linker error.
|
|
@@ -88,28 +88,30 @@ class MeoCordApp {
|
|
|
88
88
|
}
|
|
89
89
|
return this.controllerInstancesCache.get(controllerClass);
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Registers the Discord event handlers and logs in.
|
|
93
|
+
*
|
|
94
|
+
* Rejects when the login fails -- an invalid token, or Discord unreachable -- so the caller can
|
|
95
|
+
* stop with a non-zero exit code. A bot that never came online is a failed start, and a
|
|
96
|
+
* supervisor such as Docker's `restart: on-failure` or systemd can only tell if the process says so.
|
|
97
|
+
*/ async start() {
|
|
98
|
+
this.logger.log('Starting bot...');
|
|
99
|
+
this.bot.on('clientReady', ()=>this.runListener('clientReady', async ()=>{
|
|
100
|
+
this.activityInterval = setInterval(()=>this.updateActivity(), 10000);
|
|
101
|
+
await this.registerCommands();
|
|
102
|
+
}));
|
|
103
|
+
this.bot.on('interactionCreate', (interaction)=>this.runListener('interactionCreate', ()=>this.handleInteraction(interaction)));
|
|
104
|
+
this.bot.on('messageCreate', (message)=>this.runListener('messageCreate', ()=>this.handleMessage(message)));
|
|
105
|
+
this.bot.on('messageReactionAdd', (reaction, user)=>this.runListener('messageReactionAdd', ()=>this.handleReaction(reaction, {
|
|
106
|
+
user,
|
|
107
|
+
action: ReactionHandlerAction.ADD
|
|
108
|
+
})));
|
|
109
|
+
this.bot.on('messageReactionRemove', (reaction, user)=>this.runListener('messageReactionRemove', ()=>this.handleReaction(reaction, {
|
|
110
|
+
user,
|
|
111
|
+
action: ReactionHandlerAction.REMOVE
|
|
112
|
+
})));
|
|
113
|
+
await this.bot.login(this.discordToken);
|
|
114
|
+
this.logger.log('Bot is online!');
|
|
113
115
|
}
|
|
114
116
|
async registerCommands() {
|
|
115
117
|
// Keyed by type and name together, because that pair is what Discord treats as one
|
package/dist/esm/package.json.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { loadMeoCordCliConfig } from './meocord-source-config.util.js';
|
|
4
4
|
import wait from './wait.util.js';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
|
|
@@ -47,7 +47,7 @@ import chalk from 'chalk';
|
|
|
47
47
|
await wait(100);
|
|
48
48
|
process.exit(1);
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
loadMeoCordCliConfig();
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* Ensures a Discord token is configured.
|
|
@@ -56,7 +56,7 @@ import chalk from 'chalk';
|
|
|
56
56
|
* credentials — only connecting to the gateway does. Requiring one to build meant a
|
|
57
57
|
* freshly created application could not be built until a token had been obtained.
|
|
58
58
|
*/ async function validateDiscordToken() {
|
|
59
|
-
if (!
|
|
59
|
+
if (!loadMeoCordCliConfig()?.discordToken) {
|
|
60
60
|
console.error(chalk.red('Discord token is missing!'));
|
|
61
61
|
await wait(100);
|
|
62
62
|
process.exit(1);
|