#how do i convert my js disc bot to ts

59 messages · Page 1 of 1 (latest)

mighty valve
#

how do i convert my js disc bot to ts

cobalt oracle
#

@mighty valve Depends on your existing process - but if you add a tsconfig with allowJs you can mix JS and TS files while migrating to TS.

#

I recommend strict: true, allowJs: true, checkJs: false for migration config.

mighty valve
# cobalt oracle <@920487197046607872> Depends on your existing process - but if you add a `tscon...

heres mines

{
"compilerOptions": {
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. /
"module": "commonjs", /
Specify what module code is generated. /
"outDir": "./dist", /
Specify an output folder for all emitted files. /
"esModuleInterop": true, /
Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. /
"forceConsistentCasingInFileNames": true, /
Ensure that casing is correct in imports. /
"strict": true, /
Enable all strict type-checking options. /
"skipLibCheck": true, /
Skip type checking all .d.ts files. */
"allowJs": true
}
}

#

so do i rename my .js files to .ts?

cobalt oracle
#

You may want to make sure checkJs: false is on; maybe that's the default but otherwise TS will start complaining about existing JS files.

#

And yeah, then you change a file to .ts -> fix the errors that the compiler raises, mostly by adding type annotations -> repeat.

#

It helps to start from the "leaves", the files that don't depend on other files.

mighty valve
#

is there a faster way to convert the code to ts?

cobalt oracle
#

There are various automated approaches that end up with IMO kinda mediocre results

mighty valve
#

ok then ill do it myself

mighty valve
# cobalt oracle There are various automated approaches that end up with IMO kinda mediocre resul...
const path = require('path');
const getAllFiles = require('../utils/getAllFiles');

module.exports = (client: { on: (arg0: any, arg1: (arg: any) => Promise<void>) => void; }) => {
  const eventFolders = getAllFiles(path.join(__dirname, '..', 'events'), true);

  for (const eventFolder of eventFolders) {
    let eventFiles = getAllFiles(eventFolder);
    eventFiles.sort();

    const eventName = eventFolder.replace(/\\/g, '/').split('/').pop();

    client.on(eventName, async (arg) => {
      for (const eventFile of eventFiles) {
        const eventFunction = require(eventFile);
        await eventFunction(client, arg);
      }
    });
  }
};

js code converted to ts

#

is this what i should be doing?

cobalt oracle
#

You should switch require code to import

#

"module": "commonJs" in your tsconfig means it'll compile back to require code, but TS won't understand the types of things you require; they're just any.

mighty valve
cobalt oracle
#

I think it's just import path from 'path' or import { join } from 'path'.

mighty valve
#

ok

cobalt oracle
#
import path from "path";
import getAllFiles from "../utils/getAllFiles"; 

