Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding custom commands with typescript definitions leads to error #1065

Closed
brian-mann opened this issue Dec 13, 2017 · 45 comments
Closed

Adding custom commands with typescript definitions leads to error #1065

brian-mann opened this issue Dec 13, 2017 · 45 comments

Comments

@brian-mann
Copy link
Member

Cypress.Commands.add('myCustomCommand', '...')

Using TypeScript now results in...

Property ‘myCustomCommand’ does not exist on type ‘Chainable’)

How do we fix this @NicholasBoll @bahmutov

@bahmutov
Copy link
Contributor

bahmutov commented Dec 13, 2017

Good concrete example where we could use it today is https://github.com/cypress-io/snapshot

User adds custom command (in cypress/support/commands.js

require('@cypress/snapshot')()

and now has commanf

cy.wrap(42).snapshot()
// or
cy.wrap(42).snapshot({name: 'meaning of life'})

It would be nice to add this command to the cy type after https://github.com/cypress-io/snapshot/blob/master/src/index.js#L155

Cypress.Commands.add('snapshot', { prevSubject: true }, snapshot)

@shcallaway
Copy link

One way to do this would be to export the Cypress namespace and allow TypeScript users to extend the Chainable interface.

@NicholasBoll
Copy link
Contributor

NicholasBoll commented Dec 14, 2017

@shcallaway is exactly right. Chainable interfaces have to be extended:

declare namespace Cypress {
  interface Chainable<Subject> {
    myCustomCommand(value: string): Chainable<Subject>
  }
}

Cypress.Commands.add('myCustomCommand', { prevSubject: true}, snapshot)

Later:

cy.get('foo').myCustomCommand('bar')

@brian-mann
Copy link
Member Author

So we don't have to make any changes on our end right? We just need to update the Typescript recipes to show this... @bahmutov ?

@NicholasBoll
Copy link
Contributor

I'll just note there are some subtleties to TypeScript modules. TypeScript defines global declarations as a namespace and anything with an require/import/export as a module. A module has it's own local scope. A namespace participates in a global scope. Since Cypress has no exports, it is a namespace.

You can actually mix them for UMD modules.

Example:

// module
export default function foo() { return 'foo' }

// in another file:
import foo from './foo'
// global namespace
namespace Foo {
  interface foo {
    (): string
}

declare const foo: Foo.foo

// in another file
foo()
// UMD
// global namespace
namespace Foo {
  interface foo {
    (): string
}

declare const foo: Foo.foo

export default foo
export as namespace Foo

// in another file
foo()

// also works
import foo from './foo'
foo()

Chai exports a namespace because their API is chainable and you'll need access to that namespace in order to extend the Assertion interface. But they also export a const called chai: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/chai/index.d.ts#L1616

More info about TypeScript modules can be found here: https://www.typescriptlang.org/docs/handbook/modules.html

@NicholasBoll
Copy link
Contributor

NicholasBoll commented Dec 14, 2017

I was looking at the examples preprocessors__typescript-webpack and preprocessors__typescript-browserify. TypeScript will not allow you to extend a namespace if the file is a module (has import/export/require). To get around this in the example, the command.ts file(s) cannot import/export anything. They simple contain the following code:

commands.ts:

declare namespace Cypress {
  interface Chainable<Subject> {
    myCustomCommand: typeof myCustomCommand // more DRY than the following:
    // myCustomCommand(value: string): Cypress.Chainable<JQuery>
  }
}

function myCustomCommand(value: string): Cypress.Chainable<JQuery> {
  return cy.get('foo')
}

Cypress.Commands.add('myCustomCommand', myCustomCommand)

Some other file:

cy.myCustomCommand('foo')

And the index.ts simply imports it: import './command'. I'll also note the tsconfig.json needs to include all files in support for this to work (it looks like it does).

@shcallaway
Copy link

shcallaway commented Dec 14, 2017

What about commands that are supposed to return a Chainable? For example:

function login(email: string, password: string): Cypress.Chainable<Chainable> {
  cy.request(options);
});

Cypress.Commands.add("login", login);

TypeScript won't allow us to not return a value in command, and yet this is how most commands work.

@brian-mann
Copy link
Member Author

Custom commands like this should always return a value. You should return cy.request(...) here.

@shcallaway
Copy link

What about this example, then?

@brian-mann
Copy link
Member Author

It would technically work if all you're doing is enqueing more Cypress commands, but it would not work in other situations like if you return a regular Promise. So best practice would be to always return something.

We'll update the examples. It's not going to hurt anything by always returning from Custom Commands.

@shcallaway
Copy link

So in general I should return the last cy command in a custom command? Cypress will correctly enqueue all the commands that come before it?

@brian-mann
Copy link
Member Author

brian-mann commented Dec 14, 2017

Right so this is where it gets weird which is why we've opted not to return some things from the examples. It looks good when everything is chained together but then looks really weird if they are not...

All that's happening when you do cy.whatever is that its literally enqueuing a command to run. IT doesn't actually do anything at that moment. No work is kicked off.

So the return value is only applicable do the current custom command. What I mean is, the login command resolves whenever the function finishes - or its awaited if a Promise is returned. Since cy commands return synchronous chainer objects then the command would finish immediately.

The work of the other commands has just been enqueued, not run yet.

What happens is that it just looks weird...

Cypress.Commands.add('foo', () => {
  cy.get(...)
  cy.get(...)
  cy.visit(...)

  // super weird
  return cy.request(...)
})

But returning a synchronous chainer isn't actually doing anything. So you could instead just return null to indicate you're done your promise chain to make Typescript happy.

Cypress.Commands.add('foo', () => {
  cy.get(...)
  cy.get(...)
  cy.visit(...)
  cy.request(...)

  return null // we are done but still weird
})
Cypress.Commands.add('foo', () => {
  cy.get(...)
  cy.get(...)
  cy.visit(...)
  cy.request(...)
  // this is why we don't return anything in the examples
  // because it looks most familiar to the way you write your normal test code
})

@shcallaway
Copy link

shcallaway commented Dec 14, 2017

Given that the actual return value of the custom command has no purpose (outside of the custom command itself), why would I declare the return type to begin with? I could just do this:

function login(email: string, password: string): void {
  cy.request(options);
});

Cypress.Commands.add('login', login);

@brian-mann
Copy link
Member Author

Yes, I suppose that is all you need. That should work fine. Unless the custom command returns things other than more cypress commands, it can return void.

@shcallaway
Copy link

For future readers, this is the TypeScript concept that makes it possible to re-declare the Cypress namespace: declaration merging.

Also, @NicholasBoll , regarding your example, what is the purpose of adding the generic?

@bahmutov
Copy link
Contributor

I tried it out with Cypress 1.1.4 type definitions and this approach of merging Cypress interface works. https://github.com/cypress-io/snapshot/pull/11/files The new method becomes available on the interface, making TS and VSCode happy

@shcallaway
Copy link

shcallaway commented Dec 14, 2017

Hmm I'm still getting errors (e.g. Property 'goToPage' does not exist on type 'Chainable'.). I don't think declaration merging is working — possibly because of these two lines in cypress/index.d.ts?

// global variables added by Cypress when it runs
declare const cy: Cypress.Chainable;
declare const Cypress: Cypress.Cypress;

Here is my cypress/support/commands.ts:

declare namespace Cypress {
  interface Chainable {
    goToPage: typeof goToPage;
    login: typeof login;
    getGridHeader: typeof getGridHeader;
    clearSession: typeof clearSession;
    logout: typeof logout;
    // ...
  }
}

Cypress.Commands.add("goToPage", goToPage);
Cypress.Commands.add("login", login);
Cypress.Commands.add("getGridHeader", getGridHeader);
Cypress.Commands.add("clearSession", clearSession);
Cypress.Commands.add("logout", logout);
// ...

function goToPage(path: string): void {
// ...

Here is my cypress/tsconfig.cypress.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs"
  },
  "include": [
    "./*/*.ts",
    // explicitly include cypress types from node module
    "../node_modules/cypress"
  ]
}

