jmcdo29 / nest-commander

A module for using NestJS to build up CLI applications
https://nest-commander.jaymcdoniel.dev/
MIT License
435 stars 53 forks source link

TypeError: Class constructor CommandRunner cannot be invoked without 'new' #624

Closed slinkardbrandon closed 1 year ago

slinkardbrandon commented 2 years ago

Is there an existing issue for this?

Current behavior

Currently when I try to run the example on the docs site to get a bare minimum nest-commander sample working I'm running into an error being thrown and I can't seem to understand why at the moment. Below is the error I'm seeing when running my typescript build and then node ./path/to/cli/dist/main.js.

bootstrapping
Starting Nest application... NestFactory
Class constructor CommandRunner cannot be invoked without 'new' TypeError: Class constructor CommandRunner cannot be invoked without 'new'
    at new Create (/Users/bslinkard/path/to/app/folder/apps/cli/dist/commands/create.command.js:65:42)
    at Injector.instantiateClass (/Users/bslinkard/path/to/app/folder/node_modules/@nestjs/core/injector/injector.js:340:19)
    at callback (/Users/bslinkard/path/to/app/folder/node_modules/@nestjs/core/injector/injector.js:53:45)
    at async Injector.resolveConstructorParams (/Users/bslinkard/path/to/app/folder/node_modules/@nestjs/core/injector/injector.js:132:24)
    at async Injector.loadInstance (/Users/bslinkard/path/to/app/folder/node_modules/@nestjs/core/injector/injector.js:57:13)
    at async Injector.loadProvider (/Users/bslinkard/path/to/app/folder/node_modules/@nestjs/core/injector/injector.js:84:9)
    at async Promise.all (index 3)
    at async InstanceLoader.createInstancesOfProviders (/Users/bslinkard/path/to/app/folder/node_modules/@nestjs/core/injector/instance-loader.js:47:9)
    at async /Users/bslinkard/path/to/app/folder/node_modules/@nestjs/core/injector/instance-loader.js:32:13
    at async Promise.all (index 4) ExceptionHandler

Minimum reproduction code

"dependencies": {
  "typescript": "4.8.4"
  "@nestjs/common": "9.1.6",
  "nest-commander": "3.2.0",
}
// tsconfig.json
{
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules"],
  "compilerOptions": {
    "experimentalDecorators": true,
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": false,
    "noUncheckedIndexedAccess": true,
    "module": "CommonJS",
    "noEmit": false,
    "outDir": "./dist"
  }
}
// src/commands/create.command.ts

import { Command, CommandRunner } from "nest-commander";

@Command({
  name: 'create',
  description: 'Create something awesome'
})
export class Create extends CommandRunner {
  async run(
    inputs: string[],
    options: Record<string, any>
  ): Promise<void> {
    console.log('create', inputs, options);
  }
}
// src/app.module.ts
import { Module } from '@nestjs/common';
import { Create } from './commands/create.command';

@Module({
  providers: [Create]
})
export class AppModule {}
// src/main.ts
import { CommandFactory } from 'nest-commander';
import { AppModule } from './app.module.js';

async function bootstrap() {
  console.log('bootstrapping');
  await CommandFactory.run(AppModule, { usePlugins: true, logger: console });
}

bootstrap();

Expected behavior

I would expect that console.log('create') would print something (from my command file), but the CLI seems to error out before then

Package

Package version

3.2.0

Node.js version

18.12.0

In which operating systems have you tested?

Other

Same issue exists in node 16.14.2

jmcdo29 commented 2 years ago

Whoa, that's definitely unexpected. Any chance you can get me a repository that I can clone that shows this same behavior?

slinkardbrandon commented 2 years ago

Actually it doesn't look like I need to stand up a separate repo to showcase the issue because I just figured out the problem (and it was on my end). It was an issue with how my typescript is compiling rather than with this library. I actually had two issues in my TSConfig that I can show below.

First of all, I completely forgot to add the flag for emitting decorator metadata, so I had to add that in (which caused other issues, but not this one)

Second, the compile time target had to be es6+ rather than es5, so updating the target to ES2015 or later should fix this issue should anyone else stumble upon it.

{
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules"],
  "compilerOptions": {
+   "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
-   "target": "es5",
+   "target": "ES2015",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": false,
    "noUncheckedIndexedAccess": true,
    "module": "CommonJS",
    "noEmit": false,
    "outDir": "./dist"
  }
}

Maybe it makes sense to call out some of the required tsconfig settings in the docs site? I could probably submit a PR for that.

jmcdo29 commented 2 years ago

I would usually expect the emitDecoratorMetadata to already be true, as that's something that Nest itself requires, but if you think calling it out could be helpful then feel free to make a PR for that.

As for the target, do you know why it needed to be es6+ rather than es5? I always seem to miss something in the details with the versions and usually just go with what nest new or create-nx-workspace creates for me

slinkardbrandon commented 2 years ago

I would usually expect the emitDecoratorMetadata to already be true, as that's something that Nest itself requires

I think this may have been where I actually got hung up, this repo is actually my first real taste of working with Nest at all, I've not been able to use the full Nest ecosystem quite yet, but I'm hoping this tooling may be a good first step for the company as an introduction of sorts.

usually just go with what nest new or create-nx-workspace creates for me

Ahh see, I bootstrapped this repo with turborepo rather than nx and was using a baseline company tsconfig and extending from there, the company config didn't have the decorator metadata set properly at all and I only added in experimentalDecorators rather than adding both of the necessary pieces there.

According to this page on the TypeScript docs page it seems like the decorators should work with es5.

Perhaps it has something to do with Nest being compiled to es6 rather than es5 ?

jmcdo29 commented 2 years ago

this repo is actually my first real taste of working with Nest at all

Ah! Welcome to the framework then!

Perhaps it has something to do with Nest being compiled to es6 rather than es5?

Yeah, that would probably be it

Ahh see, I bootstrapped this repo with turborepo rather than nx and was using a baseline company tsconfig and extending from there, the company config didn't have the decorator metadata set properly at all and I only added in experimentalDecorators rather than adding both of the necessary pieces there.

Gotcha! Well, like I said, if you want to make a PR to specify these things feel free to do so.