export default (client: { on: (arg0: any, arg1: (arg: any) => Promise<void>) => void; }) => {
#

Though honestly, what I'd actually do here is:

import fooEvent from "../events/FooEvent";
import barEvent from "../events/BarEvent";

const events = [fooEvent, barEvent];
#

!*:discordjs-dynamic-imports

uneven roverBOT
#
retsam19#0
`!retsam19:discordjs-dynamic-imports`:

The discord.js docs recommend using fs to dynamically load and register resources (e.g. commands) within a specific folder - but this is more complex than necessary and has some footguns associated with it.

A very common issue is whether the file will have a .ts or .js extension, depending on whether the code is compiled to JS or run directly (e.g. with ts-node or tsx).

And TS does not know the contents of dynamically imported files, so you have to cast the imports (or use any) and you can have errors where the code in the file doesn't match the type you cast it to.

Instead, I recommend a static list of imports:

import FooCommand from "./commands/foo.js";
import BooCommand from "./commands/bar.js";

export const commands = [FooCommand, BarCommand];

Yes, it's 'boilerplate' but it's very simple, usually much less code than dynamic fs stuff, and TS can properly type-check it.


Or for an alternative structure for organizing your bot, you can check out the TS Community Discord bot: https://github.com/typescript-community/community-bot which uses a 'modules' organization.

mighty valve
# cobalt oracle Though honestly, what I'd actually do here is: ```ts import fooEvent from "../ev...

getallfiles.ts

import fs from "fs";
import path from "path";

export default (directory: any, foldersOnly = false) => {
  let fileNames = [];

  const files = fs.readdirSync(directory, { withFileTypes: true });

  for (const file of files) {
    const filePath = path.join(directory, file.name);

    if (foldersOnly) {
      if (file.isDirectory()) {
        fileNames.push(filePath);
      }
    } else {
      if (file.isFile()) {
        fileNames.push(filePath);
      }
    }
  }

  return fileNames;
};
#

did i convert it right?

cobalt oracle
#

@mighty valve Looks reasonable, though you want to try to minimize any usage. directory: any probably can just be directory: string.

#

Actually, I gues it has to be a string. fs.readdirSync technically accepts non-strings as its first argument (e.g. buffers and urls) but path.join only accepts strings

mighty valve
#

gives me TypeError: getAllFiles is not a function when i run the bot

#

what happened here

cobalt oracle
#

You probably do have to find the places where you're doing require("./getAllFiles") and either change it to require("./getAllFiles").default or else change it to an import downstream.

#

I though esModuleInterop might handle that, but maybe not. It's been awhile since I did a CJS to ESM code migration.

mighty valve
#

found out its happening from getLocalCommands.js

mighty valve
#

Argument of type 'any' is not assignable to parameter of type 'never'.

#
import path from "path";
import getAllFiles from "./getAllFiles";

export default (exceptions = []) => {
  let localCommands = [];

  const commandCategories = getAllFiles(
    path.join(__dirname, '..', 'commands'),
    true
  );

  for (const commandCategory of commandCategories) {
    const commandFiles = getAllFiles(commandCategory);

    for (const commandFile of commandFiles) {
      const commandObject = require(commandFile);

      if (exceptions.includes(commandObject.name)) {
        continue;
      }

      localCommands.push(commandObject);
    }
  }
cobalt oracle
#

You have to tell TS what type exceptions is.

#
(exceptions: string[] = []) => {
   //...
}
#

Generally you have to tell TS the type of empty arrays, but TS can infer based on push usage for locally declared variables:

let x = [];
x.push("foo");
x // inferred as string[] due to the push type

... but this doesn't work for function params.

mighty valve
#

Argument of type 'string | any[]' is not assignable to parameter of type 'any[]'.
Type 'string' is not assignable to type 'any[]'.

#

arecommandsdifferent.ts

#
export default (existingCommand: { description: any; options: string | any[]; }, localCommand: { description: any; options: string | any[]; }) => {
  const areChoicesDifferent = (existingChoices: any[], localChoices: any) => {
    for (const localChoice of localChoices) {
      const existingChoice = existingChoices?.find(
        (choice: { name: any; }) => choice.name === localChoice.name
      );

      if (!existingChoice) {
        return true;
      }

      if (localChoice.value !== existingChoice.value) {
        return true;
      }
    }
    return false;
  };

  const areOptionsDifferent = (existingOptions: any[], localOptions: any) => {
    for (const localOption of localOptions) {
      const existingOption = existingOptions?.find(
        (option: { name: any; }) => option.name === localOption.name
      );

      if (!existingOption) {
        return true;
      }

      if (
        localOption.description !== existingOption.description ||
        localOption.type !== existingOption.type ||
        (localOption.required || false) !== existingOption.required ||
        (localOption.choices?.length || 0) !==
          (existingOption.choices?.length || 0) ||
        areChoicesDifferent(
          localOption.choices || [],
          existingOption.choices || []
        )
      ) {
        return true;
      }
    }
    return false;
  };

  if (
    existingCommand.description !== localCommand.description ||
    existingCommand.options?.length !== (localCommand.options?.length || 0) ||
    areOptionsDifferent(existingCommand.options, localCommand.options || [])
  ) {
    return true;
  }

  return false;
};
cobalt oracle
#

Yeah, areOptionsDifferent says it only accepts arrays, you're potentially passing it a string.

#

Either you need to update areOptionsDifferent to accept strings, handle the case where existingCommand.options is a string, or change the types of existingCommand.options to not include string.

mighty valve
# cobalt oracle Either you need to update `areOptionsDifferent` to accept strings, handle the ca...

and also this
Expected 2 arguments, but got 1.

import areCommandsDifferent from "../../utils/areCommandsDifferent";
import getApplicationCommands from "../../utils/getApplicationCommands";
import getLocalCommands from "../../utils/getLocalCommands";

export default async (client: any) => {
  try {
    const localCommands = getLocalCommands();
    const applicationCommands = await getApplicationCommands(
      client,
      // testServer
    );
#

from const applicationCommands = await getApplicationCommands(

#

should i do await getApplicationCommands(
client, undefined)

#

because i dont need testserver

cobalt oracle
#

You can use ? in the definition of the function if the argumenet is supposed to be optional

#
function foo(x: string, y?: string) {}
mighty valve
#

and also

#

Cannot find module '../../../config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension.

#

import { devs, testServer } from "../../../config.json"

#

wtf happened

cobalt oracle
#

Consider using '--resolveJsonModule' to import module with '.json' extension

mighty valve
#

fixed

#

"resolveJsonModule": true

mighty valve