meocord 3.2.2 → 4.0.0-beta.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/CHANGELOG.md +60 -0
- package/README.md +160 -28
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/cjs/_shared/{controller.decorator-eT0oHb8D.cjs → controller.decorator-9nIxOp9y.cjs} +1 -1
- package/dist/cjs/_shared/{metadata-key.enum-DITxfZlw.cjs → metadata-key.enum-aABGdodb.cjs} +0 -4
- package/dist/cjs/_shared/{theme-EmG1ZEwV.cjs → theme-BXdSoifk.cjs} +4 -15
- package/dist/cjs/common/index.cjs +1 -5
- package/dist/cjs/core/index.cjs +67 -5
- package/dist/cjs/decorator/index.cjs +2 -2
- package/dist/cjs/enum/index.cjs +1 -1
- package/dist/cjs/testing/index.cjs +2 -6
- package/dist/esm/bin/app-template/meocord.config.ts.template +8 -13
- package/dist/esm/bin/app-template/package.json.template +12 -12
- package/dist/esm/bin/app-template/vitest.config.ts.template +6 -2
- package/dist/esm/bin/builder-template/builder/context-menu.builder.template +2 -2
- package/dist/esm/bin/builder-template/builder/primary-entry-point.builder.template +2 -2
- package/dist/esm/bin/builder-template/builder/slash.builder.template +2 -2
- package/dist/esm/bin/builder-template/controller/autocomplete.controller.template +4 -3
- package/dist/esm/bin/builder-template/controller/context-menu.controller.template +2 -2
- package/dist/esm/bin/builder-template/controller/primary-entry-point.controller.template +2 -2
- package/dist/esm/bin/builder-template/controller/slash.controller.template +2 -2
- package/dist/esm/bin/helper/controller-generator.helper.js +43 -26
- package/dist/esm/bin/helper/guard-generator.helper.js +8 -2
- package/dist/esm/bin/helper/service-generator.helper.js +8 -2
- package/dist/esm/bin/meocord.js +160 -140
- package/dist/esm/build/native-addons.js +235 -0
- package/dist/esm/build/rsbuild-config.js +169 -0
- package/dist/esm/common/decorator.js +0 -4
- package/dist/esm/common/theme.js +1 -5
- package/dist/esm/core/meocord-factory.js +4 -0
- package/dist/esm/enum/controller.enum.js +1 -5
- package/dist/esm/enum/metadata-key.enum.js +0 -4
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/mock-fn.js +1 -5
- package/dist/esm/util/generator-cli.util.js +54 -10
- package/dist/esm/util/json.util.js +0 -4
- package/dist/esm/util/meocord-config-loader.util.js +14 -2
- package/dist/esm/util/platform.util.js +66 -0
- package/dist/esm/util/runtime.util.js +20 -7
- package/dist/esm/util/tsconfig.util.js +1 -1
- package/dist/esm/util/wait.util.js +1 -5
- package/dist/types/common/index.d.ts +0 -16
- package/dist/types/core/index.d.ts +0 -12
- package/dist/types/decorator/index.d.ts +1 -31
- package/dist/types/enum/index.d.ts +0 -5
- package/dist/types/interface/index.d.ts +46 -57
- package/dist/types/testing/index.d.ts +4 -19
- package/meocord.eslint.cjs +0 -6
- package/meocord.eslint.d.ts +0 -6
- package/meocord.eslint.mjs +0 -6
- package/package.json +19 -23
- package/webpack.config.js +0 -147
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { prepareModifiedTsConfig } from '../util/tsconfig.util.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Native accelerators discord.js reaches for at runtime and works without.
|
|
6
|
+
*
|
|
7
|
+
* `bufferutil` and `utf-8-validate` are `ws`'s optional peers; `zlib-sync` is loaded by
|
|
8
|
+
* `@discordjs/ws` without being declared at all, falling back to uncompressed identify when it
|
|
9
|
+
* is absent. A bundler cannot tell "optional" from "missing", so bundling dependencies failed on
|
|
10
|
+
* `Can't resolve 'zlib-sync'` for every bot until these were left as runtime imports -- where a
|
|
11
|
+
* missing one is caught and ignored, exactly as it is when nothing is bundled.
|
|
12
|
+
*
|
|
13
|
+
* They are externalised as `node-commonjs` rather than plain names. A plain name in an ESM build
|
|
14
|
+
* becomes a hoisted top-level `import`, which throws `Cannot find package 'bufferutil'` before the
|
|
15
|
+
* bot runs a line -- the opposite of the try/catch `ws` wraps around it. `node-commonjs` keeps a
|
|
16
|
+
* runtime `require` at the original call site, so the library's own fallback still applies.
|
|
17
|
+
*/ const DISCORD_OPTIONAL_NATIVES = [
|
|
18
|
+
'zlib-sync',
|
|
19
|
+
'bufferutil',
|
|
20
|
+
'utf-8-validate'
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* The prefix an asset import is joined to at runtime: the output directory, with forward slashes.
|
|
24
|
+
*
|
|
25
|
+
* Rspack writes this into the bundle as a string literal without escaping it. A Windows path
|
|
26
|
+
* came through as `"D:\\a\\meocord\\dist/"` in the source, where `\\a` is a bell character and
|
|
27
|
+
* the other backslashes vanish -- every asset import evaluated to `D:ameocorddist/assets/...`.
|
|
28
|
+
* Windows accepts forward slashes everywhere a path is read, so they are used on every platform.
|
|
29
|
+
*/ function assetPrefixFor(distDir) {
|
|
30
|
+
return `${distDir.replace(/\\/g, '/').replace(/\/+$/, '')}/`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The bundler configuration MeoCord builds an application with.
|
|
34
|
+
*
|
|
35
|
+
* Built in code rather than read from a config file, so it is typed, testable, and not
|
|
36
|
+
* something a consumer can edit by accident.
|
|
37
|
+
*/ function createRsbuildConfig(options) {
|
|
38
|
+
const { mode, bundleDependencies = false, externals = [] } = options;
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const entry = options.entry ?? path.resolve(cwd, 'src', 'main.ts');
|
|
41
|
+
return {
|
|
42
|
+
source: {
|
|
43
|
+
entry: {
|
|
44
|
+
main: entry
|
|
45
|
+
},
|
|
46
|
+
// Equivalent to experimentalDecorators. The decorators themselves are only half of
|
|
47
|
+
// what MeoCord needs -- see tools.swc below for the half that carries the metadata.
|
|
48
|
+
decorators: {
|
|
49
|
+
version: 'legacy'
|
|
50
|
+
},
|
|
51
|
+
tsconfigPath: prepareModifiedTsConfig()
|
|
52
|
+
},
|
|
53
|
+
tools: {
|
|
54
|
+
swc: {
|
|
55
|
+
jsc: {
|
|
56
|
+
// Rsbuild leaves externalHelpers on, which emits the decorator helpers as imports
|
|
57
|
+
// from `@swc/helpers` -- which in turn wants `tslib` resolvable in the application.
|
|
58
|
+
// Inlining them keeps the output self-contained, which matters most when
|
|
59
|
+
// bundleDependencies is on and there is no node_modules to resolve through.
|
|
60
|
+
externalHelpers: false,
|
|
61
|
+
transform: {
|
|
62
|
+
legacyDecorator: true,
|
|
63
|
+
// Inversify resolves constructor arguments from `design:paramtypes`, which swc
|
|
64
|
+
// emits only with this on. Without it every @Controller throws while its class is
|
|
65
|
+
// being defined. It is deep-merged into builtin:swc-loader rather than replacing
|
|
66
|
+
// it, so the rest of Rsbuild's swc defaults still apply.
|
|
67
|
+
decoratorMetadata: true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
output: {
|
|
73
|
+
target: 'node',
|
|
74
|
+
// Rsbuild 2 already defaults Node builds to ESM; stated so the output format does not
|
|
75
|
+
// silently change with a future default.
|
|
76
|
+
module: true,
|
|
77
|
+
// Inverted deliberately: autoExternal leaves dependencies as runtime imports, so
|
|
78
|
+
// bundling them means turning it off.
|
|
79
|
+
autoExternal: !bundleDependencies,
|
|
80
|
+
externals: [
|
|
81
|
+
Object.fromEntries(DISCORD_OPTIONAL_NATIVES.map((name)=>[
|
|
82
|
+
name,
|
|
83
|
+
`node-commonjs ${name}`
|
|
84
|
+
])),
|
|
85
|
+
...externals
|
|
86
|
+
],
|
|
87
|
+
// Rsbuild would put the bundle in dist/static/js and assets in dist/static/*. The
|
|
88
|
+
// application's entry is dist/main.js, which is what `meocord start` runs.
|
|
89
|
+
distPath: {
|
|
90
|
+
root: path.resolve(cwd, 'dist'),
|
|
91
|
+
js: '',
|
|
92
|
+
image: 'assets',
|
|
93
|
+
svg: 'assets',
|
|
94
|
+
font: 'assets',
|
|
95
|
+
media: 'assets'
|
|
96
|
+
},
|
|
97
|
+
// No content hash: a bot reads its assets from disk rather than serving them from a CDN,
|
|
98
|
+
// so there is no cache to bust, and stable names keep `dist/assets/` predictable. Each
|
|
99
|
+
// accepts a function too, for applications whose same-named files in different folders
|
|
100
|
+
// would otherwise collide.
|
|
101
|
+
filename: {
|
|
102
|
+
js: '[name].js',
|
|
103
|
+
image: '[name][ext]',
|
|
104
|
+
svg: '[name][ext]',
|
|
105
|
+
font: '[name][ext]',
|
|
106
|
+
media: '[name][ext]'
|
|
107
|
+
},
|
|
108
|
+
// What `import image from './x.png'` evaluates to at runtime. A bot passes that string
|
|
109
|
+
// to fs or to a Discord attachment, so it has to be a real path on disk, which Rsbuild's
|
|
110
|
+
// web-oriented default is not.
|
|
111
|
+
assetPrefix: assetPrefixFor(path.resolve(cwd, 'dist')),
|
|
112
|
+
// Rsbuild inlines assets under 4 KB as base64 data URIs, so the same import would give a
|
|
113
|
+
// path for a large file and a `data:` string for a small one. A bot reads its assets
|
|
114
|
+
// with fs, where a data URI is ENOENT, so every asset is emitted as a file.
|
|
115
|
+
dataUriLimit: 0,
|
|
116
|
+
// Rsbuild emits no source maps in production by default, which would leave a crashed
|
|
117
|
+
// bot's stack trace pointing into the bundle instead of the source.
|
|
118
|
+
sourceMap: {
|
|
119
|
+
js: mode === 'production' ? 'source-map' : 'eval-source-map'
|
|
120
|
+
},
|
|
121
|
+
// Rsbuild empties dist before building by default. MeoCord runs two builds into the same
|
|
122
|
+
// directory -- the application, and meocord.config.ts into dist/meocord.config.mjs -- so
|
|
123
|
+
// cleaning would let whichever runs second erase the other.
|
|
124
|
+
cleanDistPath: false,
|
|
125
|
+
minify: {
|
|
126
|
+
// Off by default for Node targets in Rsbuild 2, so production builds would ship
|
|
127
|
+
// unminified unless this is stated.
|
|
128
|
+
js: mode === 'production',
|
|
129
|
+
jsOptions: {
|
|
130
|
+
minimizerOptions: {
|
|
131
|
+
// Inversify resolves dependencies by class identity, so a mangled class name
|
|
132
|
+
// breaks injection in production while development stays fine.
|
|
133
|
+
mangle: {
|
|
134
|
+
keep_classnames: true,
|
|
135
|
+
keep_fnames: true
|
|
136
|
+
},
|
|
137
|
+
compress: {
|
|
138
|
+
keep_classnames: true,
|
|
139
|
+
keep_fnames: true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
mode,
|
|
146
|
+
performance: {
|
|
147
|
+
// The bot is not served over a network; splitting it only makes startup resolve more files.
|
|
148
|
+
chunkSplit: {
|
|
149
|
+
strategy: 'all-in-one'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Refuses a MeoCord config that declares a `webpack` hook, which MeoCord does not run.
|
|
156
|
+
*
|
|
157
|
+
* Without this, such a config builds "successfully" with the customisation silently dropped --
|
|
158
|
+
* assets landing in the wrong place, markdown imported as a path instead of its text. Refusing
|
|
159
|
+
* is kinder than a green build that ships something else.
|
|
160
|
+
*
|
|
161
|
+
* Read as a property rather than tested with `in`: a config loaded from source comes back as
|
|
162
|
+
* jiti's interop proxy, where property access reaches the default export but `in` and
|
|
163
|
+
* Object.keys only see `default` -- so `'webpack' in config` is false even when it is set.
|
|
164
|
+
*/ function assertNoWebpackHook(config) {
|
|
165
|
+
if (config?.webpack === undefined) return;
|
|
166
|
+
throw new Error('meocord.config.ts still declares a `webpack` hook, which MeoCord no longer runs. ' + 'Rename it to `rsbuild` and reshape its body for Rsbuild: images, fonts, svg and media ' + 'need no rules any more, `output.filename` accepts a function for custom asset paths, ' + 'and raw rules go through `tools.rspack`. See the 4.0.0 release notes.');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { DISCORD_OPTIONAL_NATIVES, assertNoWebpackHook, assetPrefixFor, createRsbuildConfig };
|
package/dist/esm/common/theme.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Logger } from '../common/logger.js';
|
|
|
5
5
|
import '../common/theme.js';
|
|
6
6
|
import { MeoCordApp } from './meocord.app.js';
|
|
7
7
|
import { loadMeoCordConfig } from '../util/meocord-config-loader.util.js';
|
|
8
|
+
import { assertBuiltForThisPlatform } from '../util/platform.util.js';
|
|
8
9
|
import { MetadataKey } from '../enum/metadata-key.enum.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -36,6 +37,9 @@ class MeoCordFactory {
|
|
|
36
37
|
if (!meocordConfig) {
|
|
37
38
|
throw new Error('MeoCord config not found. Ensure meocord.config.ts exists.');
|
|
38
39
|
}
|
|
40
|
+
// Before anything is resolved: a controller or service is what first loads a native addon, and
|
|
41
|
+
// one built for another platform would otherwise fail there with a linker error.
|
|
42
|
+
assertBuiltForThisPlatform();
|
|
39
43
|
const container = new Container();
|
|
40
44
|
// Bind the Discord client as a constant value
|
|
41
45
|
const discordClient = new Client(options.clientOptions);
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* MeoCord Framework
|
|
3
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/ var ControllerType = /*#__PURE__*/ function(ControllerType) {
|
|
1
|
+
var ControllerType = /*#__PURE__*/ function(ControllerType) {
|
|
6
2
|
ControllerType["BUTTON"] = "button";
|
|
7
3
|
ControllerType["MODAL_SUBMIT"] = "modal-submit";
|
|
8
4
|
ControllerType["SELECT_MENU"] = "select-menu";
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MeoCord Framework
|
|
3
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/ /**
|
|
6
2
|
* Centralised metadata keys used across the framework's `Reflect` calls.
|
|
7
3
|
*
|
|
8
4
|
* Keeping them here prevents typos, documents the Inversify 8 key rename,
|
package/dist/esm/package.json.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* MeoCord Framework
|
|
3
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/ // ---------------------------------------------------------------------------
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
6
2
|
// Framework-agnostic mock function
|
|
7
3
|
//
|
|
8
4
|
// A minimal mock-fn implementation used internally by the testing utilities so
|
|
@@ -31,14 +31,15 @@ const logger = new Logger('MeoCord');
|
|
|
31
31
|
* @throws Will exit the process if the name is undefined, invalid,
|
|
32
32
|
* or the generated class name is invalid.
|
|
33
33
|
*/ function validateAndFormatName(originalName) {
|
|
34
|
+
// Shared by every generator -- controllers, services and guards -- so the messages name none.
|
|
34
35
|
if (!originalName) {
|
|
35
|
-
logger.error('
|
|
36
|
+
logger.error('A name is required.');
|
|
36
37
|
process.exit(1);
|
|
37
38
|
}
|
|
38
39
|
const parts = originalName.split('/');
|
|
39
40
|
const fileName = parts.pop();
|
|
40
41
|
if (!fileName) {
|
|
41
|
-
logger.error(
|
|
42
|
+
logger.error(`Invalid name: "${originalName}".`);
|
|
42
43
|
process.exit(1);
|
|
43
44
|
}
|
|
44
45
|
const kebabCaseName = kebabCase(fileName);
|
|
@@ -46,9 +47,39 @@ const logger = new Logger('MeoCord');
|
|
|
46
47
|
return {
|
|
47
48
|
parts,
|
|
48
49
|
kebabCaseName,
|
|
49
|
-
className
|
|
50
|
+
className,
|
|
51
|
+
commandName: commandNameFor(parts, kebabCaseName)
|
|
50
52
|
};
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The Discord command name a generated controller registers.
|
|
56
|
+
*
|
|
57
|
+
* The whole path, not only its last segment: `admin/ban` becomes `admin-ban`. Files are kept
|
|
58
|
+
* apart by their directories, but a Discord command name is global to the application, so two
|
|
59
|
+
* controllers named `ban` in different folders must not both register `ban`.
|
|
60
|
+
*
|
|
61
|
+
* @param parts - The folders the name is nested in.
|
|
62
|
+
* @param kebabCaseName - The name's last segment, already kebab-cased.
|
|
63
|
+
*/ function commandNameFor(parts, kebabCaseName) {
|
|
64
|
+
return [
|
|
65
|
+
...parts.map((part)=>kebabCase(part)),
|
|
66
|
+
kebabCaseName
|
|
67
|
+
].filter(Boolean).join('-');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Stops a generator before it writes anything, if any file it would write already exists.
|
|
71
|
+
*
|
|
72
|
+
* Checked for every file up front rather than one at a time, so a refusal never leaves half a
|
|
73
|
+
* component behind, and a file the user has edited is never replaced.
|
|
74
|
+
*
|
|
75
|
+
* @param filePaths - Absolute paths the generator is about to create.
|
|
76
|
+
*/ function assertFilesAbsent(filePaths) {
|
|
77
|
+
const existing = filePaths.filter((filePath)=>fs.existsSync(filePath));
|
|
78
|
+
if (existing.length === 0) return;
|
|
79
|
+
const names = existing.map((filePath)=>path.relative(process.cwd(), filePath)).join(', ');
|
|
80
|
+
logger.error(`Refusing to overwrite ${existing.length === 1 ? 'an existing file' : 'existing files'}: ${names}. ` + 'Nothing was generated. Choose another name, or move the existing file first.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
52
83
|
/**
|
|
53
84
|
* Ensures that a given directory exists. Creates the directory and any necessary parent directories if they do not exist.
|
|
54
85
|
* @param directory - The absolute path of the directory to create.
|
|
@@ -60,17 +91,30 @@ const logger = new Logger('MeoCord');
|
|
|
60
91
|
}
|
|
61
92
|
}
|
|
62
93
|
/**
|
|
63
|
-
* Writes the provided content to a file and runs ESLint on the file for formatting.
|
|
64
|
-
*
|
|
94
|
+
* Writes the provided content to a new file and runs ESLint on the file for formatting.
|
|
95
|
+
*
|
|
96
|
+
* Never replaces a file: the write is exclusive, so an existing file is left exactly as it was.
|
|
97
|
+
* Generators check with {@link assertFilesAbsent} before writing anything; this is the last line
|
|
98
|
+
* behind that. A failed write sets a non-zero exit code rather than ending with the success status
|
|
99
|
+
* of a run that produced nothing.
|
|
100
|
+
*
|
|
101
|
+
* @param filePath - The absolute path of the file to create.
|
|
65
102
|
* @param content - The content to write to the file.
|
|
66
|
-
* @throws Logs an error if the file creation or ESLint command fails.
|
|
67
103
|
*/ function generateFile(filePath, content) {
|
|
104
|
+
const relative = path.relative(process.cwd(), filePath);
|
|
68
105
|
try {
|
|
69
|
-
fs.writeFileSync(filePath, content
|
|
70
|
-
|
|
106
|
+
fs.writeFileSync(filePath, content, {
|
|
107
|
+
flag: 'wx'
|
|
108
|
+
});
|
|
109
|
+
logger.log(`Created ${relative}`);
|
|
71
110
|
formatWithLocalESLint(filePath);
|
|
72
111
|
} catch (error) {
|
|
73
|
-
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
if (error?.code === 'EEXIST') {
|
|
114
|
+
logger.error(`${relative} already exists; left it untouched.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
logger.error(`Failed to create ${relative}`, error);
|
|
74
118
|
}
|
|
75
119
|
}
|
|
76
120
|
/**
|
|
@@ -121,4 +165,4 @@ const logger = new Logger('MeoCord');
|
|
|
121
165
|
return template;
|
|
122
166
|
}
|
|
123
167
|
|
|
124
|
-
export { buildTemplate, createDirectoryIfNotExists, generateFile, populateTemplate, toClassName, validateAndFormatName };
|
|
168
|
+
export { assertFilesAbsent, buildTemplate, commandNameFor, createDirectoryIfNotExists, generateFile, populateTemplate, toClassName, validateAndFormatName };
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MeoCord Framework
|
|
3
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/ /**
|
|
6
2
|
* Helper function to fix common JSON formatting issues in tsconfig.json, such as:
|
|
7
3
|
* - Removing single-line comments.
|
|
8
4
|
* - Removing trailing commas.
|
|
@@ -40,7 +40,19 @@ let configLoaded = false;
|
|
|
40
40
|
/**
|
|
41
41
|
* Loads the source config from meocord.config.ts via jiti with tsconfig path alias resolution.
|
|
42
42
|
* Used in development mode where source files and tsconfig.json are available.
|
|
43
|
-
*/
|
|
43
|
+
*/ /**
|
|
44
|
+
* Loads meocord.config.ts from source, every time, for configuring a build.
|
|
45
|
+
*
|
|
46
|
+
* {@link loadMeoCordConfig} prefers the compiled `dist/meocord.config.mjs` and caches the result,
|
|
47
|
+
* which is right at runtime -- production has no source -- and wrong while building. The compiled
|
|
48
|
+
* file is the previous build's output, so a build that read it ran on the config as it was last
|
|
49
|
+
* time: an edit to `meocord.config.ts` took effect only on the build after next. Whatever read the
|
|
50
|
+
* config first in the process also fixed it for the rest, so the watcher's reload on a config
|
|
51
|
+
* change reloaded nothing. Build time always has the source, so it reads the source.
|
|
52
|
+
*/ function loadMeoCordSourceConfig() {
|
|
53
|
+
return loadSourceConfig();
|
|
54
|
+
}
|
|
55
|
+
function loadSourceConfig() {
|
|
44
56
|
const configPath = path.resolve(process.cwd(), 'meocord.config.ts');
|
|
45
57
|
if (!existsSync(configPath)) return undefined;
|
|
46
58
|
try {
|
|
@@ -72,4 +84,4 @@ let configLoaded = false;
|
|
|
72
84
|
}
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
export { loadMeoCordConfig };
|
|
87
|
+
export { loadMeoCordConfig, loadMeoCordSourceConfig };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* File written beside a bundle that carries native addons, naming the platform it was built for.
|
|
6
|
+
*/ const PLATFORM_MANIFEST = 'meocord.platform.json';
|
|
7
|
+
/** The C library this process runs against, when the runtime can say. */ function detectLibc() {
|
|
8
|
+
if (process.platform !== 'linux') return undefined;
|
|
9
|
+
try {
|
|
10
|
+
const report = process.report?.getReport?.();
|
|
11
|
+
if (!report?.header) return undefined;
|
|
12
|
+
return report.header.glibcVersionRuntime ? 'glibc' : 'musl';
|
|
13
|
+
} catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** The platform this process is running on. */ function currentPlatform() {
|
|
18
|
+
const libc = detectLibc();
|
|
19
|
+
return {
|
|
20
|
+
platform: process.platform,
|
|
21
|
+
arch: process.arch,
|
|
22
|
+
...libc && {
|
|
23
|
+
libc
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** `linux-x64 (glibc)`, `darwin-arm64`. */ function describePlatform({ platform, arch, libc }) {
|
|
28
|
+
return `${platform}-${arch}${libc ? ` (${libc})` : ''}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether native binaries built for one platform can load on another.
|
|
32
|
+
*
|
|
33
|
+
* The C library is compared only when both sides are known, so a runtime that cannot report it
|
|
34
|
+
* is not refused on a guess.
|
|
35
|
+
*/ function isSamePlatform(built, running) {
|
|
36
|
+
if (built.platform !== running.platform || built.arch !== running.arch) return false;
|
|
37
|
+
return !built.libc || !running.libc || built.libc === running.libc;
|
|
38
|
+
}
|
|
39
|
+
/** Records the platform a bundle's native addons were built for. */ function writePlatformManifest(distDir) {
|
|
40
|
+
writeFileSync(path.join(distDir, PLATFORM_MANIFEST), `${JSON.stringify(currentPlatform(), null, 2)}\n`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stops a bundle from starting on a platform its native addons were not built for.
|
|
44
|
+
*
|
|
45
|
+
* A binary for the wrong platform does not fail at startup. It fails when something first loads
|
|
46
|
+
* it -- for an image library, the first command that renders one -- with a dynamic linker error
|
|
47
|
+
* that names a file rather than the cause. Checking the manifest up front turns that into one
|
|
48
|
+
* clear message before the bot goes online.
|
|
49
|
+
*
|
|
50
|
+
* @param distDir - Directory holding the manifest. Defaults to the directory of the entry script.
|
|
51
|
+
*/ function assertBuiltForThisPlatform(distDir = process.argv[1] ? path.dirname(process.argv[1]) : undefined) {
|
|
52
|
+
if (!distDir) return;
|
|
53
|
+
const manifest = path.join(distDir, PLATFORM_MANIFEST);
|
|
54
|
+
if (!existsSync(manifest)) return;
|
|
55
|
+
let built;
|
|
56
|
+
try {
|
|
57
|
+
built = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const running = currentPlatform();
|
|
62
|
+
if (isSamePlatform(built, running)) return;
|
|
63
|
+
throw new Error(`This build carries native addons compiled for ${describePlatform(built)}, but is running on ` + `${describePlatform(running)}. Compiled binaries only load on the platform they were built for. ` + 'Build on the same platform you deploy to -- for a container, run `meocord build` inside the image.');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { PLATFORM_MANIFEST, assertBuiltForThisPlatform, currentPlatform, describePlatform, isSamePlatform, writePlatformManifest };
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MeoCord Framework
|
|
3
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/ /**
|
|
6
2
|
* Environment variable that pins the binary the application is spawned with.
|
|
7
3
|
*
|
|
8
4
|
* An escape hatch for pinning a specific install, or for running the application under a
|
|
@@ -61,12 +57,29 @@
|
|
|
61
57
|
* @param runtime - Binary to run the application with.
|
|
62
58
|
* @param mainJsPath - Absolute path to the built entry file.
|
|
63
59
|
*/ function buildAppCommand(runtime, mainJsPath) {
|
|
60
|
+
// With no node_modules directory in reach, bun installs a missing package from the registry
|
|
61
|
+
// the moment something imports it. A bot bundled with `bundleDependencies` is deployed exactly
|
|
62
|
+
// that way, so discord.js probing for its optional `zlib-sync` would download it in production
|
|
63
|
+
// instead of falling back. Nothing should reach the network at startup that the build did not
|
|
64
|
+
// put there.
|
|
65
|
+
const args = isBun(runtime) ? [
|
|
66
|
+
'--no-install',
|
|
67
|
+
mainJsPath
|
|
68
|
+
] : [
|
|
69
|
+
mainJsPath
|
|
70
|
+
];
|
|
64
71
|
return {
|
|
65
72
|
command: runtime,
|
|
66
|
-
args
|
|
67
|
-
mainJsPath
|
|
68
|
-
]
|
|
73
|
+
args
|
|
69
74
|
};
|
|
70
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether a runtime binary is bun, by the name it was resolved to.
|
|
78
|
+
*
|
|
79
|
+
* Split on both separators rather than through path.basename, which only knows the separator
|
|
80
|
+
* of the platform it runs on.
|
|
81
|
+
*/ function isBun(runtime) {
|
|
82
|
+
return /^bun(\.exe)?$/i.test(runtime.split(/[\\/]/).pop() ?? '');
|
|
83
|
+
}
|
|
71
84
|
|
|
72
85
|
export { RUNTIME_OVERRIDE_ENV, buildAppCommand, resolveRuntime };
|
|
@@ -7,7 +7,7 @@ import { fixJSON } from './json.util.js';
|
|
|
7
7
|
|
|
8
8
|
const logger = new Logger();
|
|
9
9
|
/**
|
|
10
|
-
* Prepares and modifies the project's `tsconfig.json` file for
|
|
10
|
+
* Prepares and modifies the project's `tsconfig.json` file for the bundler.
|
|
11
11
|
* - Verifies the existence of `tsconfig.json`.
|
|
12
12
|
* - Fixes invalid JSON if necessary by correcting formatting issues like comments or trailing commas.
|
|
13
13
|
* - Updates paths in `compilerOptions` and other sections to absolute paths.
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { ColorResolvable } from 'discord.js';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* MeoCord Framework
|
|
5
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
6
|
-
* SPDX-License-Identifier: MIT
|
|
7
|
-
*/
|
|
8
3
|
declare class Logger {
|
|
9
4
|
private context?;
|
|
10
5
|
private readonly colorMap;
|
|
@@ -19,12 +14,6 @@ declare class Logger {
|
|
|
19
14
|
private logWithContext;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
/**
|
|
23
|
-
* MeoCord Framework
|
|
24
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
25
|
-
* SPDX-License-Identifier: MIT
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
17
|
declare class Theme {
|
|
29
18
|
static successColor: ColorResolvable;
|
|
30
19
|
static infoColor: ColorResolvable;
|
|
@@ -32,11 +21,6 @@ declare class Theme {
|
|
|
32
21
|
static warningColor: ColorResolvable;
|
|
33
22
|
}
|
|
34
23
|
|
|
35
|
-
/**
|
|
36
|
-
* MeoCord Framework
|
|
37
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
38
|
-
* SPDX-License-Identifier: MIT
|
|
39
|
-
*/
|
|
40
24
|
/**
|
|
41
25
|
* Composes multiple class or method decorators into a single decorator.
|
|
42
26
|
*
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { Container, ServiceIdentifier } from 'inversify';
|
|
2
2
|
import { Client, ActivityOptions } from 'discord.js';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* MeoCord Framework
|
|
6
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
7
|
-
* SPDX-License-Identifier: MIT
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
4
|
declare class MeoCordApp {
|
|
11
5
|
private readonly controllerClasses;
|
|
12
6
|
private readonly container;
|
|
@@ -122,12 +116,6 @@ declare class MeoCordApp {
|
|
|
122
116
|
private gracefulShutdown;
|
|
123
117
|
}
|
|
124
118
|
|
|
125
|
-
/**
|
|
126
|
-
* MeoCord Framework
|
|
127
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
128
|
-
* SPDX-License-Identifier: MIT
|
|
129
|
-
*/
|
|
130
|
-
|
|
131
119
|
declare class MeoCordFactory {
|
|
132
120
|
private static logger;
|
|
133
121
|
static create(target: ServiceIdentifier): MeoCordApp;
|
|
@@ -2,13 +2,7 @@ import { BuildableCommandType, CommandBuilderBase, CommandBuilderConstructor, Co
|
|
|
2
2
|
import { AutocompleteInteraction, OmitPartialGroupDMChannel, Message, MessageReaction, PartialMessageReaction, ClientOptions, ActivityOptions } from 'discord.js';
|
|
3
3
|
import { CommandType } from '../enum/index.js';
|
|
4
4
|
import { ServiceIdentifier } from 'inversify';
|
|
5
|
-
import '
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* MeoCord Framework
|
|
9
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
10
|
-
* SPDX-License-Identifier: MIT
|
|
11
|
-
*/
|
|
5
|
+
import '@rsbuild/core';
|
|
12
6
|
|
|
13
7
|
/**
|
|
14
8
|
* `@Service()` decorator to mark a class as a service that can be injected into controllers or used as standalone services.
|
|
@@ -29,12 +23,6 @@ import 'webpack';
|
|
|
29
23
|
*/
|
|
30
24
|
declare function Service<T>(): (target: new (...args: any[]) => T) => void;
|
|
31
25
|
|
|
32
|
-
/**
|
|
33
|
-
* MeoCord Framework
|
|
34
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
35
|
-
* SPDX-License-Identifier: MIT
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
26
|
/**
|
|
39
27
|
* This decorator is used to mark a class as a Discord command builder that later can be registered on the `@Command` decorator.
|
|
40
28
|
* It defines the command type using metadata and dynamically makes the class injectable if it isn't already.
|
|
@@ -55,12 +43,6 @@ declare function Service<T>(): (target: new (...args: any[]) => T) => void;
|
|
|
55
43
|
*/
|
|
56
44
|
declare function CommandBuilder<T extends BuildableCommandType>(commandType: T): (target: new () => CommandBuilderBase<T>) => void;
|
|
57
45
|
|
|
58
|
-
/**
|
|
59
|
-
* MeoCord Framework
|
|
60
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
61
|
-
* SPDX-License-Identifier: MIT
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
46
|
/**
|
|
65
47
|
* Decorator to register message handlers in the controller.
|
|
66
48
|
*
|
|
@@ -229,12 +211,6 @@ declare function Controller(): (target: any) => void;
|
|
|
229
211
|
*/
|
|
230
212
|
declare function findAmbiguousRoutes(patterns: string[]): [string, string][];
|
|
231
213
|
|
|
232
|
-
/**
|
|
233
|
-
* MeoCord Framework
|
|
234
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
235
|
-
* SPDX-License-Identifier: MIT
|
|
236
|
-
*/
|
|
237
|
-
|
|
238
214
|
/**
|
|
239
215
|
* `@Guard()` decorator to mark a class as a Guard that later can be added on `@UseGuard` decorator.
|
|
240
216
|
*
|
|
@@ -317,12 +293,6 @@ interface GuardWithParams {
|
|
|
317
293
|
*/
|
|
318
294
|
declare function UseGuard(...guards: ((new (...args: any[]) => GuardInterface) | GuardWithParams)[]): any;
|
|
319
295
|
|
|
320
|
-
/**
|
|
321
|
-
* MeoCord Framework
|
|
322
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
323
|
-
* SPDX-License-Identifier: MIT
|
|
324
|
-
*/
|
|
325
|
-
|
|
326
296
|
/**
|
|
327
297
|
* `@MeoCord()` decorator for declaring the MeoCord application class.
|
|
328
298
|
*
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
export { C as CommandType, R as ReactionHandlerAction } from '../controller.enum-DYfhYaat.js';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* MeoCord Framework
|
|
5
|
-
* Copyright (c) 2025-present Ukasyah Rahmatullah Zada
|
|
6
|
-
* SPDX-License-Identifier: MIT
|
|
7
|
-
*/
|
|
8
3
|
/**
|
|
9
4
|
* Centralised metadata keys used across the framework's `Reflect` calls.
|
|
10
5
|
*
|