And I don't think it matters, but here is my cypress/plugins/index.js:

const webpack = require("@cypress/webpack-preprocessor");
module.exports = (on, config) => {
  const webpackOptions = {
    resolve: {
      extensions: [".ts", ".js"]
    },
    module: {
      rules: [
        {
          test: /\.ts$/,
          loader: "ts-loader",
          options: {
            configFile: "tsconfig.cypress.json"
          }
        }
      ]
    }
  };

  on("file:preprocessor", webpack({
    webpackOptions: webpackOptions
  }));
};

@NicholasBoll
Copy link
Contributor

Also, @NicholasBoll , regarding your example, what is the purpose of adding the generic?

@shcallaway I added the Generic (should be available on Cypress 1.2.0, but not on Cypress 1.1.4) to allow they subject to be passed around so that the .then type would be known. For backwards compatibility, the Subject is defaulted to any. For example:

cy.wrap({ foo: 'bar' })
  .then(subject => {
    subject; // $ExpectType { foo: string }
  })
  .its('foo') // keyof type checked - 'foo1' would be a type error
  .then(subject => {
    subject; // $ExpectType string
  })

Without the generic, the subject would always be any

@NicholasBoll
Copy link
Contributor

@shcallaway I knew that TypeScript merged declarations, but I didn't know they documented it. Thanks!

