@sapphire/cli 1.5.1-next.5c0f4fc.0 → 1.6.0-next.52e7dec.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.
@@ -25,15 +25,34 @@ async function createComponent(component, name, config, configLoc) {
25
25
  else if (await fileExists(corePath)) {
26
26
  return CreateFileFromTemplate(`components/${template}`, target, config, params, false, true);
27
27
  }
28
- throw new Error("Can't find the template.");
28
+ throw new Error(`Couldn't find a template file for that component type.${parseCommonHints(component)}`);
29
29
  }
30
30
  async function fetchConfig() {
31
- const a = await Promise.race([findUp('.sapphirerc.json', { cwd: '.' }), sleep(5000)]);
32
- if (a) {
33
- return a;
31
+ const configFileAsJson = await Promise.race([findUp('.sapphirerc.json', { cwd: '.' }), sleep(5000)]);
32
+ if (configFileAsJson) {
33
+ return configFileAsJson;
34
34
  }
35
35
  return Promise.race([findUp('.sapphirerc.yml', { cwd: '.' }), sleep(5000)]);
36
36
  }
37
+ /**
38
+ * Parses common hints for the user
39
+ * @param component Component name
40
+ * @returns A string with a hint for the user
41
+ */
42
+ function parseCommonHints(component) {
43
+ const newLine = '\n';
44
+ const lowerCaseComponent = component.toLowerCase();
45
+ if (lowerCaseComponent === 'command' || lowerCaseComponent === 'commands') {
46
+ return `${newLine}Hint: You wrote "${component}", instead of "messagecommand", "slashcommand", or "contextmenucommand"`;
47
+ }
48
+ if (lowerCaseComponent === 'interaction-handler' ||
49
+ lowerCaseComponent === 'interaction-handlers' ||
50
+ lowerCaseComponent === 'interactionhandler' ||
51
+ lowerCaseComponent === 'interactionhandlers') {
52
+ return `${newLine}Hint: You wrote "${component}", instead of "buttoninteractionhandler", "autocompleteinteractionhandler", "modalinteractionhandler", or "selectmenuinteractionhandler"`;
53
+ }
54
+ return '';
55
+ }
37
56
  export default async (component, name) => {
38
57
  const spinner = new Spinner(`Creating a ${component.toLowerCase()}`).start();
39
58
  const fail = (error, additionalExecution) => {
@@ -20,7 +20,8 @@ export default async () => {
20
20
  arguments: response.arguments,
21
21
  commands: response.commands,
22
22
  listeners: response.listeners,
23
- preconditions: response.preconditions
23
+ preconditions: response.preconditions,
24
+ 'interaction-handlers': response['interaction-handlers']
24
25
  },
25
26
  customFileTemplates: {
26
27
  enabled: response.cftEnabled,
@@ -45,11 +45,15 @@ async function configureYarnRc(location, name, value) {
45
45
  return true;
46
46
  }
47
47
  async function installYarnV3(location, verbose) {
48
- const value = await execa('yarn', ['set', 'version', 'berry'], {
48
+ const valueSetVersion = await execa('yarn', ['set', 'version', 'berry'], {
49
49
  stdio: verbose ? 'inherit' : undefined,
50
50
  cwd: `./${location}/`
51
51
  });
52
- if (value.exitCode !== 0) {
52
+ const valueInstallPlugins = await execa('yarn', ['plugin', 'import', 'interactive-tools'], {
53
+ stdio: verbose ? 'inherit' : undefined,
54
+ cwd: `./${location}/`
55
+ });
56
+ if (valueSetVersion.exitCode !== 0 || valueInstallPlugins.exitCode !== 0) {
53
57
  throw new Error('An unknown error occurred while installing Yarn v3. Try running Sapphire CLI with "--verbose" flag.');
54
58
  }
55
59
  await Promise.all([
@@ -6,35 +6,45 @@ export async function CreateFileFromTemplate(template, target, config, params, c
6
6
  const location = custom ? template : `${templatesFolder}${template}`;
7
7
  const output = {};
8
8
  if (component) {
9
- const [c, f] = await getComponentTemplateWithConfig(location);
10
- output.c = c;
11
- output.f = f;
9
+ const [config, templateContent] = await getComponentTemplateWithConfig(location);
10
+ output.config = config;
11
+ output.templateContent = templateContent;
12
12
  }
13
- output.f ??= await readFile(location, 'utf8');
14
- if (!output.f) {
15
- throw new Error("Can't read file.");
13
+ output.templateContent ??= await readFile(location, 'utf8');
14
+ if (!output.templateContent) {
15
+ throw new Error("Couldn't read the template file. Are you sure it exists, the name is correct, and the content is valid?");
16
16
  }
17
17
  if (params) {
18
18
  for (const param of Object.entries(params)) {
19
- output.f = output.f.replaceAll(`{{${param[0]}}}`, param[1]);
19
+ output.templateContent = output.templateContent.replaceAll(`{{${param[0]}}}`, param[1]);
20
20
  }
21
21
  }
22
- if (!output || (component && (!output.c || !output.c.category))) {
23
- throw new Error('Invalid template.');
22
+ if (!output || (component && (!output.config || !output.config.category))) {
23
+ throw new Error('The template is invalid. Please create a valid template structure.');
24
24
  }
25
- const dir = component ? config.locations[output.c.category] : null;
26
- const ta = component ? target.replace('%L%', dir) : target;
27
- if (await fileExists(ta)) {
28
- throw new Error('Component already exists');
25
+ const directoryForOutput = component ? config?.locations[output.config.category] : null;
26
+ const targetPath = component ? target.replace('%L%', directoryForOutput) : target;
27
+ if (await fileExists(targetPath)) {
28
+ throw new Error('A component with the provided name already exists. Please provide a unique name.');
29
29
  }
30
- await writeFileRecursive(ta, output.f);
30
+ await writeFileRecursive(targetPath, output.templateContent);
31
31
  return true;
32
32
  }
33
+ /**
34
+ * Gets the template and the config from a component template
35
+ * @param path Path to the template
36
+ * @returns [config, template] The config and the template
37
+ */
33
38
  async function getComponentTemplateWithConfig(path) {
34
39
  const file = await readFile(path, 'utf8');
35
40
  const fa = file.split(/---(\r\n|\r|\n|)/gm);
36
41
  return [JSON.parse(fa[0]), fa[2]];
37
42
  }
43
+ /**
44
+ * Writes a file recursively
45
+ * @param target Target path
46
+ * @param data Data to write
47
+ */
38
48
  async function writeFileRecursive(target, data) {
39
49
  const resolvedTarget = resolve(target);
40
50
  const dir = dirname(resolvedTarget);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -47,6 +47,12 @@ export const PromptInit = [
47
47
  message: 'Where do you store your preconditions? (do not include the base)',
48
48
  initial: 'preconditions'
49
49
  },
50
+ {
51
+ type: 'text',
52
+ name: 'interaction-handlers',
53
+ message: 'Where do you store your interaction handlers? (do not include the base)',
54
+ initial: 'interaction-handlers'
55
+ },
50
56
  {
51
57
  type: 'confirm',
52
58
  name: 'cftEnabled',
@@ -63,7 +63,8 @@ export const PromptNew = (projectName, yarn, pnpm) => {
63
63
  {
64
64
  type: (prev) => (prev === 'Yarn' ? 'confirm' : false),
65
65
  name: 'yarnV3',
66
- message: 'Do you want to use Yarn v3?'
66
+ message: 'Do you want to use Yarn v3?',
67
+ initial: true
67
68
  },
68
69
  {
69
70
  type: 'confirm',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sapphire/cli",
3
- "version": "1.5.1-next.5c0f4fc.0",
3
+ "version": "1.6.0-next.52e7dec.0",
4
4
  "description": "CLI for Sapphire Framework",
5
5
  "author": "@sapphire",
6
6
  "license": "MIT",
@@ -48,7 +48,10 @@
48
48
  "@commitlint/config-conventional": "^17.4.4",
49
49
  "@favware/cliff-jumper": "^2.0.0",
50
50
  "@favware/npm-deprecate": "^1.0.7",
51
+ "@sapphire/decorators": "*",
51
52
  "@sapphire/eslint-config": "^4.4.0",
53
+ "@sapphire/framework": "*",
54
+ "@sapphire/plugin-api": "*",
52
55
  "@sapphire/prettier-config": "^1.4.5",
53
56
  "@sapphire/ts-config": "^4.0.0",
54
57
  "@types/js-yaml": "^4.0.5",
@@ -57,10 +60,11 @@
57
60
  "@typescript-eslint/eslint-plugin": "^5.58.0",
58
61
  "@typescript-eslint/parser": "^5.58.0",
59
62
  "cz-conventional-changelog": "^3.3.0",
63
+ "discord.js": "*",
60
64
  "eslint": "^8.38.0",
61
65
  "eslint-config-prettier": "^8.8.0",
62
66
  "eslint-plugin-prettier": "^4.2.1",
63
- "globby": "^13.1.3",
67
+ "globby": "^13.1.4",
64
68
  "husky": "^8.0.3",
65
69
  "lint-staged": "^13.2.1",
66
70
  "pinst": "^3.0.0",
@@ -5,8 +5,9 @@
5
5
  "arguments": "arguments",
6
6
  "commands": "commands",
7
7
  "listeners": "listeners",
8
- "preconditions": "preconditions"
9
- "routes": "routes",
8
+ "preconditions": "preconditions",
9
+ "interaction-handlers": "interaction-handlers",
10
+ "routes": "routes"
10
11
  },
11
12
  "customFileTemplates": {
12
13
  "enabled": false,
@@ -5,6 +5,7 @@ locations:
5
5
  commands: commands
6
6
  listeners: listeners
7
7
  preconditions: preconditions
8
+ interaction-handlers: interaction-handlers
8
9
  routes: routes
9
10
  customFileTemplates:
10
11
  enabled: false
@@ -1,11 +1,11 @@
1
1
  { "category": "arguments" }
2
2
  ---
3
3
  import { ApplyOptions } from '@sapphire/decorators';
4
- import { Argument, ArgumentOptions } from '@sapphire/framework';
4
+ import { Argument } from '@sapphire/framework';
5
5
 
6
- @ApplyOptions<ArgumentOptions>({})
6
+ @ApplyOptions<Argument.Options>({})
7
7
  export class UserArgument extends Argument<string> {
8
- async run(parameter: string) {
8
+ public override run(parameter: string) {
9
9
  return this.ok(parameter);
10
10
  }
11
11
  }
@@ -0,0 +1,50 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4
+ const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
5
+
6
+ class AutocompleteHandler extends InteractionHandler {
7
+ /**
8
+ * @param {InteractionHandler.Context} context
9
+ * @param {InteractionHandler.Options} options
10
+ */
11
+ constructor(context, options) {
12
+ super(context, {
13
+ ...options,
14
+ interactionHandlerType: InteractionHandlerTypes.Autocomplete
15
+ });
16
+ }
17
+
18
+ /**
19
+ * @param {import('discord.js').AutocompleteInteraction} interaction
20
+ * @param {import('discord.js').ApplicationCommandOptionChoiceData[]} result
21
+ */
22
+ async run(interaction, result) {
23
+ return interaction.respond(result);
24
+ }
25
+
26
+ /**
27
+ * @param {import('discord.js').AutocompleteInteraction} interaction
28
+ */
29
+ async parse(interaction) {
30
+ // Only run this interaction for the command with ID '1000000000000000000'
31
+ if (interaction.commandId !== '1000000000000000000') return this.none();
32
+ // Get the focussed (current) option
33
+ const focusedOption = interaction.options.getFocused(true);
34
+ // Ensure that the option name is one that can be autocompleted, or return none if not.
35
+ switch (focusedOption.name) {
36
+ case 'search': {
37
+ // Search your API or similar. This is example code!
38
+ const searchResult = await myApi.searchForSomething(focusedOption.value);
39
+ // Map the search results to the structure required for Autocomplete
40
+ return this.some(searchResult.map((match) => ({ name: match.name, value: match.key })));
41
+ }
42
+ default:
43
+ return this.none();
44
+ }
45
+ }
46
+ }
47
+
48
+ module.exports = {
49
+ AutocompleteHandler
50
+ };
@@ -0,0 +1,32 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ import { ApplyOptions } from '@sapphire/decorators';
4
+ import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5
+ import { AutocompleteInteraction, type ApplicationCommandOptionChoiceData } from 'discord.js';
6
+
7
+ @ApplyOptions<InteractionHandler.Options>({
8
+ interactionHandlerType: InteractionHandlerTypes.Autocomplete
9
+ })
10
+ export class AutocompleteHandler extends InteractionHandler {
11
+ public override async run(interaction: AutocompleteInteraction, result: ApplicationCommandOptionChoiceData[]) {
12
+ return interaction.respond(result);
13
+ }
14
+
15
+ public override async parse(interaction: AutocompleteInteraction) {
16
+ // Only run this interaction for the command with ID '1000000000000000000'
17
+ if (interaction.commandId !== '1000000000000000000') return this.none();
18
+ // Get the focussed (current) option
19
+ const focusedOption = interaction.options.getFocused(true);
20
+ // Ensure that the option name is one that can be autocompleted, or return none if not.
21
+ switch (focusedOption.name) {
22
+ case 'search': {
23
+ // Search your API or similar. This is example code!
24
+ const searchResult = await myApi.searchForSomething(focusedOption.value);
25
+ // Map the search results to the structure required for Autocomplete
26
+ return this.some(searchResult.map((match) => ({ name: match.name, value: match.key })));
27
+ }
28
+ default:
29
+ return this.none();
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,39 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4
+
5
+ class ButtonHandler extends InteractionHandler {
6
+ /**
7
+ * @param {InteractionHandler.Context} context
8
+ * @param {InteractionHandler.Options} options
9
+ */
10
+ constructor(context, options) {
11
+ super(context, {
12
+ ...options,
13
+ interactionHandlerType: InteractionHandlerTypes.Button
14
+ });
15
+ }
16
+
17
+ /**
18
+ * @param {import('discord.js').ButtonInteraction} interaction
19
+ */
20
+ async run(interaction) {
21
+ await interaction.reply({
22
+ content: 'Hello from a button interaction handler!',
23
+ // Let's make it so only the person who pressed the button can see this message!
24
+ ephemeral: true
25
+ });
26
+ }
27
+
28
+ /**
29
+ * @param {import('discord.js').ButtonInteraction} interaction
30
+ */
31
+ parse(interaction) {
32
+ if (interaction.customId !== 'my-awesome-button') return this.none();
33
+ return this.some();
34
+ }
35
+ }
36
+
37
+ module.exports = {
38
+ ButtonHandler
39
+ };
@@ -0,0 +1,24 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ import { ApplyOptions } from '@sapphire/decorators';
4
+ import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5
+ import type { ButtonInteraction } from 'discord.js';
6
+
7
+ @ApplyOptions<InteractionHandler.Options>({
8
+ interactionHandlerType: InteractionHandlerTypes.Button
9
+ })
10
+ export class ButtonHandler extends InteractionHandler {
11
+ public async run(interaction: ButtonInteraction) {
12
+ await interaction.reply({
13
+ content: 'Hello from a button interaction handler!',
14
+ // Let's make it so only the person who pressed the button can see this message!
15
+ ephemeral: true
16
+ });
17
+ }
18
+
19
+ public override parse(interaction: ButtonInteraction) {
20
+ if (interaction.customId !== 'my-awesome-button') return this.none();
21
+
22
+ return this.some();
23
+ }
24
+ }
@@ -16,7 +16,7 @@ export class UserCommand extends Command {
16
16
  );
17
17
  }
18
18
 
19
- public async contextMenuRun(interaction: Command.ContextMenuCommandInteraction) {
19
+ public override async contextMenuRun(interaction: Command.ContextMenuCommandInteraction) {
20
20
  return interaction.reply({ content: 'Hello world!' });
21
21
  }
22
22
  }
@@ -1,9 +1,9 @@
1
1
  { "category": "listeners" }
2
2
  ---
3
3
  import { ApplyOptions } from '@sapphire/decorators';
4
- import { Listener, ListenerOptions } from '@sapphire/framework';
4
+ import { Listener } from '@sapphire/framework';
5
5
 
6
- @ApplyOptions<ListenerOptions>({})
6
+ @ApplyOptions<Listener.Options>({})
7
7
  export class UserEvent extends Listener {
8
- public run() {}
8
+ public override run() {}
9
9
  }
@@ -8,7 +8,7 @@ import type { Message } from 'discord.js';
8
8
  description: 'A basic command'
9
9
  })
10
10
  export class UserCommand extends Command {
11
- public async messageRun(message: Message) {
11
+ public override async messageRun(message: Message) {
12
12
  return message.channel.send('Hello world!');
13
13
  }
14
14
  }
@@ -0,0 +1,39 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4
+
5
+ class ModalHandler extends InteractionHandler {
6
+ /**
7
+ * @param {InteractionHandler.Context} context
8
+ * @param {InteractionHandler.Options} options
9
+ */
10
+ constructor(context, options) {
11
+ super(context, {
12
+ ...options,
13
+ interactionHandlerType: InteractionHandlerTypes.ModalSubmit
14
+ });
15
+ }
16
+
17
+ /**
18
+ * @param {import('discord.js').ModalSubmitInteraction} interaction
19
+ */
20
+ async run(interaction) {
21
+ await interaction.reply({
22
+ content: 'Thank you for submitting the form!',
23
+ ephemeral: true
24
+ });
25
+ }
26
+
27
+ /**
28
+ * @param {import('discord.js').ModalSubmitInteraction} interaction
29
+ */
30
+ parse(interaction) {
31
+ if (interaction.customId !== 'hello-popup') return this.none();
32
+
33
+ return this.some();
34
+ }
35
+ }
36
+
37
+ module.exports = {
38
+ ModalHandler
39
+ };
@@ -0,0 +1,23 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ import { ApplyOptions } from '@sapphire/decorators';
4
+ import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5
+ import type { ModalSubmitInteraction } from 'discord.js';
6
+
7
+ @ApplyOptions<InteractionHandler.Options>({
8
+ interactionHandlerType: InteractionHandlerTypes.ModalSubmit
9
+ })
10
+ export class ModalHandler extends InteractionHandler {
11
+ public async run(interaction: ModalSubmitInteraction) {
12
+ await interaction.reply({
13
+ content: 'Thank you for submitting the form!',
14
+ ephemeral: true
15
+ });
16
+ }
17
+
18
+ public override parse(interaction: ModalSubmitInteraction) {
19
+ if (interaction.customId !== 'hello-popup') return this.none();
20
+
21
+ return this.some();
22
+ }
23
+ }
@@ -1,6 +1,7 @@
1
1
  { "category": "routes" }
2
2
  ---
3
3
  const { methods, Route } = require('@sapphire/plugin-api');
4
+
4
5
  class UserRoute extends Route {
5
6
  /**
6
7
  * @param {Route.Context} context
@@ -0,0 +1,39 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4
+
5
+ class MenuHandler extends InteractionHandler {
6
+ /**
7
+ * @param {InteractionHandler.Context} context
8
+ * @param {InteractionHandler.Options} options
9
+ */
10
+ constructor(context, options) {
11
+ super(context, {
12
+ ...options,
13
+ interactionHandlerType: InteractionHandlerTypes.SelectMenu
14
+ });
15
+ }
16
+
17
+ /**
18
+ * @param {import('discord.js').StringSelectMenuInteraction} interaction
19
+ */
20
+ async run(interaction) {
21
+ await interaction.reply({
22
+ // Remember how we can have multiple values? Let's get the first one!
23
+ content: `You selected: ${interaction.values[0]}`
24
+ });
25
+ }
26
+
27
+ /**
28
+ * @param {import('discord.js').StringSelectMenuInteraction} interaction
29
+ */
30
+ parse(interaction) {
31
+ if (interaction.customId !== 'my-echo-select') return this.none();
32
+
33
+ return this.some();
34
+ }
35
+ }
36
+
37
+ module.exports = {
38
+ MenuHandler
39
+ };
@@ -0,0 +1,23 @@
1
+ { "category": "interaction-handlers" }
2
+ ---
3
+ import { ApplyOptions } from '@sapphire/decorators';
4
+ import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5
+ import type { StringSelectMenuInteraction } from 'discord.js';
6
+
7
+ @ApplyOptions<InteractionHandler.Options>({
8
+ interactionHandlerType: InteractionHandlerTypes.SelectMenu
9
+ })
10
+ export class MenuHandler extends InteractionHandler {
11
+ public override async run(interaction: StringSelectMenuInteraction) {
12
+ await interaction.reply({
13
+ // Remember how we can have multiple values? Let's get the first one!
14
+ content: `You selected: ${interaction.values[0]}`
15
+ });
16
+ }
17
+
18
+ public override parse(interaction: StringSelectMenuInteraction) {
19
+ if (interaction.customId !== 'my-echo-select') return this.none();
20
+
21
+ return this.some();
22
+ }
23
+ }
@@ -27,11 +27,14 @@
27
27
  "preconditions": {
28
28
  "type": "string"
29
29
  },
30
+ "interaction-handlers": {
31
+ "type": "string"
32
+ },
30
33
  "routes": {
31
34
  "type": "string"
32
35
  }
33
36
  },
34
- "required": ["base", "arguments", "commands", "listeners", "preconditions"]
37
+ "required": ["base", "arguments", "commands", "listeners", "preconditions", "interaction-handlers"]
35
38
  },
36
39
  "customFileTemplates": {
37
40
  "description": "Settings about custom component (piece) templates",