meocord 3.0.0 → 3.2.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/AUTHOR.md +2 -3
- package/README.md +361 -55
- package/dist/cjs/_shared/controller.decorator-MUHA_A3z.cjs +529 -0
- package/dist/cjs/core/index.cjs +246 -86
- package/dist/cjs/decorator/index.cjs +4 -2
- package/dist/cjs/enum/index.cjs +17 -4
- package/dist/cjs/testing/index.cjs +88 -4
- package/dist/esm/bin/app-template/README.md.template +61 -0
- package/dist/esm/bin/app-template/_env.example.template +2 -0
- package/dist/esm/bin/app-template/_gitignore.template +22 -0
- package/dist/esm/bin/app-template/_prettierrc.mjs.template +10 -0
- package/dist/esm/bin/app-template/eslint.config.ts.template +25 -0
- package/dist/esm/bin/app-template/meocord.config.ts.template +25 -0
- package/dist/esm/bin/app-template/package.json.template +38 -0
- package/dist/esm/bin/app-template/src/app.ts.template +44 -0
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +19 -0
- package/dist/esm/bin/app-template/src/controllers/context-menu/builders/sample.builder.ts.template +10 -0
- package/dist/esm/bin/app-template/src/controllers/context-menu/sample.context-menu.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/context-menu/sample.context-menu.controller.ts.template +13 -0
- package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.ts.template +28 -0
- package/dist/esm/bin/app-template/src/controllers/modal-submit/sample.modal-submit.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/modal-submit/sample.modal-submit.controller.ts.template +13 -0
- package/dist/esm/bin/app-template/src/controllers/reaction/sample.reaction.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/reaction/sample.reaction.controller.ts.template +29 -0
- package/dist/esm/bin/app-template/src/controllers/select-menu/sample.select-menu.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/select-menu/sample.select-menu.controller.ts.template +11 -0
- package/dist/esm/bin/app-template/src/controllers/slash/builders/sample.builder.ts.template +10 -0
- package/dist/esm/bin/app-template/src/controllers/slash/sample.slash.controller.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/controllers/slash/sample.slash.controller.ts.template +19 -0
- package/dist/esm/bin/app-template/src/guards/rate-limit.guard.spec.ts.template +13 -0
- package/dist/esm/bin/app-template/src/guards/rate-limit.guard.ts.template +52 -0
- package/dist/esm/bin/app-template/src/main.ts.template +14 -0
- package/dist/esm/bin/app-template/src/services/sample.service.spec.ts.template +17 -0
- package/dist/esm/bin/app-template/src/services/sample.service.ts.template +9 -0
- package/dist/esm/bin/app-template/tsconfig.eslint.json.template +5 -0
- package/dist/esm/bin/app-template/tsconfig.json.template +30 -0
- package/dist/esm/bin/app-template/tsconfig.test.json.template +8 -0
- package/dist/esm/bin/app-template/vitest.config.ts.template +35 -0
- package/dist/esm/bin/builder-template/builder/primary-entry-point.builder.template +26 -0
- package/dist/esm/bin/builder-template/controller/autocomplete.controller.template +16 -0
- package/dist/esm/bin/builder-template/controller/button.controller.template +1 -1
- package/dist/esm/bin/builder-template/controller/channel-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/context-menu.controller.template +2 -2
- package/dist/esm/bin/builder-template/controller/mentionable-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/modal-submit.controller.template +1 -1
- package/dist/esm/bin/builder-template/controller/primary-entry-point.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/role-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/slash.controller.template +1 -1
- package/dist/esm/bin/builder-template/controller/user-select-menu.controller.template +12 -0
- package/dist/esm/bin/generator.js +4 -9
- package/dist/esm/bin/helper/app-generator.helper.js +78 -0
- package/dist/esm/bin/helper/controller-generator.helper.js +24 -7
- package/dist/esm/bin/meocord.js +156 -75
- package/dist/esm/core/meocord.app.js +247 -86
- package/dist/esm/decorator/controller.decorator.js +73 -10
- package/dist/esm/decorator/guard.decorator.js +1 -1
- package/dist/esm/decorator/index.js +1 -1
- package/dist/esm/enum/controller.enum.js +23 -4
- package/dist/esm/testing/mock-interaction.js +89 -5
- package/dist/esm/util/common.util.js +11 -3
- package/dist/esm/util/generator-cli.util.js +21 -4
- package/dist/esm/util/interaction.util.js +174 -0
- package/dist/esm/util/package-manager.util.js +9 -2
- package/dist/esm/util/package-version.util.js +32 -0
- package/dist/esm/util/runtime.util.js +72 -0
- package/dist/types/controller.enum-DYfhYaat.d.ts +36 -0
- package/dist/types/core/index.d.ts +75 -2
- package/dist/types/decorator/index.d.ts +61 -49
- package/dist/types/enum/index.d.ts +1 -1
- package/dist/types/interface/index.d.ts +89 -3
- package/dist/types/testing/index.d.ts +3 -21
- package/package.json +11 -12
- package/dist/cjs/_shared/controller.decorator-CC6BjHkS.cjs +0 -288
- package/dist/types/controller.enum-QA-IuReF.d.ts +0 -18
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"strictNullChecks": true,
|
|
8
|
+
"strictBindCallApply": true,
|
|
9
|
+
"strictPropertyInitialization": false,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"noFallthroughCasesInSwitch": true,
|
|
12
|
+
"emitDecoratorMetadata": true,
|
|
13
|
+
"experimentalDecorators": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"noImplicitAny": false,
|
|
20
|
+
"noEmit": true,
|
|
21
|
+
"outDir": "./dist",
|
|
22
|
+
"rootDir": ".",
|
|
23
|
+
"paths": {
|
|
24
|
+
"@src/*": ["./src/*"]
|
|
25
|
+
},
|
|
26
|
+
"types": ["node"]
|
|
27
|
+
},
|
|
28
|
+
"include": ["src/**/*.ts"],
|
|
29
|
+
"exclude": ["meocord.config.ts", "dist", "vitest.config.ts", "node_modules", "src/**/*.spec.ts"]
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import swc from 'unplugin-swc'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
// SWC rather than vite's esbuild: esbuild cannot emit decorator metadata, which the
|
|
7
|
+
// dependency injection behind @Controller and @Service reads at runtime.
|
|
8
|
+
plugins: [
|
|
9
|
+
swc.vite({
|
|
10
|
+
jsc: {
|
|
11
|
+
parser: { syntax: 'typescript', decorators: true },
|
|
12
|
+
transform: { legacyDecorator: true, decoratorMetadata: true },
|
|
13
|
+
target: 'es2022',
|
|
14
|
+
},
|
|
15
|
+
module: { type: 'es6' },
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
resolve: {
|
|
19
|
+
alias: { '@src': fileURLToPath(new URL('./src', import.meta.url)) },
|
|
20
|
+
},
|
|
21
|
+
test: {
|
|
22
|
+
globals: true,
|
|
23
|
+
setupFiles: ['reflect-metadata'],
|
|
24
|
+
clearMocks: true,
|
|
25
|
+
include: ['src/**/*.spec.ts'],
|
|
26
|
+
coverage: {
|
|
27
|
+
// istanbul rather than v8: v8 coverage needs Node's inspector API, which bun does
|
|
28
|
+
// not implement, and this template is expected to run under either runtime.
|
|
29
|
+
provider: 'istanbul',
|
|
30
|
+
all: false,
|
|
31
|
+
include: ['src/**/*.ts'],
|
|
32
|
+
exclude: ['src/**/*.spec.ts'],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApplicationCommandType,
|
|
3
|
+
ApplicationIntegrationType,
|
|
4
|
+
EntryPointCommandHandlerType,
|
|
5
|
+
InteractionContextType,
|
|
6
|
+
} from 'discord.js'
|
|
7
|
+
import { CommandBuilder } from 'meocord/decorator'
|
|
8
|
+
import { CommandType } from 'meocord/enum'
|
|
9
|
+
|
|
10
|
+
// Entry point commands have no builder class in @discordjs/builders, so the raw REST
|
|
11
|
+
// body is returned instead — MeoCord registers it as-is. `AppHandler` is what makes
|
|
12
|
+
// Discord send the interaction here; `DiscordLaunchActivity` opens the activity without
|
|
13
|
+
// ever reaching the bot.
|
|
14
|
+
@CommandBuilder(CommandType.PRIMARY_ENTRY_POINT)
|
|
15
|
+
export class SampleCommandBuilder {
|
|
16
|
+
build() {
|
|
17
|
+
return {
|
|
18
|
+
type: ApplicationCommandType.PrimaryEntryPoint as const,
|
|
19
|
+
name: 'sample-entry-point',
|
|
20
|
+
description: 'Launch the activity',
|
|
21
|
+
handler: EntryPointCommandHandlerType.AppHandler,
|
|
22
|
+
integration_types: [ApplicationIntegrationType.GuildInstall],
|
|
23
|
+
contexts: [InteractionContextType.Guild],
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AutocompleteInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Autocomplete } from 'meocord/decorator'
|
|
3
|
+
|
|
4
|
+
@Controller()
|
|
5
|
+
export class {{className}}AutocompleteController {
|
|
6
|
+
|
|
7
|
+
// The option this completes must be declared with `.setAutocomplete(true)` on the
|
|
8
|
+
// slash command builder, otherwise Discord never sends the interaction.
|
|
9
|
+
@Autocomplete('sample-slash', 'query')
|
|
10
|
+
async completeQuery(interaction: AutocompleteInteraction) {
|
|
11
|
+
const { value } = interaction.options.getFocused(true)
|
|
12
|
+
const choices = ['alpha', 'beta', 'gamma'].filter(choice => choice.startsWith(value))
|
|
13
|
+
|
|
14
|
+
await interaction.respond(choices.map(choice => ({ name: choice, value: choice })))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -10,7 +10,7 @@ export class {{className}}ButtonController {
|
|
|
10
10
|
await interaction.reply('Button clicked!')
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
@Command('button-with
|
|
13
|
+
@Command('button-with/{id}', CommandType.BUTTON)
|
|
14
14
|
async handleButtonWithId(interaction: ButtonInteraction, { id }) {
|
|
15
15
|
await interaction.reply(`Button with id: ${id} clicked!`)
|
|
16
16
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ChannelSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}ChannelSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('channel-select-menu', CommandType.CHANNEL_SELECT_MENU)
|
|
9
|
+
async handleChannelSelectMenu(interaction: ChannelSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected: ${interaction.channels.map(channel => channel.toString()).join(', ')}`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction } from 'discord.js'
|
|
2
|
-
import { Controller, Command
|
|
3
|
-
import { SampleCommandBuilder } from '
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { SampleCommandBuilder } from '{{builderImportPath}}'
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
6
6
|
export class {{className}}ContextMenuController {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MentionableSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}MentionableSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('mentionable-select-menu', CommandType.MENTIONABLE_SELECT_MENU)
|
|
9
|
+
async handleMentionableSelectMenu(interaction: MentionableSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected ${interaction.users.size} user(s) and ${interaction.roles.size} role(s).`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Controller, Command } from 'meocord/decorator'
|
|
|
3
3
|
import { CommandType } from 'meocord/enum'
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
6
|
-
export class {{className}}
|
|
6
|
+
export class {{className}}ModalSubmitController {
|
|
7
7
|
|
|
8
8
|
@Command('submit-modal', CommandType.MODAL_SUBMIT)
|
|
9
9
|
async handleModal(interaction: ModalSubmitInteraction) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PrimaryEntryPointCommandInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { SampleCommandBuilder } from '{{builderImportPath}}'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}PrimaryEntryPointController {
|
|
7
|
+
|
|
8
|
+
@Command('sample-entry-point', SampleCommandBuilder)
|
|
9
|
+
async handleEntryPoint(interaction: PrimaryEntryPointCommandInteraction) {
|
|
10
|
+
await interaction.reply('Launching the activity.')
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RoleSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}RoleSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('role-select-menu', CommandType.ROLE_SELECT_MENU)
|
|
9
|
+
async handleRoleSelectMenu(interaction: RoleSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected: ${interaction.roles.map(role => role.name).join(', ')}`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChatInputCommandInteraction } from 'discord.js'
|
|
2
2
|
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
-
import { SampleCommandBuilder } from '
|
|
3
|
+
import { SampleCommandBuilder } from '{{builderImportPath}}'
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
6
6
|
export class {{className}}SlashController {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UserSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}UserSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('user-select-menu', CommandType.USER_SELECT_MENU)
|
|
9
|
+
async handleUserSelectMenu(interaction: UserSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected: ${interaction.users.map(user => user.username).join(', ')}`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Argument } from 'commander';
|
|
2
|
+
import { ControllerType } from '../enum/controller.enum.js';
|
|
2
3
|
import { ControllerGeneratorHelper } from './helper/controller-generator.helper.js';
|
|
3
4
|
import { Logger } from '../common/logger.js';
|
|
4
5
|
import '../common/theme.js';
|
|
@@ -9,15 +10,9 @@ import wait from '../util/wait.util.js';
|
|
|
9
10
|
class GeneratorCLI {
|
|
10
11
|
register(program) {
|
|
11
12
|
const generatorCommand = program.command('generate').alias('g').description('Generate components');
|
|
12
|
-
generatorCommand.command('controller').alias('co').description('Generate a controller component').addArgument(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'message',
|
|
16
|
-
'modal-submit',
|
|
17
|
-
'reaction',
|
|
18
|
-
'select-menu',
|
|
19
|
-
'slash'
|
|
20
|
-
])).addArgument(new Argument('<name>', 'Name of the controller')).action(async (type, name)=>{
|
|
13
|
+
generatorCommand.command('controller').alias('co').description('Generate a controller component').addArgument(// Derived from the enum rather than listed by hand, so a new controller kind
|
|
14
|
+
// cannot be added to the framework and stay unreachable from the CLI.
|
|
15
|
+
new Argument('<type>', 'Type of the controller (e.g., button, context-menu, etc.)').choices(Object.values(ControllerType))).addArgument(new Argument('<name>', 'Name of the controller')).action(async (type, name)=>{
|
|
21
16
|
await this.handleGenerateComponent({
|
|
22
17
|
component: 'controller',
|
|
23
18
|
type: type,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs__default from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { populateTemplate } from '../../util/generator-cli.util.js';
|
|
5
|
+
|
|
6
|
+
const __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
/** Marks a packaged template file, and is dropped from the name that gets written. */ const TEMPLATE_SUFFIX = '.template';
|
|
8
|
+
/**
|
|
9
|
+
* Stands in for a leading dot while the file is packaged.
|
|
10
|
+
*
|
|
11
|
+
* npm omits a `.gitignore` from a published tarball, and this repository's own tooling
|
|
12
|
+
* would treat a packaged `.ts` as one of its sources, so nothing here is stored under
|
|
13
|
+
* the name it is written as.
|
|
14
|
+
*/ const DOT_PREFIX = '_';
|
|
15
|
+
/**
|
|
16
|
+
* The prefix each package manager needs for the framework to run on its runtime.
|
|
17
|
+
*
|
|
18
|
+
* The separating space belongs to the value, because the template writes
|
|
19
|
+
* `{{runtimePrefix}}meocord` with nothing between them — a package manager that needs
|
|
20
|
+
* no prefix has to render as `meocord …` rather than ` meocord …`. Trimming an entry
|
|
21
|
+
* here would join it to the command that follows.
|
|
22
|
+
*/ const RUNTIME_PREFIXES = {
|
|
23
|
+
// Without `--bun`, bun honours the CLI's `#!/usr/bin/env node` line and hands it to
|
|
24
|
+
// node, which an image built on bun alone does not have.
|
|
25
|
+
bun: 'bun --bun '
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The script prefix for a package manager.
|
|
29
|
+
*
|
|
30
|
+
* @param packageManager - Package manager the application was created with.
|
|
31
|
+
*/ function runtimePrefixFor(packageManager) {
|
|
32
|
+
return RUNTIME_PREFIXES[packageManager] ?? '';
|
|
33
|
+
}
|
|
34
|
+
/** The name a packaged template is written under. */ function outputName(templateName) {
|
|
35
|
+
const name = templateName.endsWith(TEMPLATE_SUFFIX) ? templateName.slice(0, -TEMPLATE_SUFFIX.length) : templateName;
|
|
36
|
+
return name.startsWith(DOT_PREFIX) ? `.${name.slice(DOT_PREFIX.length)}` : name;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Writes a new application from the template packaged with this framework.
|
|
40
|
+
*
|
|
41
|
+
* The template ships inside the package rather than being fetched, so the application a
|
|
42
|
+
* given release scaffolds is always one that release can run. A template resolved at
|
|
43
|
+
* generation time drifts from the CLI asking for it, in whichever direction happens to
|
|
44
|
+
* be newer.
|
|
45
|
+
*/ class AppGeneratorHelper {
|
|
46
|
+
/**
|
|
47
|
+
* Renders every packaged template file into the target directory.
|
|
48
|
+
*
|
|
49
|
+
* @param targetDir - Directory to write the application into.
|
|
50
|
+
* @param variables - Values substituted into the template.
|
|
51
|
+
* @returns The written paths, relative to the target directory.
|
|
52
|
+
*/ generateApp(targetDir, variables) {
|
|
53
|
+
return this.templateFiles(this.templateDir).map((templatePath)=>{
|
|
54
|
+
const relative = path.relative(this.templateDir, templatePath).split(path.sep).map((segment)=>outputName(segment)).join(path.sep);
|
|
55
|
+
const destination = path.join(targetDir, relative);
|
|
56
|
+
fs__default.mkdirSync(path.dirname(destination), {
|
|
57
|
+
recursive: true
|
|
58
|
+
});
|
|
59
|
+
fs__default.writeFileSync(destination, populateTemplate(templatePath, variables));
|
|
60
|
+
return relative;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
templateFiles(dir) {
|
|
64
|
+
return fs__default.readdirSync(dir, {
|
|
65
|
+
withFileTypes: true
|
|
66
|
+
}).flatMap((entry)=>{
|
|
67
|
+
const full = path.join(dir, entry.name);
|
|
68
|
+
return entry.isDirectory() ? this.templateFiles(full) : [
|
|
69
|
+
full
|
|
70
|
+
];
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
constructor(){
|
|
74
|
+
this.templateDir = path.resolve(__dirname$1, '..', 'app-template');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { AppGeneratorHelper, runtimePrefixFor };
|
|
@@ -14,7 +14,7 @@ class ControllerGeneratorHelper {
|
|
|
14
14
|
*/ generateController(args, type) {
|
|
15
15
|
const { parts, kebabCaseName, className } = validateAndFormatName(args.controllerName);
|
|
16
16
|
const controllerDir = path.join(process.cwd(), 'src', 'controllers', type, ...parts);
|
|
17
|
-
const template = this.buildControllerTemplate(className, type);
|
|
17
|
+
const template = this.buildControllerTemplate(className, type, parts);
|
|
18
18
|
this.generateControllerStructure(controllerDir, kebabCaseName, className, type, template);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -23,8 +23,8 @@ class ControllerGeneratorHelper {
|
|
|
23
23
|
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
24
24
|
* @returns The populated template string for the controller.
|
|
25
25
|
* @throws Will throw an error if the controller type is unsupported.
|
|
26
|
-
*/ buildControllerTemplate(className, type) {
|
|
27
|
-
const templateConfig = this.getTemplateConfig(type, className);
|
|
26
|
+
*/ buildControllerTemplate(className, type, parts = []) {
|
|
27
|
+
const templateConfig = this.getTemplateConfig(type, className, parts);
|
|
28
28
|
if (!templateConfig) {
|
|
29
29
|
throw new Error(`Unsupported controller type: ${type}`);
|
|
30
30
|
}
|
|
@@ -35,22 +35,38 @@ class ControllerGeneratorHelper {
|
|
|
35
35
|
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
36
36
|
* @param className - The name of the controller class.
|
|
37
37
|
* @returns An object containing the template path and variables, or `undefined` if not found.
|
|
38
|
-
*/ getTemplateConfig(type, className) {
|
|
38
|
+
*/ getTemplateConfig(type, className, parts = []) {
|
|
39
39
|
const baseDir = path.resolve(__dirname$1, '..', 'builder-template', 'controller');
|
|
40
40
|
const templates = {
|
|
41
41
|
[ControllerType.BUTTON]: 'button.controller.template',
|
|
42
42
|
[ControllerType.MODAL_SUBMIT]: 'modal-submit.controller.template',
|
|
43
43
|
[ControllerType.SELECT_MENU]: 'select-menu.controller.template',
|
|
44
|
+
[ControllerType.USER_SELECT_MENU]: 'user-select-menu.controller.template',
|
|
45
|
+
[ControllerType.ROLE_SELECT_MENU]: 'role-select-menu.controller.template',
|
|
46
|
+
[ControllerType.MENTIONABLE_SELECT_MENU]: 'mentionable-select-menu.controller.template',
|
|
47
|
+
[ControllerType.CHANNEL_SELECT_MENU]: 'channel-select-menu.controller.template',
|
|
44
48
|
[ControllerType.REACTION]: 'reaction.controller.template',
|
|
45
49
|
[ControllerType.MESSAGE]: 'message.controller.template',
|
|
46
50
|
[ControllerType.CONTEXT_MENU]: 'context-menu.controller.template',
|
|
47
|
-
[ControllerType.SLASH]: 'slash.controller.template'
|
|
51
|
+
[ControllerType.SLASH]: 'slash.controller.template',
|
|
52
|
+
[ControllerType.AUTOCOMPLETE]: 'autocomplete.controller.template',
|
|
53
|
+
[ControllerType.PRIMARY_ENTRY_POINT]: 'primary-entry-point.controller.template'
|
|
48
54
|
};
|
|
49
55
|
const template = templates[type] ? path.resolve(baseDir, templates[type]) : undefined;
|
|
56
|
+
// The builder is written beside the controller, so a nested controller name moves
|
|
57
|
+
// it too. Hard-coding `@src/controllers/<type>/builders/...` only ever pointed at
|
|
58
|
+
// the top-level one, which for a nested name does not exist.
|
|
59
|
+
const builderImportPath = [
|
|
60
|
+
'@src/controllers',
|
|
61
|
+
type,
|
|
62
|
+
...parts,
|
|
63
|
+
'builders/sample.builder'
|
|
64
|
+
].join('/');
|
|
50
65
|
return template ? {
|
|
51
66
|
template,
|
|
52
67
|
variables: {
|
|
53
|
-
className
|
|
68
|
+
className,
|
|
69
|
+
builderImportPath
|
|
54
70
|
}
|
|
55
71
|
} : undefined;
|
|
56
72
|
}
|
|
@@ -99,7 +115,8 @@ class ControllerGeneratorHelper {
|
|
|
99
115
|
const baseDir = path.resolve(__dirname$1, '..', 'builder-template', 'builder');
|
|
100
116
|
const templates = {
|
|
101
117
|
[ControllerType.CONTEXT_MENU]: 'context-menu.builder.template',
|
|
102
|
-
[ControllerType.SLASH]: 'slash.builder.template'
|
|
118
|
+
[ControllerType.SLASH]: 'slash.builder.template',
|
|
119
|
+
[ControllerType.PRIMARY_ENTRY_POINT]: 'primary-entry-point.builder.template'
|
|
103
120
|
};
|
|
104
121
|
const template = templates[type] ? path.resolve(baseDir, templates[type]) : undefined;
|
|
105
122
|
return template ? {
|