@shcallaway
Copy link

shcallaway commented Dec 14, 2017

@bahmutov I don't think your PR on snapshot fully demonstrates how to appropriately use custom commands with TypeScript. Specifically, it does not address:

  1. The extremely common use case of using cy in commands.ts. The TS compiler insists that myCustomCommand does not exist on type Chainable, even after re-declaring the namespace and interface.

image

I believe this is due to the declare const cy: Cypress.Chainable in index.d.ts.

  1. The fact that, as we determined earlier, most custom commands don't return anything. And yet users will naturally expect to be able to do something like this:

image

For this behavior to work, all custom commands should return something like Chainable<void>. (It sounds like this is being fixed in the upcoming 1.2.0 release.)

@NicholasBoll
Copy link
Contributor

A suggestion and I don't know how the Cypress team feels about it, but we don't use Cypress.Commands.add for actions we want to complete that don't return anything (void). It is a bit odd to type and if we don't return chainable methods it might not make sense. We just use function calls:

export function login(
  name: string,
  password?: string,
): void {
  cy.log(`Logging in as ${name}`);

  cy.request({
    method: 'POST',
    url: '/api/webconsole/login',
    body: {
      username: name,
      password: 'logrhythm!1',
    },
  }).then(response => {
    console.log(response);
    return response;
  });
}

In some other file:

import { login } from '../helpers/login'

describe('Login', () => {
  it('should allow the user to enter credentials', () => {
    login('username', 'password');

    cy.title().should('contain', 'Dashboard');
  });
});

@NicholasBoll
Copy link
Contributor

NicholasBoll commented Dec 15, 2017

@shcallaway, I just checkout out the branch that @bahmutov provided: https://github.com/cypress-io/snapshot/pull/11/files and changed a few things to verify:

// commands.ts
// register .snapshot() command
require('../..').register()

// merge Cypress interface with new command "snapshot()"
declare namespace Cypress {
  interface Chainable {
    snapshot(): void
  }
}

declare namespace Cypress {
  interface Chainable {
    getBody: typeof getBody
  }
}

function getBody(): Cypress.Chainable {
  return cy.get('body')
}

Cypress.Commands.add('getBody', getBody)
// spec-typescript.ts
describe('@cypress/snapshot', () => {
  beforeEach(() => {
    cy.visit('https://example.cypress.io')
  })

  context('simple types', () => {
    it('works with objects', () => {
      cy.wrap({ foo: 42 }).snapshot()
    })

    it('works with numbers', () => {
      cy.wrap(42).snapshot()
    })

    it('works with strings', () => {
      cy.wrap('foo-bar').snapshot()
    })

    it('works with arrays', () => {
      cy.wrap([1, 2, 3]).snapshot()
    })

    it('should get the body tag', () => {
      cy.getBody().should('contain', 'Kitchen Sink')
    })
  })
})

That is working for me. getBody is known and can be chained.

@shcallaway
Copy link

😕 I must be doing something wrong then.

@NicholasBoll It's good that it works as expected, though.

@NicholasBoll
Copy link
Contributor

