@tsed/cli-core 7.0.0-beta.2 → 7.0.0-beta.4

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.
Files changed (44) hide show
  1. package/lib/esm/CliCore.js +1 -1
  2. package/lib/esm/decorators/command.js +1 -1
  3. package/lib/esm/decorators/index.js +0 -4
  4. package/lib/esm/fn/command.js +31 -3
  5. package/lib/esm/interfaces/index.js +1 -1
  6. package/lib/esm/packageManagers/PackageManagersModule.js +10 -18
  7. package/lib/esm/packageManagers/supports/BaseManager.js +2 -6
  8. package/lib/esm/services/CliHttpClient.js +3 -2
  9. package/lib/esm/services/CliHttpLogClient.js +1 -1
  10. package/lib/esm/services/CliLoadFile.js +5 -18
  11. package/lib/esm/services/CliPlugins.js +2 -2
  12. package/lib/esm/services/CliService.js +58 -71
  13. package/lib/esm/services/ProjectPackageJson.js +1 -1
  14. package/lib/esm/utils/getCommandMetadata.js +34 -7
  15. package/lib/esm/utils/index.js +1 -0
  16. package/lib/esm/utils/validate.js +23 -0
  17. package/lib/types/decorators/command.d.ts +2 -2
  18. package/lib/types/decorators/index.d.ts +0 -4
  19. package/lib/types/fn/command.d.ts +58 -3
  20. package/lib/types/interfaces/CommandMetadata.d.ts +17 -15
  21. package/lib/types/interfaces/{CommandParameters.d.ts → CommandOptions.d.ts} +20 -3
  22. package/lib/types/interfaces/CommandProvider.d.ts +0 -10
  23. package/lib/types/interfaces/index.d.ts +3 -3
  24. package/lib/types/packageManagers/PackageManagersModule.d.ts +1 -2
  25. package/lib/types/services/CliHttpLogClient.d.ts +1 -1
  26. package/lib/types/services/CliLoadFile.d.ts +2 -4
  27. package/lib/types/services/CliService.d.ts +4 -10
  28. package/lib/types/utils/index.d.ts +1 -0
  29. package/lib/types/utils/mapCommanderArgs.d.ts +1 -1
  30. package/lib/types/utils/validate.d.ts +14 -0
  31. package/package.json +2 -2
  32. package/lib/esm/decorators/on.js +0 -8
  33. package/lib/esm/decorators/onAdd.js +0 -5
  34. package/lib/esm/decorators/onExec.js +0 -5
  35. package/lib/esm/decorators/onPostInstall.js +0 -5
  36. package/lib/esm/decorators/onPrompt.js +0 -5
  37. package/lib/esm/domains/CommandStoreKeys.js +0 -8
  38. package/lib/types/decorators/on.d.ts +0 -1
  39. package/lib/types/decorators/onAdd.d.ts +0 -1
  40. package/lib/types/decorators/onExec.d.ts +0 -1
  41. package/lib/types/decorators/onPostInstall.d.ts +0 -1
  42. package/lib/types/decorators/onPrompt.d.ts +0 -1
  43. package/lib/types/domains/CommandStoreKeys.d.ts +0 -7
  44. /package/lib/esm/interfaces/{CommandParameters.js → CommandOptions.js} +0 -0
