task-script-support-cli 0.2.19 → 0.3.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.
Files changed (65) hide show
  1. package/dist/package.json +2 -2
  2. package/dist/src/commands/gen.d.ts +1 -2
  3. package/dist/src/commands/gen.d.ts.map +1 -1
  4. package/dist/src/index.js +3 -2
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/services/arg-service.d.ts +3 -0
  7. package/dist/src/services/arg-service.d.ts.map +1 -1
  8. package/dist/src/services/arg-service.js +18 -2
  9. package/dist/src/services/arg-service.js.map +1 -1
  10. package/dist/src/services/file-service.d.ts.map +1 -1
  11. package/dist/src/services/file-service.js +2 -1
  12. package/dist/src/services/file-service.js.map +1 -1
  13. package/dist/src/services/prompt-service.js +1 -1
  14. package/dist/src/services/prompt-service.js.map +1 -1
  15. package/dist/src/services/spawn-service.d.ts +0 -9
  16. package/dist/src/services/spawn-service.d.ts.map +1 -1
  17. package/dist/src/services/spawn-service.js +4 -100
  18. package/dist/src/services/spawn-service.js.map +1 -1
  19. package/dist/src/tasks/create-new-project.d.ts.map +1 -1
  20. package/dist/src/tasks/create-new-project.js +3 -0
  21. package/dist/src/tasks/create-new-project.js.map +1 -1
  22. package/dist/src/tasks/generate/generate-command.d.ts +3 -18
  23. package/dist/src/tasks/generate/generate-command.d.ts.map +1 -1
  24. package/dist/src/tasks/generate/generate-command.js +7 -60
  25. package/dist/src/tasks/generate/generate-command.js.map +1 -1
  26. package/dist/src/tasks/generate/generate-service.d.ts +3 -10
  27. package/dist/src/tasks/generate/generate-service.d.ts.map +1 -1
  28. package/dist/src/tasks/generate/generate-service.js +9 -22
  29. package/dist/src/tasks/generate/generate-service.js.map +1 -1
  30. package/dist/src/tasks/generate/generate-task.d.ts +3 -10
  31. package/dist/src/tasks/generate/generate-task.d.ts.map +1 -1
  32. package/dist/src/tasks/generate/generate-task.js +9 -22
  33. package/dist/src/tasks/generate/generate-task.js.map +1 -1
  34. package/dist/src/templates/command.d.ts.map +1 -1
  35. package/dist/src/templates/command.js +8 -2
  36. package/dist/src/templates/command.js.map +1 -1
  37. package/dist/src/templates/service.d.ts +1 -1
  38. package/dist/src/templates/service.d.ts.map +1 -1
  39. package/dist/src/templates/service.js +29 -1
  40. package/dist/src/templates/service.js.map +1 -1
  41. package/dist/src/templates/task.d.ts +1 -1
  42. package/dist/src/templates/task.d.ts.map +1 -1
  43. package/dist/src/templates/task.js +37 -1
  44. package/dist/src/templates/task.js.map +1 -1
  45. package/dist/src/types/project.d.ts +4 -0
  46. package/dist/src/types/project.d.ts.map +1 -1
  47. package/dist/src/wrappers/gen-app-task.d.ts +50 -0
  48. package/dist/src/wrappers/gen-app-task.d.ts.map +1 -0
  49. package/dist/src/wrappers/gen-app-task.js +124 -0
  50. package/dist/src/wrappers/gen-app-task.js.map +1 -0
  51. package/package.json +2 -2
  52. package/src/index.ts +3 -2
  53. package/src/services/arg-service.ts +21 -2
  54. package/src/services/file-service.ts +2 -1
  55. package/src/services/prompt-service.ts +1 -1
  56. package/src/services/spawn-service.ts +1 -74
  57. package/src/tasks/create-new-project.ts +2 -0
  58. package/src/tasks/generate/generate-command.ts +7 -70
  59. package/src/tasks/generate/generate-service.ts +8 -14
  60. package/src/tasks/generate/generate-task.ts +8 -14
  61. package/src/templates/command.ts +10 -2
  62. package/src/templates/service.ts +34 -2
  63. package/src/templates/task.ts +42 -2
  64. package/src/types/project.ts +5 -0
  65. package/src/wrappers/gen-app-task.ts +150 -0