@shcallaway Note the snapshot repo is using Cypress 1.1.4 which has no generic. 1.2.0 adds the Subject generic. This shouldn't effect any implementation, but if you are creating your own commands in TypeScript the interface declaration will change slightly:

declare namespace Cypress {
  interface Chainable<Subject> {
    getBody: typeof getBody
  }
}

// can omit return type - it is inferred - will be `Cypress.Chainable<JQuery<HTMLBodyElement>>`
function getBody() {
  return cy.get('body')
}

@NicholasBoll
Copy link
Contributor

My steps were checking out the snapshot repo, checking out the branch and running npm install. I'm using VS Code. I'm curious to know why it isn't working for you.

Are you trying the snapshot repo?

Something to watch out for is your tsconfig.json needs to include files from ./cypress/support (or where ever you've configured that folder). Here's the tsconfig.json for the snapshot repo:

{
  "include": [
    "node_modules/cypress",
    "cypress/*/*.ts"
  ]
}

@shcallaway
Copy link

shcallaway commented Dec 15, 2017

@NicholasBoll I'm working off my own app — not snapshot. It appears to be configured correctly though. You can see my files here.

Just in case, I checked out the snapshot TypeScript branch and ran the tests (with your changes). They pass.

@shcallaway
Copy link

shcallaway commented Dec 15, 2017

Note that I have tried forcibly importing the Cypress types into my commands.ts by doing:

import "../../node_modules/cypress/index.d.ts";

Which results in a lot of these:

TS90010: Type 'Cypress.Chainable' is not assignable to type 'Cypress.Chainable'. Two different types with this name exist, but they are unrelated.��

I believe these errors occur because the return type for custom command is Cypress.Chainable but the return value is cy. Even though cy is of type Cypress.Chainable, TS is not correctly merging my definition of Cypress.Chainable with the existing definition.

@NicholasBoll
Copy link
Contributor

I got that same error when I used an import in commands.ts. I had to not have any import/export in that file (makes TypeScript treat as a module). Not sure why TS thinks they are unrelated in that case.

The tsconfig was a little tricky, but I think I'm doing it the same as you. My tsconfig.json looks like this:

./cypress/tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "../node_modules",
    "sourceMap": true,
    "target": "es2017",
    "strict": true,
    "moduleResolution": "node",
    "types": [
      "mocha"
    ],
    "lib": [
      "dom",
      "es2017"
    ]
  },
  "include": [
    "../node_modules",
    "**/*.ts"
  ]
}

Note the baseUrl and the include with ../node_modules.

Also make sure you don't have @types/cypress lurking in your node_modules. I was getting duplicate and conflicting definitions. Those types are pre-1.0.

@NicholasBoll
Copy link
Contributor

Normally TypeScript includes node_modules, but if the file isn't defined in the same directory as node_modules, I've had missing types.

I just noticed your tsconfig.json has ./*/*.ts. I have **/*.ts. I don't know the subtleties of glob, but the globstar should include any number of directories. I consider that safer.

@bahmutov
Copy link
Contributor

bahmutov commented Jan 2, 2018

I just tried it from scratch in TS 2.6.2 and Cypress 1.4.1 and it seems to work, adding new commands https://github.com/cypress-io/add-cypress-custom-command-in-typescript