@@ -58,7 +58,7 @@ export class CliCore {
58
58
  name: settings.name || "tsed",
59
59
  argv,
60
60
  project: {
61
- // rootDir: this.getProjectRoot(argv),
61
+ rootDir: this.getProjectRoot(argv),
62
62
  srcDir: "src",
63
63
  scriptsDir: "scripts",
64
64
  ...(settings.project || {})
@@ -1,6 +1,6 @@
1
1
  import { command } from "../fn/command.js";
2
2
  export function Command(options) {
3
3
  return (token) => {
4
- command(token, options);
4
+ command({ ...options, token });
5
5
  };
6
6
  }
@@ -1,5 +1 @@
1
1
  export * from "./command.js";
2
- export * from "./onAdd.js";
3
- export * from "./onExec.js";
4
- export * from "./onPostInstall.js";
5
- export * from "./onPrompt.js";
@@ -1,5 +1,33 @@
1
1
  import { injectable } from "@tsed/di";
2
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
3
- export function command(token, options) {
4
- return injectable(token).type("command").set(CommandStoreKeys.COMMAND, options);
2
+ import { JsonSchema } from "@tsed/schema";
3
+ JsonSchema.add("prompt", function prompt(label) {
4
+ this.customKey("x-label", label);
5
+ return this;
6
+ })
7
+ .add("when", function when(fn) {
8
+ this.customKey("x-when", fn);
9
+ return this;
10
+ })
11
+ .add("opt", function opt(v) {
12
+ this.customKey("x-opt", v);
13
+ return this;
14
+ })
15
+ .add("choices", function choices(choices) {
16
+ this.customKey("x-choices", choices);
17
+ return this;
18
+ });
19
+ export function command(options) {
20
+ if (!options.token) {
21
+ return injectable(Symbol.for(`COMMAND_${options.name}`))
22
+ .type("command")
23
+ .set("command", options)
24
+ .factory(() => {
25
+ return {
26
+ ...options,
27
+ $prompt: options.prompt,
28
+ $exec: options.handler
29
+ };
30
+ });
31
+ }
32
+ return injectable(options.token).type("command").set("command", options);
5
33
  }
@@ -2,7 +2,7 @@ import { Type } from "@tsed/core";
2
2
  export * from "./CliDefaultOptions.js";
3
3
  export * from "./CommandData.js";
4
4
  export * from "./CommandMetadata.js";
5
- export * from "./CommandParameters.js";
5
+ export * from "./CommandOptions.js";
6
6
  export * from "./CommandProvider.js";
7
7
  export * from "./PackageJson.js";
8
8
  export * from "./ProjectPreferences.js";
@@ -1,5 +1,4 @@
1
- import { __decorate, __metadata, __param } from "tslib";
2
- import { Inject, injectable } from "@tsed/di";
1
+ import { Inject, inject, injectable, injectMany } from "@tsed/di";
3
2
  import { EMPTY, throwError } from "rxjs";
4
3
  import { catchError } from "rxjs/operators";
5
4
  import { ProjectPackageJson } from "../services/ProjectPackageJson.js";
@@ -18,17 +17,19 @@ function mapPackagesWithInvalidVersion(deps) {
18
17
  .filter(([, version]) => !isValidVersion(version))
19
18
  .map(toString);
20
19
  }
21
- let PackageManagersModule = class PackageManagersModule {
22
- constructor(packageManagers) {
23
- this.packageManagers = packageManagers;
24
- this.packageManagers = packageManagers.filter((manager) => manager.has());
20
+ export class PackageManagersModule {
21
+ constructor() {
22
+ this.projectPackageJson = inject(ProjectPackageJson);
23
+ this.packageManagers = injectMany("package:manager").filter((manager) => {
24
+ return manager.has();
25
+ });
25
26
  }
26
27
  init(options = {}) {
27
28
  const packageManager = this.get(options.packageManager);
28
29
  options.packageManager = packageManager.name;
29
30
  options = {
30
31
  ...options,
31
- cwd: this.projectPackageJson.dir,
32
+ cwd: this.projectPackageJson.cwd,
32
33
  env: {
33
34
  ...process.env,
34
35
  GH_TOKEN: this.projectPackageJson.GH_TOKEN
@@ -45,7 +46,7 @@ let PackageManagersModule = class PackageManagersModule {
45
46
  const deps = mapPackagesWithInvalidVersion(this.projectPackageJson.dependencies);
46
47
  options = {
47
48
  ...options,
48
- cwd: this.projectPackageJson.dir,
49
+ cwd: this.projectPackageJson.cwd,
49
50
  env: {
50
51
  ...process.env,
51
52
  GH_TOKEN: this.projectPackageJson.GH_TOKEN
@@ -116,14 +117,5 @@ let PackageManagersModule = class PackageManagersModule {
116
117
  });
117
118
  return this.get().runScript(scriptName, options).pipe(errorPipe());
118
119
  }
119
- };
120
- __decorate([
121
- Inject(),
122
- __metadata("design:type", ProjectPackageJson)
123
- ], PackageManagersModule.prototype, "projectPackageJson", void 0);
124
- PackageManagersModule = __decorate([
125
- __param(0, Inject("package:manager")),
126
- __metadata("design:paramtypes", [Array])
127
- ], PackageManagersModule);
128
- export { PackageManagersModule };
120
+ }
129
121
  injectable(PackageManagersModule).imports([YarnManager, YarnBerryManager, NpmManager, PNpmManager, BunManager]);
@@ -1,10 +1,10 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import { Inject } from "@tsed/di";
1
+ import { inject } from "@tsed/di";
3
2
  import { Observable } from "rxjs";
4
3
  import { CliExeca } from "../../services/CliExeca.js";
5
4
  export class BaseManager {
6
5
  constructor() {
7
6
  this.verboseOpt = "--verbose";
7
+ this.cliExeca = inject(CliExeca);
8
8
  }
9
9
  has() {
10
10
  try {
@@ -23,7 +23,3 @@ export class BaseManager {
23
23
  return this.cliExeca.run(this.cmd, [cmd, options.verbose && this.verboseOpt, ...args].filter(Boolean), options);
24
24
  }
25
25
  }
26
- __decorate([
27
- Inject(CliExeca),
28
- __metadata("design:type", CliExeca)
29
- ], BaseManager.prototype, "cliExeca", void 0);
@@ -41,10 +41,11 @@ export class CliHttpClient extends CliHttpLogClient {
41
41
  return this.mapResponse(result, options);
42
42
  }
43
43
  getRequestParameters(method, endpoint, options) {
44
+ const url = (this.host || "") + endpoint.replace(this.host || "", "");
44
45
  options = {
45
46
  method,
46
- url: (this.host || "") + endpoint.replace(this.host || "", ""),
47
47
  ...options,
48
+ url,
48
49
  params: options.params || options.qs,
49
50
  data: options.data,
50
51
  headers: {
@@ -53,7 +54,7 @@ export class CliHttpClient extends CliHttpLogClient {
53
54
  ...(options.headers || {})
54
55
  }
55
56
  };
56
- this.configureProxy(endpoint, options);
57
+ this.configureProxy(url, options);
57
58
  return options;
58
59
  }
59
60
  configureProxy(endpoint, options) {
@@ -11,7 +11,7 @@ let CliHttpLogClient = class CliHttpLogClient {
11
11
  this.callee = options.callee || "http";
12
12
  }
13
13
  onSuccess(options) {
14
- return this.logger.debug({
14
+ this.logger.debug({
15
15
  ...this.formatLog(options),
16
16
  status: "OK"
17
17
  });
@@ -1,21 +1,12 @@
1
1
  import { extname } from "node:path";
2
2
  import { inject, injectable } from "@tsed/di";
3
- import { default as Ajv } from "ajv";
3
+ import { validate } from "../utils/validate.js";
4
4
  import { CliFs } from "./CliFs.js";
5
5
  import { CliYaml } from "./CliYaml.js";
6
6
  export class CliLoadFile {
7
- // @ts-ignore
8
- #ajv;
9
7
  constructor() {
10
8
  this.cliYaml = inject(CliYaml);
11
9
  this.cliFs = inject(CliFs);
12
- const options = {
13
- verbose: false,
14
- coerceTypes: true,
15
- strict: false
16
- };
17
- // @ts-ignore
18
- this.#ajv = new Ajv(options);
19
10
  }
20
11
  /**
21
12
  * Load a configuration file from yaml, json
@@ -33,18 +24,14 @@ export class CliLoadFile {
33
24
  throw new Error("Unsupported format file");
34
25
  }
35
26
  if (schema) {
36
- const validate = this.#ajv.compile(schema);
37
- const isValid = validate(config);
27
+ const { isValid, errors, value } = validate(config, schema);
38
28
  if (!isValid) {
39
- const [error] = validate.errors;
40
- throw new Error([
41
- `${error.instancePath.replace(/\//gi, ".")} `,
42
- error.message,
43
- error.params?.allowedValues && `. Allowed values: ${error.params?.allowedValues}`
44
- ]
29
+ const [error] = errors;
30
+ throw new Error([`${error.path.replace(/\//gi, ".")} `, error.message, error.expected && `. Allowed values: ${error.expected}`]
45
31
  .filter(Boolean)
46
32
  .join(""));
47
33
  }
34
+ return value;
48
35
  }
49
36
  return config;
50
37
  }
@@ -1,6 +1,6 @@
1
1
  import { constant, inject, injectable } from "@tsed/di";
2
+ import { $asyncEmit } from "@tsed/hooks";
2
3
  import chalk from "chalk";
3
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
4
4
  import { PackageManagersModule } from "../packageManagers/PackageManagersModule.js";
5
5
  import { createSubTasks } from "../utils/createTasksRunner.js";
6
6
  import { loadPlugins } from "../utils/loadPlugins.js";
@@ -33,7 +33,7 @@ export class CliPlugins {
33
33
  return {
34
34
  title: `Run plugin '${chalk.cyan(plugin)}'`,
35
35
  task: () => {
36
- return this.cliHooks.emit(CommandStoreKeys.ADD, plugin, ctx);
36
+ return $asyncEmit("$onAddPlugin", [plugin, ctx]);
37
37
  }
38
38
  };
39
39
  });
@@ -1,4 +1,4 @@
1
- import { classOf } from "@tsed/core";
1
+ import { classOf, isArrowFn } from "@tsed/core";
2
2
  import { configuration, constant, context, destroyInjector, DIContext, getContext, inject, injectable, injector, logger, Provider, runInContext } from "@tsed/di";
3
3
  import { $asyncAlter, $asyncEmit } from "@tsed/hooks";
4
4
  import { pascalCase } from "change-case";
@@ -6,11 +6,10 @@ import { Argument, Command } from "commander";
6
6
  import Inquirer from "inquirer";
7
7
  import inquirer_autocomplete_prompt from "inquirer-autocomplete-prompt";
8
8
  import { v4 } from "uuid";
9
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
10
9
  import { PackageManagersModule } from "../packageManagers/index.js";
11
10
  import { createSubTasks, createTasksRunner } from "../utils/createTasksRunner.js";
12
11
  import { getCommandMetadata } from "../utils/getCommandMetadata.js";
13
- import { mapCommanderOptions } from "../utils/index.js";
12
+ import { mapCommanderOptions, validate } from "../utils/index.js";
14
13
  import { mapCommanderArgs } from "../utils/mapCommanderArgs.js";
15
14
  import { parseOption } from "../utils/parseOption.js";
16
15
  import { CliHooks } from "./CliHooks.js";
@@ -44,78 +43,57 @@ export class CliService {
44
43
  */
45
44
  runLifecycle(cmdName, data = {}, $ctx) {
46
45
  return runInContext($ctx, async () => {
47
- await $asyncEmit("$loadPackageJson");
48
- data = await this.beforePrompt(cmdName, data);
49
- $ctx.set("data", data);
50
- data = await this.prompt(cmdName, data);
51
- await this.dispatch(cmdName, data, $ctx);
52
- });
53
- }
54
- async dispatch(cmdName, data, $ctx) {
55
- try {
56
46
  $ctx.set("dispatchCmd", cmdName);
57
- $ctx.set("data", data);
58
- await this.exec(cmdName, data, $ctx);
59
- }
60
- catch (er) {
61
- await $asyncEmit("$onFinish", er);
47
+ await $asyncEmit("$loadPackageJson");
48
+ data = await this.prompt(cmdName, data, $ctx);
49
+ try {
50
+ await this.exec(cmdName, data, $ctx);
51
+ }
52
+ catch (er) {
53
+ await $asyncEmit("$onFinish", [data, er]);
54
+ await destroyInjector();
55
+ throw er;
56
+ }
57
+ await $asyncEmit("$onFinish", [data]);
62
58
  await destroyInjector();
63
- throw er;
64
- }
65
- await $asyncEmit("$onFinish");
66
- await destroyInjector();
59
+ });
67
60
  }
68
61
  async exec(cmdName, data, $ctx) {
69
- const initialTasks = await this.getTasks(cmdName, data);
70
- if (initialTasks.length) {
71
- const tasks = [
72
- ...initialTasks,
73
- {
62
+ const tasks = await this.getTasks(cmdName, data);
63
+ $ctx.set("data", data);
64
+ if (tasks.length) {
65
+ if (this.reinstallAfterRun && (this.projectPkg.rewrite || this.projectPkg.reinstall)) {
66
+ tasks.push({
74
67
  title: "Install dependencies",
75
- enabled: () => this.reinstallAfterRun && (this.projectPkg.rewrite || this.projectPkg.reinstall),
76
68
  task: createSubTasks(() => this.packageManagers.install(data), { ...data, concurrent: false })
77
- },
78
- ...(await this.getPostInstallTasks(cmdName, data))
79
- ];
80
- return createTasksRunner(tasks, this.mapData(cmdName, data, $ctx));
81
- }
82
- }
83
- /**
84
- * Run prompt for a given command
85
- * @param cmdName
86
- * @param data Initial data
87
- */
88
- async beforePrompt(cmdName, data = {}) {
89
- const provider = this.commands.get(cmdName);
90
- const instance = inject(provider.useClass);
91
- const verbose = data.verbose;
92
- if (instance.$beforePrompt) {
93
- data = await instance.$beforePrompt(JSON.parse(JSON.stringify(data)));
94
- data.verbose = verbose;
69
+ }, ...(await this.getPostInstallTasks(cmdName, data)));
70
+ }
71
+ data = this.mapData(cmdName, data, $ctx);
72
+ $ctx.set("data", data);
73
+ return createTasksRunner(tasks, data);
95
74
  }
96
- return data;
97
75
  }
98
76
  /**
99
77
  * Run prompt for a given command
100
78
  * @param cmdName
101
- * @param ctx Initial data
79
+ * @param data
80
+ * @param $ctx
102
81
  */
103
- async prompt(cmdName, ctx = {}) {
82
+ async prompt(cmdName, data = {}, $ctx) {
104
83
  const provider = this.commands.get(cmdName);
105
- const instance = inject(provider.useClass);
84
+ const instance = inject(provider.token);
85
+ $ctx.set("data", data);
106
86
  if (instance.$prompt) {
107
- const questions = [
108
- ...(await instance.$prompt(ctx)),
109
- ...(await this.hooks.emit(CommandStoreKeys.PROMPT_HOOKS, cmdName, ctx))
110
- ];
87
+ const questions = [...(await instance.$prompt(data))];
111
88
  if (questions.length) {
112
- ctx = {
113
- ...ctx,
89
+ data = {
90
+ ...data,
114
91
  ...(await Inquirer.prompt(questions))
115
92
  };
116
93
  }
117
94
  }
118
- return ctx;
95
+ $ctx.set("data", data);
96
+ return data;
119
97
  }
120
98
  /**
121
99
  * Run lifecycle
@@ -127,14 +105,10 @@ export class CliService {
127
105
  const provider = this.commands.get(cmdName);
128
106
  const instance = inject(provider.token);
129
107
  data = this.mapData(cmdName, data, $ctx);
130
- if (instance.$beforeExec) {
131
- await instance.$beforeExec(data);
132
- }
133
- return [
134
- ...(await instance.$exec(data)),
135
- ...(await this.hooks.emit(CommandStoreKeys.EXEC_HOOKS, cmdName, data)),
136
- ...(await $asyncAlter(`$alter${pascalCase(cmdName)}Tasks`, [], [data]))
137
- ].map((opts) => {
108
+ const tasks = [];
109
+ tasks.push(...((await instance.$exec(data)) || []));
110
+ tasks.push(...(await $asyncAlter(`$alter${pascalCase(cmdName)}Tasks`, [], [data])));
111
+ return tasks.map((opts) => {
138
112
  return {
139
113
  ...opts,
140
114
  task: async (arg, task) => {
@@ -148,32 +122,44 @@ export class CliService {
148
122
  }
149
123
  async getPostInstallTasks(cmdName, data) {
150
124
  const provider = this.commands.get(cmdName);
151
- const instance = inject(provider.useClass);
125
+ const instance = inject(provider.token);
152
126
  data = this.mapData(cmdName, data, getContext());
153
127
  return [
154
128
  ...(instance.$postInstall ? await instance.$postInstall(data) : []),
155
- ...(await this.hooks.emit(CommandStoreKeys.POST_INSTALL_HOOKS, cmdName, data)),
156
129
  ...(await $asyncAlter(`$alter${pascalCase(cmdName)}PostInstallTasks`, [], [data])),
157
130
  ...(instance.$afterPostInstall ? await instance.$afterPostInstall(data) : [])
158
131
  ];
159
132
  }
160
133
  createCommand(metadata) {
161
- const { args, name, options, description, alias, allowUnknownOption } = metadata;
134
+ const { name, description, alias, inputSchema } = metadata;
162
135
  if (this.commands.has(name)) {
163
136
  return this.commands.get(name).command;
164
137
  }
165
- let cmd = this.program.command(name);
138
+ const { args, options, allowUnknownOption } = metadata.getOptions();
166
139
  const onAction = (commandName) => {
167
140
  const [, ...rawArgs] = cmd.args;
168
141
  const mappedArgs = mapCommanderArgs(args, this.program.args.filter((arg) => commandName === arg));
169
142
  const allOpts = mapCommanderOptions(commandName, this.program.commands);
170
- const data = {
143
+ let data = {
171
144
  ...allOpts,
172
145
  verbose: !!this.program.opts().verbose,
173
146
  ...mappedArgs,
174
147
  ...cmd.opts(),
175
148
  rawArgs
176
149
  };
150
+ if (inputSchema) {
151
+ const { isValid, errors, value } = validate(data, isArrowFn(inputSchema) ? inputSchema() : inputSchema);
152
+ if (isValid) {
153
+ data = value;
154
+ }
155
+ else {
156
+ logger().error({
157
+ event: "VALIDATION_ERROR",
158
+ errors
159
+ });
160
+ throw new Error("Validation error");
161
+ }
162
+ }
177
163
  const $ctx = new DIContext({
178
164
  id: v4(),
179
165
  injector: injector(),
@@ -187,6 +173,7 @@ export class CliService {
187
173
  configuration().set("command.metadata", metadata);
188
174
  return this.runLifecycle(name, data, $ctx);
189
175
  };
176
+ let cmd = this.program.command(name);
190
177
  if (alias) {
191
178
  cmd = cmd.alias(alias);
192
179
  }
@@ -205,7 +192,7 @@ export class CliService {
205
192
  }
206
193
  mapData(cmdName, data, $ctx) {
207
194
  const provider = this.commands.get(cmdName);
208
- const instance = inject(provider.useClass);
195
+ const instance = inject(provider.token);
209
196
  const verbose = data.verbose;
210
197
  data.commandName ||= cmdName;
211
198
  if (instance.$mapContext) {
@@ -227,7 +214,7 @@ export class CliService {
227
214
  * @param provider
228
215
  */
229
216
  build(provider) {
230
- const metadata = getCommandMetadata(provider.useClass);
217
+ const metadata = getCommandMetadata(provider.token);
231
218
  if (metadata.name) {
232
219
  if (this.commands.has(metadata.name)) {
233
220
  throw Error(`The ${metadata.name} command is already registered. Change your command name used by the class ${classOf(provider.useClass)}`);
@@ -59,7 +59,7 @@ export class ProjectPackageJson {
59
59
  this.setCWD(dir);
60
60
  }
61
61
  get cwd() {
62
- return String(constant("project.rootDir"));
62
+ return String(constant("project.rootDir", ""));
63
63
  }
64
64
  get name() {
65
65
  return this.raw.name;
@@ -1,17 +1,44 @@
1
- import { Store } from "@tsed/core";
2
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
1
+ import { isArrowFn, Store } from "@tsed/core";
3
2
  export function getCommandMetadata(token) {
4
- const { name, alias, args = {}, allowUnknownOption, description, options = {}, enableFeatures, disableReadUpPkg, bindLogger = true, ...opts } = Store.from(token)?.get(CommandStoreKeys.COMMAND);
3
+ const { name, alias, args = {}, description, options = {}, enableFeatures, disableReadUpPkg, inputSchema, bindLogger = true, ...opts } = Store.from(token)?.get("command");
5
4
  return {
6
5
  name,
6
+ inputSchema,
7
7
  alias,
8
- args,
9
8
  description,
10
- options,
11
- allowUnknownOption: !!allowUnknownOption,
12
9
  enableFeatures: enableFeatures || [],
13
10
  disableReadUpPkg: !!disableReadUpPkg,
14
11
  bindLogger,
15
- ...opts
12
+ ...opts,
13
+ getOptions() {
14
+ if (inputSchema) {
15
+ const schema = isArrowFn(inputSchema) ? inputSchema() : inputSchema;
16
+ Object.entries(schema.get("properties") || {})?.forEach(([propertyKey, propertySchema]) => {
17
+ const base = {
18
+ type: propertySchema.getTarget(),
19
+ itemType: propertySchema.isCollection ? propertySchema.get("items").getTarget() : undefined,
20
+ description: propertySchema.get("description") || "",
21
+ defaultValue: propertySchema.get("default"),
22
+ required: schema.isRequired(propertyKey)
23
+ };
24
+ const opt = propertySchema.get("x-opt");
25
+ if (opt) {
26
+ options[opt] = {
27
+ ...base,
28
+ customParser: schema.get("custom-parser")
29
+ };
30
+ }
31
+ else {
32
+ args[propertyKey] = base;
33
+ }
34
+ });
35
+ opts.allowUnknownOption = !!schema.get("additionalProperties");
36
+ }
37
+ return {
38
+ args,
39
+ options,
40
+ allowUnknownOption: !!opts.allowUnknownOption
41
+ };
42
+ }
16
43
  };
17
44
  }
@@ -8,3 +8,4 @@ export * from "./mapCommanderArgs.js";
8
8
  export * from "./mapCommanderOptions.js";
9
9
  export * from "./parseOption.js";
10
10
  export * from "./resolveConfiguration.js";
11
+ export * from "./validate.js";
@@ -0,0 +1,23 @@
1
+ import { Ajv } from "ajv";
2
+ const ajv = new Ajv({
3
+ verbose: false,
4
+ coerceTypes: true,
5
+ strict: false,
6
+ allErrors: true
7
+ });
8
+ export function validate(value, schema) {
9
+ const validate = ajv.compile(schema.toJSON());
10
+ const result = validate(value);
11
+ if (!result) {
12
+ const errors = (validate.errors || []).map((e) => ({
13
+ path: e.instancePath || e.schemaPath,
14
+ message: e.message,
15
+ expected: (e.params && e.params.type) || undefined
16
+ }));
17
+ return {
18
+ isValid: false,
19
+ errors: errors
20
+ };
21
+ }
22
+ return { isValid: true, value: value };
23
+ }
@@ -1,2 +1,2 @@
1
- import type { CommandParameters } from "../interfaces/CommandParameters.js";
2
- export declare function Command(options: CommandParameters): ClassDecorator;
1
+ import type { BaseCommandOptions } from "../interfaces/CommandOptions.js";
2
+ export declare function Command<Input = any>(options: BaseCommandOptions<Input>): ClassDecorator;
@@ -1,5 +1 @@
1
1
  export * from "./command.js";
2
- export * from "./onAdd.js";
3
- export * from "./onExec.js";
4
- export * from "./onPostInstall.js";
5
- export * from "./onPrompt.js";
@@ -1,3 +1,58 @@
1
- import { injectable, type TokenProvider } from "@tsed/di";
2
- import type { CommandParameters } from "../interfaces/CommandParameters.js";
3
- export declare function command(token: TokenProvider, options: CommandParameters): ReturnType<typeof injectable>;
1
+ import type { Type } from "@tsed/core";
2
+ import { type FactoryTokenProvider } from "@tsed/di";
3
+ import { JsonSchema } from "@tsed/schema";
4
+ import type { CommandOptions } from "../interfaces/CommandOptions.js";
5
+ import type { CommandProvider } from "../interfaces/index.js";
6
+ type SchemaChoice = {
7
+ label: string;
8
+ value: string;
9
+ checked?: ((ctx: any) => boolean) | boolean;
10
+ items?: SchemaChoice[];
11
+ };
12
+ declare module "@tsed/schema" {
13
+ interface JsonSchema {
14
+ prompt(label: string): this;
15
+ opt(value: string): this;
16
+ when(fn: (ctx: any) => boolean): this;
17
+ choices(value: SchemaChoice[]): this;
18
+ }
19
+ }
20
+ export declare function command<Input>(options: CommandOptions<Input>): TsED.ProviderBuilder<FactoryTokenProvider<{
21
+ $prompt: any;
22
+ $exec: any;
23
+ token: import("@tsed/di").TokenProvider<CommandProvider>;
24
+ name: string;
25
+ alias?: string;
26
+ description: string;
27
+ args?: {
28
+ [key: string]: import("../interfaces/CommandOptions.js").CommandArg;
29
+ };
30
+ inputSchema?: JsonSchema<Input> | (() => JsonSchema<Input>) | undefined;
31
+ options?: {
32
+ [key: string]: import("../interfaces/CommandOptions.js").CommandOpts;
33
+ };
34
+ allowUnknownOption?: boolean;
35
+ enableFeatures?: string[];
36
+ disableReadUpPkg?: boolean;
37
+ bindLogger?: boolean;
38
+ } | {
39
+ $prompt: any;
40
+ $exec: any;
41
+ prompt?<T extends import("inquirer").Answers = import("inquirer").Answers>(initialOptions: Partial<Input>): import("../interfaces/CommandProvider.js").QuestionOptions<T> | Promise<import("../interfaces/CommandProvider.js").QuestionOptions<T>>;
42
+ handler: (data: Input) => import("../interfaces/Tasks.js").Tasks | Promise<import("../interfaces/Tasks.js").Tasks> | any | Promise<any>;
43
+ name: string;
44
+ alias?: string;
45
+ description: string;
46
+ args?: {
47
+ [key: string]: import("../interfaces/CommandOptions.js").CommandArg;
48
+ };
49
+ inputSchema?: JsonSchema<Input> | (() => JsonSchema<Input>) | undefined;
50
+ options?: {
51
+ [key: string]: import("../interfaces/CommandOptions.js").CommandOpts;
52
+ };
53
+ allowUnknownOption?: boolean;
54
+ enableFeatures?: string[];
55
+ disableReadUpPkg?: boolean;
56
+ bindLogger?: boolean;
57
+ }>> | TsED.ClassProviderBuilder<Type<CommandProvider<Input>>>;
58
+ export {};
@@ -1,19 +1,21 @@
1
- import type { CommandArg, CommandOptions, CommandParameters } from "./CommandParameters.js";
2
- export interface CommandMetadata extends CommandParameters {
3
- /**
4
- * CommandProvider arguments
5
- */
6
- args: {
7
- [key: string]: CommandArg;
8
- };
9
- /**
10
- * CommandProvider options
11
- */
12
- options: {
13
- [key: string]: CommandOptions;
14
- };
15
- allowUnknownOption?: boolean;
1
+ import type { BaseCommandOptions, CommandArg, CommandOpts } from "./CommandOptions.js";
2
+ export interface CommandMetadata extends Omit<BaseCommandOptions<any>, "args" | "options" | "allowUnknownOption"> {
16
3
  enableFeatures: string[];
17
4
  disableReadUpPkg: boolean;
18
5
  bindLogger: boolean;
6
+ getOptions(): {
7
+ /**
8
+ * CommandProvider arguments
9
+ */
10
+ args: {
11
+ [key: string]: CommandArg;
12
+ };
13
+ /**
14
+ * CommandProvider options
15
+ */
16
+ options: {
17
+ [key: string]: CommandOpts;
18
+ };
19
+ allowUnknownOption?: boolean;
20
+ };
19
21
  }
@@ -1,4 +1,9 @@
1
1
  import { Type } from "@tsed/core";
2
+ import type { TokenProvider } from "@tsed/di";
3
+ import type { JsonSchema } from "@tsed/schema";
4
+ import type { Answers } from "inquirer";
5
+ import type { CommandProvider, QuestionOptions } from "./CommandProvider.js";
6
+ import type { Tasks } from "./Tasks.js";
2
7
  export interface CommandArg {
3
8
  /**
4
9
  * Description of the argument
@@ -21,7 +26,7 @@ export interface CommandArg {
21
26
  */
22
27
  required?: boolean;
23
28
  }
24
- export interface CommandOptions {
29
+ export interface CommandOpts {
25
30
  /**
26
31
  * Description of the commander.option()
27
32
  */
@@ -48,7 +53,7 @@ export interface CommandOptions {
48
53
  */
49
54
  customParser?: (value: any) => any;
50
55
  }
51
- export interface CommandParameters {
56
+ export interface BaseCommandOptions<Input> {
52
57
  /**
53
58
  * name commands
54
59
  */
@@ -64,14 +69,26 @@ export interface CommandParameters {
64
69
  args?: {
65
70
  [key: string]: CommandArg;
66
71
  };
72
+ inputSchema?: JsonSchema<Input> | (() => JsonSchema<Input>);
67
73
  /**
68
74
  * CommandProvider options
69
75
  */
70
76
  options?: {
71
- [key: string]: CommandOptions;
77
+ [key: string]: CommandOpts;
72
78
  };
73
79
  allowUnknownOption?: boolean;
74
80
  enableFeatures?: string[];
75
81
  disableReadUpPkg?: boolean;
82
+ bindLogger?: boolean;
83
+ }
84
+ interface FunctionalCommandOptions<Input> extends BaseCommandOptions<Input> {
85
+ prompt?<T extends Answers = Answers>(initialOptions: Partial<Input>): QuestionOptions<T> | Promise<QuestionOptions<T>>;
86
+ handler: (data: Input) => Tasks | Promise<Tasks> | any | Promise<any>;
87
+ [key: string]: any;
88
+ }
89
+ export interface ClassCommandOptions<Input> extends BaseCommandOptions<Input> {
90
+ token: TokenProvider<CommandProvider>;
76
91
  [key: string]: any;
77
92
  }
93
+ export type CommandOptions<Input> = ClassCommandOptions<Input> | FunctionalCommandOptions<Input>;
94
+ export {};
@@ -9,11 +9,6 @@ declare module "inquirer" {
9
9
  }
10
10
  export type QuestionOptions<T extends Answers = Answers> = QuestionCollection<T>;
11
11
  export interface CommandProvider<Ctx = any> {
12
- /**
13
- * Run a function before the main prompt. Useful for pre-loading data from the file system
14
- * @param initialOptions
15
- */
16
- $beforePrompt?(initialOptions: Partial<Ctx>): Partial<Ctx>;
17
12
  /**
18
13
  * Hook to create the main prompt for the command
19
14
  * See https://github.com/enquirer/enquirer for more detail on question configuration.
@@ -25,11 +20,6 @@ export interface CommandProvider<Ctx = any> {
25
20
  * @param ctx
26
21
  */
27
22
  $mapContext?(ctx: Partial<Ctx>): Ctx;
28
- /**
29
- * Run something before the exec hook
30
- * @param ctx
31
- */
32
- $beforeExec?(ctx: Ctx): Promise<any>;
33
23
  /**
34
24
  * Run a command
35
25
  * @param ctx
@@ -1,10 +1,10 @@
1
- import { Type } from "@tsed/core";
1
+ import type { TokenProvider } from "@tsed/di";
2
2
  import type { CommandProvider } from "./CommandProvider.js";
3
3
  import type { PackageJson } from "./PackageJson.js";
4
4
  export * from "./CliDefaultOptions.js";
5
5
  export * from "./CommandData.js";
6
6
  export * from "./CommandMetadata.js";
7
- export * from "./CommandParameters.js";
7
+ export * from "./CommandOptions.js";
8
8
  export * from "./CommandProvider.js";
9
9
  export * from "./PackageJson.js";
10
10
  export * from "./ProjectPreferences.js";
@@ -15,7 +15,7 @@ declare global {
15
15
  /**
16
16
  * Load given commands
17
17
  */
18
- commands: Type<CommandProvider>[];
18
+ commands: TokenProvider<CommandProvider>[];
19
19
  /**
20
20
  * Init Cli with defined argv
21
21
  */
@@ -5,9 +5,8 @@ export interface InstallOptions {
5
5
  [key: string]: any;
6
6
  }
7
7
  export declare class PackageManagersModule {
8
- protected packageManagers: BaseManager[];
9
8
  protected projectPackageJson: ProjectPackageJson;
10
- constructor(packageManagers: BaseManager[]);
9
+ protected packageManagers: BaseManager[];
11
10
  init(options?: InstallOptions): Promise<void>;
12
11
  install(options?: InstallOptions): ({
13
12
  title: string;
@@ -6,7 +6,7 @@ export declare class CliHttpLogClient {
6
6
  callee: string;
7
7
  protected logger: Logger;
8
8
  constructor(options?: Partial<BaseLogClientOptions>);
9
- protected onSuccess(options: Record<string, unknown>): Logger;
9
+ protected onSuccess(options: Record<string, unknown>): void;
10
10
  protected onError(error: any, options: any): void;
11
11
  protected logToCurl(options: any): string;
12
12
  protected getStatusCodeFromError(error: any): any;
@@ -1,13 +1,11 @@
1
- import { type Schema } from "ajv";
1
+ import type { JsonSchema } from "@tsed/schema";
2
2
  import { CliFs } from "./CliFs.js";
3
3
  import { CliYaml } from "./CliYaml.js";
4
4
  export declare class CliLoadFile {
5
- #private;
6
5
  protected cliYaml: CliYaml;
7
6
  protected cliFs: CliFs;
8
- constructor();
9
7
  /**
10
8
  * Load a configuration file from yaml, json
11
9
  */
12
- loadFile<Model = any>(path: string, schema?: Schema): Promise<Model>;
10
+ loadFile<Model = any>(path: string, schema?: JsonSchema<Model>): Promise<Model>;
13
11
  }
@@ -26,20 +26,14 @@ export declare class CliService {
26
26
  * @param $ctx
27
27
  */
28
28
  runLifecycle(cmdName: string, data: CommandData | undefined, $ctx: DIContext): Promise<Promise<void>>;
29
- dispatch(cmdName: string, data: CommandData, $ctx: DIContext): Promise<void>;
30
29
  exec(cmdName: string, data: any, $ctx: DIContext): Promise<any>;
31
30
  /**
32
31
  * Run prompt for a given command
33
32
  * @param cmdName
34
- * @param data Initial data
35
- */
36
- beforePrompt(cmdName: string, data?: CommandData): Promise<CommandData>;
37
- /**
38
- * Run prompt for a given command
39
- * @param cmdName
40
- * @param ctx Initial data
33
+ * @param data
34
+ * @param $ctx
41
35
  */
42
- prompt(cmdName: string, ctx?: CommandData): Promise<CommandData>;
36
+ prompt(cmdName: string, data: CommandData | undefined, $ctx: DIContext): Promise<CommandData>;
43
37
  /**
44
38
  * Run lifecycle
45
39
  * @param cmdName
@@ -48,7 +42,7 @@ export declare class CliService {
48
42
  getTasks(cmdName: string, data: any): Promise<Task[]>;
49
43
  getPostInstallTasks(cmdName: string, data: any): Promise<any[]>;
50
44
  createCommand(metadata: CommandMetadata): any;
51
- private load;
45
+ load(): void;
52
46
  private mapData;
53
47
  /**
54
48
  * Build command and sub-commands
@@ -8,3 +8,4 @@ export * from "./mapCommanderArgs.js";
8
8
  export * from "./mapCommanderOptions.js";
9
9
  export * from "./parseOption.js";
10
10
  export * from "./resolveConfiguration.js";
11
+ export * from "./validate.js";
@@ -1,4 +1,4 @@
1
- import type { CommandArg } from "../interfaces/CommandParameters.js";
1
+ import type { CommandArg } from "../interfaces/CommandOptions.js";
2
2
  export declare function mapCommanderArgs(args: {
3
3
  [arg: string]: CommandArg;
4
4
  }, commandArgs: any[]): any;
@@ -0,0 +1,14 @@
1
+ import type { JsonSchema } from "@tsed/schema";
2
+ export declare function validate<Value>(value: unknown, schema: JsonSchema<Value>): {
3
+ isValid: boolean;
4
+ errors: {
5
+ path: string;
6
+ message: string | undefined;
7
+ expected: any;
8
+ }[];
9
+ value?: undefined;
10
+ } | {
11
+ isValid: boolean;
12
+ value: Value;
13
+ errors?: undefined;
14
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tsed/cli-core",
3
3
  "description": "Build your CLI with TypeScript and Decorators",
4
- "version": "7.0.0-beta.2",
4
+ "version": "7.0.0-beta.4",
5
5
  "type": "module",
6
6
  "main": "./lib/esm/index.js",
7
7
  "source": "./src/index.ts",
@@ -65,7 +65,7 @@
65
65
  "uuid": "^10.0.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@tsed/typescript": "7.0.0-beta.2",
68
+ "@tsed/typescript": "7.0.0-beta.4",
69
69
  "@types/commander": "2.12.2",
70
70
  "@types/figures": "3.0.1",
71
71
  "@types/fs-extra": "^11.0.4",
@@ -1,8 +0,0 @@
1
- import { StoreMerge } from "@tsed/core";
2
- export function On(hookName, name) {
3
- return (target, propertyKey) => {
4
- StoreMerge(hookName, {
5
- [name]: [propertyKey]
6
- })(target);
7
- };
8
- }
@@ -1,5 +0,0 @@
1
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
2
- import { On } from "./on.js";
3
- export function OnAdd(cliPlugin) {
4
- return On(CommandStoreKeys.ADD, cliPlugin);
5
- }
@@ -1,5 +0,0 @@
1
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
2
- import { On } from "./on.js";
3
- export function OnExec(cmdName) {
4
- return On(CommandStoreKeys.EXEC_HOOKS, cmdName);
5
- }
@@ -1,5 +0,0 @@
1
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
2
- import { On } from "./on.js";
3
- export function OnPostInstall(cmdName) {
4
- return On(CommandStoreKeys.POST_INSTALL_HOOKS, cmdName);
5
- }
@@ -1,5 +0,0 @@
1
- import { CommandStoreKeys } from "../domains/CommandStoreKeys.js";
2
- import { On } from "./on.js";
3
- export function OnPrompt(cmdName) {
4
- return On(CommandStoreKeys.PROMPT_HOOKS, cmdName);
5
- }
@@ -1,8 +0,0 @@
1
- export var CommandStoreKeys;
2
- (function (CommandStoreKeys) {
3
- CommandStoreKeys["COMMAND"] = "command";
4
- CommandStoreKeys["ADD"] = "$onAdd";
5
- CommandStoreKeys["EXEC_HOOKS"] = "$onExec";
6
- CommandStoreKeys["POST_INSTALL_HOOKS"] = "$onPostInstall";
7
- CommandStoreKeys["PROMPT_HOOKS"] = "$onPrompt";
8
- })(CommandStoreKeys || (CommandStoreKeys = {}));
@@ -1 +0,0 @@
1
- export declare function On(hookName: string, name: string): MethodDecorator;
@@ -1 +0,0 @@
1
- export declare function OnAdd(cliPlugin: string): MethodDecorator;
@@ -1 +0,0 @@
1
- export declare function OnExec(cmdName: string): MethodDecorator;
@@ -1 +0,0 @@
1
- export declare function OnPostInstall(cmdName: string): MethodDecorator;
@@ -1 +0,0 @@
1
- export declare function OnPrompt(cmdName: string): MethodDecorator;
@@ -1,7 +0,0 @@
1
- export declare enum CommandStoreKeys {
2
- COMMAND = "command",
3
- ADD = "$onAdd",
4
- EXEC_HOOKS = "$onExec",
5
- POST_INSTALL_HOOKS = "$onPostInstall",
6
- PROMPT_HOOKS = "$onPrompt"
7
- }