@@ -13,7 +13,8 @@ export class ArgService {
13
13
 
14
14
  const argObjects: ArgObject[] = [];
15
15
  for (const a of this.args) {
16
- if (Array.isArray(a) || typeof a === "string") continue;
16
+ if (Array.isArray(a) || typeof a === "string" || a === undefined)
17
+ continue;
17
18
  argObjects.push(a);
18
19
  }
19
20
 
@@ -32,6 +33,7 @@ export class ArgService {
32
33
  throw new Error("Missing Arguments in ArgService!");
33
34
  }
34
35
  return this.args.find((arg) => {
36
+ if (arg === undefined) return false;
35
37
  if (typeof arg === "string") return false;
36
38
  if (Array.isArray(arg)) return false;
37
39
  return arg[flag] !== undefined;
@@ -42,9 +44,26 @@ export class ArgService {
42
44
  return UtilService.titleizeAll(name);
43
45
  }
44
46
 
47
+ hasPositionalArg(index: number = 0, typeOf: string = "string"): boolean {
48
+ return this.args!.length > index && typeof this.args![index] === typeOf;
49
+ }
50
+
51
+ getPositionalArg<T = string>(index: number = 0): T | undefined {
52
+ return this.args!.length > index ? (this.args![index] as T) : undefined;
53
+ }
54
+
55
+ getStringArg(): string | undefined {
56
+ if (this.hasPositionalArg(0, "string")) {
57
+ return this.getPositionalArg(0)!;
58
+ }
59
+ return undefined;
60
+ }
61
+
45
62
  getTargetName(): string | undefined {
46
63
  if (!this.hasFlag(CLIOptions.targetName)) {
47
- return undefined;
64
+ return this.hasPositionalArg()
65
+ ? this.cleanTargetName(this.getStringArg()!)
66
+ : undefined;
48
67
  }
49
68
  const argInput = this.getOption(CLIOptions.targetName);
50
69
  return this.cleanTargetName(argInput);
@@ -20,7 +20,8 @@ export class FileService {
20
20
  }
21
21
 
22
22
  toRelativePath(fromPath: string, toPath: string) {
23
- return this.ensurePosix(path.relative(fromPath, toPath));
23
+ const p = this.ensurePosix(path.relative(fromPath, toPath));
24
+ return !p.includes("/") ? `./${p}` : p;
24
25
  }
25
26
 
26
27
  ensurePosix(possiblyWindowsPath: string) {
@@ -63,7 +63,7 @@ export class PromptService {
63
63
  options: { name: string; value: T }[],
64
64
  ): Promise<T[]> {
65
65
  await this.utilService.sleep(800);
66
- return await sortableCheckbox({
66
+ return sortableCheckbox({
67
67
  message,
68
68
  choices: options,
69
69
  });
@@ -1,5 +1,4 @@
1
- import childProcess, { ChildProcess, spawn } from "node:child_process";
2
- import type { ProcessStatus } from "../types/process";
1
+ import childProcess from "node:child_process";
3
2
  import { singleton } from "tsyringe";
4
3
 
5
4
  @singleton()
@@ -12,76 +11,4 @@ export class SpawnService {
12
11
  throw error;
13
12
  }
14
13
  }
15
-
16
- execSync(command: string) {
17
- try {
18
- return childProcess.execSync(command).toString();
19
- } catch (error) {
20
- console.error(`Error executing command: ${command}`);
21
- throw error;
22
- }
23
- }
24
-
25
- exec(command: string) {
26
- try {
27
- return new Promise((res, rej) => {
28
- return childProcess.exec(command, (err, stdout, stderr) => {
29
- if (err) return rej(err);
30
- return res({ stdout, stderr });
31
- });
32
- });
33
- } catch (error) {
34
- console.error(`Error executing command: ${command}`, error);
35
- throw error;
36
- }
37
- }
38
-
39
- backgroundExec(command: string, args: string[] = []): ChildProcess {
40
- const child = spawn(command, args, {
41
- detached: true,
42
- stdio: "ignore",
43
- });
44
-
45
- child.unref();
46
- return child;
47
- }
48
-
49
- isProcessCompleted(pid: number): boolean {
50
- try {
51
- // sending signal 0 checks if the process exists
52
- process.kill(pid, 0);
53
- return false;
54
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
55
- } catch (error) {
56
- return true;
57
- }
58
- }
59
-
60
- kill(pid: number) {
61
- try {
62
- process.kill(pid);
63
- } catch (error) {
64
- throw new Error(
65
- `Failed to kill process with PID ${pid}: ${(error as Error).message}`,
66
- );
67
- }
68
- }
69
-
70
- forceKill(pid: number) {
71
- try {
72
- process.kill(pid, "SIGKILL");
73
- } catch (error) {
74
- throw new Error(
75
- `Failed to forcefully kill process with PID ${pid}: ${(error as Error).message}`,
76
- );
77
- }
78
- }
79
-
80
- async getProcessStatus(pid: number): Promise<ProcessStatus> {
81
- const completed = this.isProcessCompleted(pid);
82
- return {
83
- pid,
84
- status: completed ? "completed" : "running",
85
- };
86
- }
87
14
  }
@@ -51,6 +51,8 @@ export default class CreateNewProject extends AppTask {
51
51
  let projectName: string;
52
52
  if (this.argService.hasFlag(CLIOptions.name)) {
53
53
  projectName = this.argService.getOption<string>(CLIOptions.name);
54
+ } else if (this.argService.hasPositionalArg()) {
55
+ projectName = this.argService.getStringArg()!;
54
56
  } else {
55
57
  projectName = await this.promptService.getInput("Enter Project Name");
56
58
  }
@@ -1,35 +1,23 @@
1
1
  import chalk from "chalk";
2
- import { AppTask } from "../../wrappers/app-task";
3
2
  import { autoInjectable } from "tsyringe";
4
3
  import { AppState, GenTargetType } from "../../types/state";
5
- import { FileService } from "../../services/file-service";
6
- import { UtilService } from "../../services/util-service";
7
4
  import { CaseType } from "../../types/format";
8
5
  import path from "path";
9
6
  import { getCommandTemplate } from "../../templates/command";
10
7
  import { ProjectService } from "../../services/project-service";
11
- import { PromptService } from "../../services/prompt-service";
8
+ import GenerateAppTask from "../../wrappers/gen-app-task";
12
9
 
13
10
  /**
14
11
  * Generates a new command class
15
12
  */
16
13
  @autoInjectable()
17
- export default class GenerateCommand extends AppTask {
14
+ export default class GenerateCommand extends GenerateAppTask {
18
15
  loggerName = "Generate Command";
19
16
 
20
- constructor(
21
- private fileService: FileService,
22
- private utilService: UtilService,
23
- private projectService: ProjectService,
24
- private promptService: PromptService,
25
- ) {
26
- super();
27
- }
28
-
29
17
  /**
30
18
  * Generates a new command class file.
31
19
  *
32
- * @returns updated state with outputDestination set to generated result path.
20
+ * @returns updated state with outputDestination set to a generated result path.
33
21
  */
34
22
  async run(): Promise<void | Partial<AppState>> {
35
23
  const { utilService, fileService, projectService } = this;
@@ -52,9 +40,10 @@ export default class GenerateCommand extends AppTask {
52
40
  );
53
41
  const filename = `${utilService.titleizedToCase(targetName, convention)}${ProjectService.defaults.extention}`;
54
42
 
55
- const taskMappings = new Map();
56
- await this.addTasksToMap(taskMappings);
57
- const rendered = getCommandTemplate(className, taskMappings);
43
+ const injectableMappings = new Map<string, string>();
44
+ await this.addTasksToMap(injectableMappings);
45
+
46
+ const rendered = getCommandTemplate(className, injectableMappings);
58
47
  const destination = path.join(targetDirectory, filename);
59
48
 
60
49
  // write contents
@@ -80,56 +69,4 @@ export default class GenerateCommand extends AppTask {
80
69
  throw new Error("Unable to determine targetDirectory");
81
70
  }
82
71
  }
83
-
84
- /**
85
- * Prompt to select tasks to use in the command being generated.
86
- *
87
- * @param taskMappings the map to add picked tasks to import for the command
88
- */
89
- async addTasksToMap(taskMappings: Map<string, string>) {
90
- if (!this.state.data.project?.taskDestination) {
91
- return;
92
- }
93
-
94
- const taskFiles = this.fileService.getFilesInDir(
95
- this.state.data.project.taskDestination,
96
- );
97
-
98
- this.logger.debug(`Found ${taskFiles.length} task files`);
99
-
100
- // prompt for tasks to use in the new command
101
- const pickedTasks = await this.promptService.pickMultiple(
102
- "Include Tasks",
103
- taskFiles.map((tf) => ({
104
- name: this.utilService.titleizedToCase(
105
- this.utilService.titleizeAll(this.fileService.getFilenameNoExt(tf)),
106
- CaseType.PASCAL_CASE,
107
- ),
108
- value: tf,
109
- })),
110
- );
111
-
112
- this.logger.debug(`Selected ${pickedTasks.length} tasks`);
113
- if (!pickedTasks.length) {
114
- return;
115
- }
116
-
117
- // add them to the map
118
- for (const picked of pickedTasks) {
119
- const filename = this.fileService.getFilenameNoExt(picked);
120
- const taskClassName = this.utilService.titleizedToCase(
121
- this.utilService.titleizeAll(filename),
122
- CaseType.PASCAL_CASE,
123
- );
124
- const relativeSource = this.state.data.project!.commandDestination!;
125
- const relativeDest = picked;
126
- taskMappings.set(
127
- taskClassName,
128
- this.fileService
129
- .toRelativePath(relativeSource, relativeDest)
130
- // remove extension for typescript import
131
- .replace(ProjectService.defaults.extention, ""),
132
- );
133
- }
134
- }
135
72
  }
@@ -1,33 +1,23 @@
1
1
  import chalk from "chalk";
2
- import { AppTask } from "../../wrappers/app-task";
3
2
  import { autoInjectable } from "tsyringe";
4
3
  import { GenTargetType } from "../../types/state";
5
4
  import { ProjectService } from "../../services/project-service";
6
- import { UtilService } from "../../services/util-service";
7
- import { FileService } from "../../services/file-service";
8
5
  import { CaseType } from "../../types/format";
9
6
  import path from "path";
10
7
  import { getServiceTemplate } from "../../templates/service";
8
+ import GenerateAppTask from "../../wrappers/gen-app-task";
11
9
 
12
10
  /**
13
11
  * Generates a new service class
14
12
  */
15
13
  @autoInjectable()
16
- export default class GenerateService extends AppTask {
14
+ export default class GenerateService extends GenerateAppTask {
17
15
  loggerName = "Generate Service";
18
16
 
19
- constructor(
20
- private projectService: ProjectService,
21
- private utilService: UtilService,
22
- private fileService: FileService,
23
- ) {
24
- super();
25
- }
26
-
27
17
  /**
28
18
  * Generates a new service class file.
29
19
  *
30
- * @returns updated state with outputDestination set to generated result path.
20
+ * @returns updated state with outputDestination set to a generated result path.
31
21
  */
32
22
  async run() {
33
23
  if (this.state.data.genTargetType !== GenTargetType.Service) {
@@ -48,7 +38,11 @@ export default class GenerateService extends AppTask {
48
38
  CaseType.PASCAL_CASE,
49
39
  );
50
40
 
51
- const rendered = getServiceTemplate(className);
41
+ const injectableMappings = new Map<string, string>();
42
+ if (this.argService.hasFlag("inject")) {
43
+ await this.addServicesToMap(injectableMappings, targetDirectory);
44
+ }
45
+ const rendered = getServiceTemplate(className, injectableMappings);
52
46
  const filename = `${utilService.titleizedToCase(targetName, convention)}${ProjectService.defaults.extention}`;
53
47
  const destination = path.join(targetDirectory, filename);
54
48
 
@@ -1,33 +1,23 @@
1
1
  import chalk from "chalk";
2
- import { AppTask } from "../../wrappers/app-task";
3
2
  import { autoInjectable } from "tsyringe";
4
3
  import { GenTargetType } from "../../types/state";
5
4
  import { getTaskTemplate } from "../../templates/task";
6
5
  import { CaseType } from "../../types/format";
7
- import { UtilService } from "../../services/util-service";
8
- import { FileService } from "../../services/file-service";
9
6
  import { ProjectService } from "../../services/project-service";
10
7
  import path from "path";
8
+ import GenAppTask from "../../wrappers/gen-app-task";
11
9
 
12
10
  /**
13
11
  * Generates a new task class
14
12
  */
15
13
  @autoInjectable()
16
- export default class GenerateTask extends AppTask {
14
+ export default class GenerateTask extends GenAppTask {
17
15
  loggerName = "Generate Task";
18
16
 
19
- constructor(
20
- private utilService: UtilService,
21
- private fileService: FileService,
22
- private projectService: ProjectService,
23
- ) {
24
- super();
25
- }
26
-
27
17
  /**
28
18
  * Generates a new task class file.
29
19
  *
30
- * @returns updated state with outputDestination set to generated result path.
20
+ * @returns updated state with outputDestination set to a generated result path.
31
21
  */
32
22
  async run() {
33
23
  if (this.state.data.genTargetType !== GenTargetType.Task) {
@@ -49,7 +39,11 @@ export default class GenerateTask extends AppTask {
49
39
  CaseType.PASCAL_CASE,
50
40
  );
51
41
 
52
- const rendered = getTaskTemplate(className);
42
+ const injectableMappings = new Map<string, string>();
43
+ if (this.argService.hasFlag("inject")) {
44
+ await this.addServicesToMap(injectableMappings, targetDirectory);
45
+ }
46
+ const rendered = getTaskTemplate(className, injectableMappings);
53
47
  const filename = `${utilService.titleizedToCase(targetName, convention)}${ProjectService.defaults.extention}`;
54
48
  const destination = path.join(targetDirectory, filename);
55
49
 
@@ -1,3 +1,7 @@
1
+ const nonClashingName = (name: string) => `${name}Task`;
2
+ const nameCheck = (name: string, className: string) =>
3
+ name === className ? nonClashingName(name) : name;
4
+
1
5
  export const getCommandTemplate = (
2
6
  className: string,
3
7
  tasks: Map<string, string> = new Map(),
@@ -7,12 +11,16 @@ import { Command } from "../wrappers/command";
7
11
  ${tasks
8
12
  .keys()
9
13
  .toArray()
10
- .map((k) => `import ${k} from "${tasks.get(k)}";`)
14
+ .map((k) => `import ${nameCheck(k, className)} from "${tasks.get(k)}";`)
11
15
  .join("\n")}
12
16
 
13
17
  @singleton()
14
18
  export class ${className} extends Command {
15
- tasks = [${tasks.keys().toArray().join(", ")}];
19
+ tasks = [${tasks
20
+ .keys()
21
+ .toArray()
22
+ .map((k) => nameCheck(k, className))
23
+ .join(", ")}];
16
24
  }
17
25
 
18
26
  `;
@@ -1,5 +1,11 @@
1
- export const getServiceTemplate = (className: string) =>
2
- `import { singleton } from "tsyringe";
1
+ const camelCase = (s: string) => s.charAt(0).toLowerCase() + s.slice(1);
2
+
3
+ export const getServiceTemplate = (
4
+ className: string,
5
+ injections: Map<string, string>,
6
+ ) => {
7
+ if (!injections || injections.size === 0) {
8
+ return `import { singleton } from "tsyringe";
3
9
 
4
10
  /**
5
11
  * ${className}
@@ -10,3 +16,29 @@ export class ${className} {
10
16
  }
11
17
 
12
18
  `;
19
+ }
20
+
21
+ return `import { singleton } from "tsyringe";
22
+ ${injections
23
+ .keys()
24
+ .toArray()
25
+ .map((k) => `import { ${k} } from "${injections.get(k)}";`)
26
+ .join("\n")}
27
+
28
+
29
+ /**
30
+ * ${className}
31
+ */
32
+ @singleton()
33
+ export class ${className} {
34
+ constructor(
35
+ ${injections
36
+ .keys()
37
+ .toArray()
38
+ .map((k) => `private ${camelCase(k)}: ${k}`)
39
+ .join(",\n ")}
40
+ ) {}
41
+ }
42
+
43
+ `;
44
+ };
@@ -1,6 +1,34 @@
1
- export const getTaskTemplate = (className: string) =>
2
- `import { AppTask } from "../wrappers/app-task";
1
+ const camelCase = (s: string) => s.charAt(0).toLowerCase() + s.slice(1);
2
+
3
+ export const getTaskTemplate = (
4
+ className: string,
5
+ injections: Map<string, string>,
6
+ ) => {
7
+ if (!injections || injections.size === 0) {
8
+ return `import { AppTask } from "../wrappers/app-task";
9
+ import { autoInjectable } from "tsyringe";
10
+
11
+ /**
12
+ * ${className}
13
+ */
14
+ @autoInjectable()
15
+ export default class ${className} extends AppTask {
16
+ loggerName = "${className}";
17
+ async run() {
18
+ // TODO: implement task
19
+ }
20
+ }
21
+
22
+ `;
23
+ }
24
+
25
+ return `import { AppTask } from "../wrappers/app-task";
3
26
  import { autoInjectable } from "tsyringe";
27
+ ${injections
28
+ .keys()
29
+ .toArray()
30
+ .map((k) => `import { ${k} } from "${injections.get(k)}";`)
31
+ .join("\n")}
4
32
 
5
33
  /**
6
34
  * ${className}
@@ -8,9 +36,21 @@ import { autoInjectable } from "tsyringe";
8
36
  @autoInjectable()
9
37
  export default class ${className} extends AppTask {
10
38
  loggerName = "${className}";
39
+
40
+ constructor(
41
+ ${injections
42
+ .keys()
43
+ .toArray()
44
+ .map((k) => `private ${camelCase(k)}: ${k}`)
45
+ .join(",\n ")}
46
+ ) {
47
+ super();
48
+ }
49
+
11
50
  async run() {
12
51
  // TODO: implement task
13
52
  }
14
53
  }
15
54
 
16
55
  `;
56
+ };
@@ -10,3 +10,8 @@ export interface ProjectData {
10
10
  serviceFolders: string[];
11
11
  commandFolders: string[];
12
12
  }
13
+
14
+ export type ProjectImport = {
15
+ importClassName: string;
16
+ importPath: string;
17
+ };
@@ -0,0 +1,150 @@
1
+ import { AppTask } from "./app-task";
2
+ import { autoInjectable } from "tsyringe";
3
+ import { CaseType } from "../types/format";
4
+ import { UtilService } from "../services/util-service";
5
+ import { FileService } from "../services/file-service";
6
+ import { ProjectService } from "../services/project-service";
7
+ import { PromptService } from "../services/prompt-service";
8
+ import { ProjectImport } from "../types/project";
9
+
10
+ /**
11
+ * Generation Task Type
12
+ */
13
+ @autoInjectable()
14
+ export default class GenAppTask extends AppTask {
15
+ constructor(
16
+ protected utilService: UtilService,
17
+ protected fileService: FileService,
18
+ protected projectService: ProjectService,
19
+ protected promptService: PromptService,
20
+ ) {
21
+ super();
22
+ }
23
+
24
+ /**
25
+ * Prompt to select tasks to use in the command being generated.
26
+ *
27
+ * @param taskMappings the map to add picked tasks to import for the command
28
+ */
29
+ async addTasksToMap(taskMappings: Map<string, string>) {
30
+ if (!this.state.data.project?.taskDestination) {
31
+ return;
32
+ }
33
+
34
+ const taskFiles = this.fileService.getFilesInDir(
35
+ this.state.data.project.taskDestination,
36
+ );
37
+
38
+ this.logger.debug(`Found ${taskFiles.length} task files`);
39
+ const pickedTasks = await this.promptForImports("Include Tasks", taskFiles);
40
+
41
+ this.logger.debug(`Selected ${pickedTasks.length} tasks`);
42
+ if (!pickedTasks.length) {
43
+ return;
44
+ }
45
+ // add them to the map
46
+ this.formalizeImports(
47
+ pickedTasks,
48
+ this.state.data.project!.commandDestination!,
49
+ ).forEach((i: ProjectImport) =>
50
+ taskMappings.set(i.importClassName, i.importPath),
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Prompt to select services to use in the command being generated.
56
+ *
57
+ * @param serviceMappings the map to add picked services to import for the command
58
+ * @param sourcePath the path to the parent directory of the source file being
59
+ * generated to resolve relative paths for imports
60
+ */
61
+ async addServicesToMap(
62
+ serviceMappings: Map<string, string>,
63
+ sourcePath: string,
64
+ ) {
65
+ if (!this.state.data.project?.serviceDestination) {
66
+ return;
67
+ }
68
+
69
+ const serviceFiles = this.fileService.getFilesInDir(
70
+ this.state.data.project.serviceDestination,
71
+ );
72
+
73
+ this.logger.debug(`Found ${serviceFiles.length} service files`);
74
+ const pickedServices = await this.promptForImports(
75
+ "Include Services",
76
+ serviceFiles,
77
+ );
78
+
79
+ this.logger.debug(`Selected ${pickedServices.length} services`);
80
+ if (!pickedServices.length) {
81
+ return;
82
+ }
83
+ // add them to the map
84
+ this.formalizeImports(pickedServices, sourcePath).forEach(
85
+ (i: ProjectImport) =>
86
+ serviceMappings.set(i.importClassName, i.importPath),
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Formalize the selected imports into a format that can be used in the source code being generated.
92
+ *
93
+ * 1. Generate a PascalCased import name from the filename
94
+ * 2. Convert to a relative path
95
+ * 3. Remove the file extension for TypeScript import
96
+ *
97
+ * @param selectedPaths the selected file paths to formalize
98
+ * @param relativeSource the path to the source file being generated to resolve relative paths
99
+ * @returns the formalized ProjectImport array ({ importClassName, importPath })
100
+ */
101
+ private formalizeImports(
102
+ selectedPaths: string[],
103
+ relativeSource: string,
104
+ ): ProjectImport[] {
105
+ if (!selectedPaths?.length) {
106
+ return [];
107
+ }
108
+ const formalizedPaths: ProjectImport[] = [];
109
+ for (const fullPath of selectedPaths) {
110
+ const filename = this.fileService.getFilenameNoExt(fullPath);
111
+ const importClassName = this.utilService.titleizedToCase(
112
+ this.utilService.titleizeAll(filename),
113
+ CaseType.PASCAL_CASE,
114
+ );
115
+
116
+ formalizedPaths.push({
117
+ importClassName,
118
+ importPath: this.fileService
119
+ .toRelativePath(relativeSource, fullPath)
120
+ .replace(ProjectService.defaults.extention, ""),
121
+ });
122
+ }
123
+ return formalizedPaths;
124
+ }
125
+
126
+ /**
127
+ * Prompt to select imports to use in the class being generated.
128
+ *
129
+ * @param messagePrompt the prompt message to display
130
+ * @param filePaths the file paths to select from
131
+ * @returns the selected file paths
132
+ */
133
+ private async promptForImports(
134
+ messagePrompt: string,
135
+ filePaths: string[],
136
+ ): Promise<string[]> {
137
+ return await this.promptService.pickMultiple(
138
+ messagePrompt,
139
+ filePaths.map((filePath: string) => ({
140
+ name: this.utilService.titleizedToCase(
141
+ this.utilService.titleizeAll(
142
+ this.fileService.getFilenameNoExt(filePath),
143
+ ),
144
+ CaseType.PASCAL_CASE,
145
+ ),
146
+ value: filePath,
147
+ })),
148
+ );
149
+ }
150
+ }