I suspect there are other TS definitions in node_modules that somehow conflict or break TS compiler :(

@jennifer-shehane jennifer-shehane added the stage: work in progress There is an open PR for this issue [WIP] label Jan 3, 2018
@jogelin
Copy link

jogelin commented Apr 23, 2018

Any update with the version 2.x of cypress ?
I have the same issue:

Property login does not exist on type Chainable<undefined>

I am using cypress 2.1.0 and ts 2.7.1

Is https://github.com/bahmutov/add-typescript-to-cypress still necessary ? Ts seems to work without webpack preprocessor ....

@krzysztof-grzybek
Copy link

krzysztof-grzybek commented Aug 3, 2018

I fixed it by adding index.d.ts file in my commands folder. In this file I added something like this:

import { MyCustomType } from '../Types';

declare global {
  namespace Cypress {
    interface Chainable<Subject = any> {
      login(): Chainable<MyCustomType>;
    }
  }
}

If You don't import or export anything, just omit global namespace declaration:

declare namespace Cypress {
  interface Chainable<Subject = any> {
    login(): Chainable<MyCustomType>;
  }
}

Keep in mind that it won't work with Typesciprt < 2.3, becuase default generics type has to be supported.

@brandonmp-cw
Copy link

brandonmp-cw commented Sep 12, 2018

I was having this problem. TS and custom Cypress commands were working fine, but I after I added another custom command, everything broke.

I think it was b/c I was importing something into my cypress.d.ts in the wrong spot.

//** I had been importing something here

declare namespace Cypress {
  import { SalesforceGetAccountArgs } from "../cypress/support/sf";
//** Moving it to here (within the declaration) fixed things, apparently
  import { CustomerIoMessage } from "../lib/customer-io";

  export interface Chainable<Subject> {
    sfGetAccount: (
      getAccountArgs: SalesforceGetAccountArgs
    ) => Chainable<Partial<SalesforceAccount>>;
   getSentEmails: (email: string) => Chainable<Array<CustomerIoMessage>>;
  }
}

@andezzat
Copy link
Contributor

andezzat commented Dec 11, 2018

I've just had success extending the Cypress namespace declaration by simply including a neat index.d.ts file in the root of my cypress/support/commands folder. I (already) import that folder's cypress/support/commands/index.js file into my cypress/support/index.js and voila!

Would you guys be interested in adding these simple steps as an example in your docs to show users how they can extend type declarations for when they add custom commands?

@christophechevalier
Copy link

christophechevalier commented Dec 19, 2018

A few days ago, I made the migration of e2e javascript tests from cypress to typescript.
All is not completely finished because the return type on each command is any.
To do so, I just have to import the desired return types for each order.

command.d.ts :

import {IUserLogin} from '../../src/app/shared/services/users.service';

declare namespace Cypress {
  interface Chainable<Subject = any> {
    // login.commands
    login (username: string, password: string, shouldSuccess?: boolean): Chainable<IUserLogin>;

...

Note that at the very beginning of the file

./cypress/plugins/index.ts

, I added this magic line:

/// <reference types = "node" />

which allows me to recognize the type definitions for node and thus, to have a typescript file.

Here are the changes to supportFile and pluginsFile to do :

cypress.json

{
  "baseUrl": "http://localhost:4200",
  "video": false,
  "pluginsFile": "cypress/plugins/index.ts",
  "supportFile": "cypress/support/index.ts"
}

Here are the versions of the dependencies I use :

package.json

  "devDependencies": {
...
    "@bahmutov/add-typescript-to-cypress": "2.0.0",
    "@types/cypress": "1.1.3",
    "@types/node": "10.0.4",
    "cypress": "3.1.3",
    "ts-node": "6.1.2",
    "typescript": "2.9.2"
...
  },

If it interests people, I share a link to the cypress directory of my project :

https://gitlab.com/linagora/petals-cockpit/tree/master/frontend/cypress

@NicholasBoll
Copy link
Contributor

NicholasBoll commented Dec 19, 2018

@christophechevalier Typescript is weird about mixing modules and namespaces: https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html

Cypress uses a namespace. You can tell because you don't import cy to use the command. This is easier for JS development because you don't have to import anything. It is harder for Typescript development because now Typescript has to be told a namespace exists. Try moving your imports into the namespace as suggested here: #1065 (comment)

@NicholasBoll
Copy link
Contributor

Also I strongly encourage people use regular functions instead of custom commands: https://medium.com/@NicholasBoll/cypress-io-scaling-e2e-testing-with-custom-commands-6b72b902aab

@bahmutov
Copy link
Contributor

bahmutov commented Feb 7, 2019

You can provide TypeScript definitions for custom commands, even when using JavaScript in your specs

@bahmutov bahmutov closed this as completed Feb 7, 2019
@jennifer-shehane jennifer-shehane removed the stage: work in progress There is an open PR for this issue [WIP] label Feb 14, 2019
@spencermefford
Copy link

I had issues declaring the namespace when importing other files into commands.ts. I ran across this solution which worked for me:

cypress-io/add-cypress-custom-command-in-typescript#2 (comment)

Just wanted to help anyone else find that if they end up on this thread.

@vipulrawat
Copy link

I am using parcel instead to webpack, so can't use the webpack preprocessor. Is there any way to get the types of custom commands?

@NicholasBoll
Copy link
Contributor

I am using parcel instead to webpack, so can't use the webpack preprocessor. Is there any way to get the types of custom commands?

Cypress runs each of your test files in an isolated context. It doesn't matter that your app is bundled by Parcel. You can still use the webpack plugin for Cypress. You can use the browserify plugin. You can create a Parcel plugin instead. Typescript doesn't care which one you use either.

@vipulrawat
Copy link

Thanks @NicholasBoll. Didn't knew about this. It solved my issue.

@KarlaLopez1411
Copy link

Hi, guys, I follow all your recommendations but I got this error:
Screen Shot 2019-10-11 at 12 45 36

I'm trying to test a backend function.
Could someone help with this?

@stychu
Copy link

stychu commented Aug 24, 2021

Dunno if this is solved but I ran in the same problem... Following official guides doesn't allow me pretty much for importing any custom types to cover for custom commands definition types.
However this seem to kind of work.

// cypress/support/commands.ts

import { ssoPageSelectors } from './pageSelectors'
import { IAccount } from '../data/users'

const logInAs = (account: IAccount) => {
  const { username, password } = account
  cy.visit('/')

  cy.assertElementExists(ssoPageSelectors.userNameInput)
    .type(username)
    .get('body')
    .then(($body) => {
      const passwordInput = $body.find(ssoPageSelectors.passwordInput).length

      if (!passwordInput) {
        cy.get(ssoPageSelectors.loginButton).click()
      }
    })
    .assertElementExists(ssoPageSelectors.passwordInput)
    .type(password)
    .assertElementExists(ssoPageSelectors.loginButton)
    .click()
}

const assertChildrenLength = (element: string, count: number) => {
  cy.assertElementExists(element).children().should('have.length', count)
}

const assertElementExists = (element: string) => {
  cy.get(element).should('exist')
}

const assertElementNotExists = (element: string) => {
  cy.get(element).should('not.exist')
}

const commands = [
  logInAs,
  assertChildrenLength,
  assertElementExists,
  assertElementNotExists,
]

commands.forEach((command) => Cypress.Commands.add(command.name, command))

// cypress/support/types.d.ts

/* eslint-disable unused-imports/no-unused-imports */
/* eslint @typescript-eslint/no-unused-vars: 0 */
/// <reference types="cypress" />

import { IAccount } from '../data/users'

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Login with a given type of a user account
       * @param account - type of the account
       * @example
       * import { users } from '../../data/users'
       * cy.logInAs(users.ADMIN)
       */
      logInAs(account: IAccount): Cypress.Chainable<Element>

      /**
       * Asserts the length of children elements
       * @param element - selector as a string for element
       * @param count - expected count of child elements
       * @example
       * cy.assertChildrenLength('#element', 3)
       * cy.assertChildrenLength('.class', 3)
       */
      assertChildrenLength(
        element: string,
        count: number,
      ): Cypress.Chainable<Element>

      /**
       * Asserts that given element exists in the DOM
       * @param element - selector as a string for element
       * @example
       * cy.assertElementExists('#element')
       * cy.assertElementExists('.class')
       */
      assertElementExists(element: string): Cypress.Chainable<Element>

      /**
       * Asserts that given element exists in the DOM
       * @param element - selector as a string for element
       * @example
       * cy.assertElementExists('#element')
       * cy.assertElementExists('.class')
       */
      assertElementNotExists(element: string): Cypress.Chainable<Element>
    }
  }
}

@wiegell
Copy link

wiegell commented Sep 8, 2021

I fixed it by adding index.d.ts file in my commands folder. In this file I added something like this:

import { MyCustomType } from '../Types';

declare global {
  namespace Cypress {
    interface Chainable<Subject = any> {
      login(): Chainable<MyCustomType>;
    }
  }
}

If You don't import or export anything, just omit global namespace declaration:

declare namespace Cypress {
  interface Chainable<Subject = any> {
    login(): Chainable<MyCustomType>;
  }
}

Keep in mind that it won't work with Typesciprt < 2.3, becuase default generics type has to be supported.

This worked, but the index and command files have to be .js files - i wrongly still had them as .ts , which gives compilation errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests