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.
Files changed (52) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +160 -28
  3. package/THIRD_PARTY_NOTICES.md +40 -0
  4. package/dist/cjs/_shared/{controller.decorator-eT0oHb8D.cjs → controller.decorator-9nIxOp9y.cjs} +1 -1
  5. package/dist/cjs/_shared/{metadata-key.enum-DITxfZlw.cjs → metadata-key.enum-aABGdodb.cjs} +0 -4
  6. package/dist/cjs/_shared/{theme-EmG1ZEwV.cjs → theme-BXdSoifk.cjs} +4 -15
  7. package/dist/cjs/common/index.cjs +1 -5
  8. package/dist/cjs/core/index.cjs +67 -5
  9. package/dist/cjs/decorator/index.cjs +2 -2
  10. package/dist/cjs/enum/index.cjs +1 -1
  11. package/dist/cjs/testing/index.cjs +2 -6
  12. package/dist/esm/bin/app-template/meocord.config.ts.template +8 -13
  13. package/dist/esm/bin/app-template/package.json.template +12 -12
  14. package/dist/esm/bin/app-template/vitest.config.ts.template +6 -2
  15. package/dist/esm/bin/builder-template/builder/context-menu.builder.template +2 -2
  16. package/dist/esm/bin/builder-template/builder/primary-entry-point.builder.template +2 -2
  17. package/dist/esm/bin/builder-template/builder/slash.builder.template +2 -2
  18. package/dist/esm/bin/builder-template/controller/autocomplete.controller.template +4 -3
  19. package/dist/esm/bin/builder-template/controller/context-menu.controller.template +2 -2
  20. package/dist/esm/bin/builder-template/controller/primary-entry-point.controller.template +2 -2
  21. package/dist/esm/bin/builder-template/controller/slash.controller.template +2 -2
  22. package/dist/esm/bin/helper/controller-generator.helper.js +43 -26
  23. package/dist/esm/bin/helper/guard-generator.helper.js +8 -2
  24. package/dist/esm/bin/helper/service-generator.helper.js +8 -2
  25. package/dist/esm/bin/meocord.js +160 -140
  26. package/dist/esm/build/native-addons.js +235 -0
  27. package/dist/esm/build/rsbuild-config.js +169 -0
  28. package/dist/esm/common/decorator.js +0 -4
  29. package/dist/esm/common/theme.js +1 -5
  30. package/dist/esm/core/meocord-factory.js +4 -0
  31. package/dist/esm/enum/controller.enum.js +1 -5
  32. package/dist/esm/enum/metadata-key.enum.js +0 -4
  33. package/dist/esm/package.json.js +1 -1
  34. package/dist/esm/testing/mock-fn.js +1 -5
  35. package/dist/esm/util/generator-cli.util.js +54 -10
  36. package/dist/esm/util/json.util.js +0 -4
  37. package/dist/esm/util/meocord-config-loader.util.js +14 -2
  38. package/dist/esm/util/platform.util.js +66 -0
  39. package/dist/esm/util/runtime.util.js +20 -7
  40. package/dist/esm/util/tsconfig.util.js +1 -1
  41. package/dist/esm/util/wait.util.js +1 -5
  42. package/dist/types/common/index.d.ts +0 -16
  43. package/dist/types/core/index.d.ts +0 -12
  44. package/dist/types/decorator/index.d.ts +1 -31
  45. package/dist/types/enum/index.d.ts +0 -5
  46. package/dist/types/interface/index.d.ts +46 -57
  47. package/dist/types/testing/index.d.ts +4 -19
  48. package/meocord.eslint.cjs +0 -6
  49. package/meocord.eslint.d.ts +0 -6
  50. package/meocord.eslint.mjs +0 -6
  51. package/package.json +19 -23
  52. 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 };
@@ -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
  * Composes multiple class or method decorators into a single decorator.
7
3
  *
8
4
  * @example
@@ -1,8 +1,4 @@
1
- /**
2
- * MeoCord Framework
3
- * Copyright (c) 2025-present Ukasyah Rahmatullah Zada
4
- * SPDX-License-Identifier: MIT
5
- */ class Theme {
1
+ class Theme {
6
2
  }
7
3
  Theme.successColor = '#28A745';
8
4
  Theme.infoColor = '#17A2B8';
@@ -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,
@@ -1,4 +1,4 @@
1
- var version = "3.2.2";
1
+ var version = "4.0.0-beta.0";
2
2
  var packageJson = {
3
3
  version: version};
4
4
 
@@ -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('Guard name is required.');
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('Invalid guard name.');
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
- * @param filePath - The absolute path of the file to create or overwrite.
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
- logger.log(`Created ${path.relative(process.cwd(), filePath)}`);
106
+ fs.writeFileSync(filePath, content, {
107
+ flag: 'wx'
108
+ });
109
+ logger.log(`Created ${relative}`);
71
110
  formatWithLocalESLint(filePath);
72
111
  } catch (error) {
73
- logger.error(`Failed to create ${filePath}`, error);
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
- */ function loadSourceConfig() {
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 usage with tools like Webpack.
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,8 +1,4 @@
1
- /**
2
- * MeoCord Framework
3
- * Copyright (c) 2025-present Ukasyah Rahmatullah Zada
4
- * SPDX-License-Identifier: MIT
5
- */ function wait(ms) {
1
+ function wait(ms) {
6
2
  return new Promise((resolve)=>setTimeout(resolve, ms));
7
3
  }
8
4
 
@@ -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 'webpack';
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
  *