task-script-support-cli 0.1.1

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 (151) hide show
  1. package/.prettierignore +3 -0
  2. package/.prettierrc +1 -0
  3. package/.vscode/launch.json +34 -0
  4. package/README.md +3 -0
  5. package/dist/eslint.config.d.ts +3 -0
  6. package/dist/eslint.config.d.ts.map +1 -0
  7. package/dist/eslint.config.js +16 -0
  8. package/dist/eslint.config.js.map +1 -0
  9. package/dist/package.json +55 -0
  10. package/dist/src/commands/about.d.ts +7 -0
  11. package/dist/src/commands/about.d.ts.map +1 -0
  12. package/dist/src/commands/about.js +24 -0
  13. package/dist/src/commands/about.js.map +1 -0
  14. package/dist/src/commands/gen.d.ts +12 -0
  15. package/dist/src/commands/gen.d.ts.map +1 -0
  16. package/dist/src/commands/gen.js +35 -0
  17. package/dist/src/commands/gen.js.map +1 -0
  18. package/dist/src/index.d.ts +2 -0
  19. package/dist/src/index.d.ts.map +1 -0
  20. package/dist/src/index.js +34 -0
  21. package/dist/src/index.js.map +1 -0
  22. package/dist/src/services/arg-service.d.ts +10 -0
  23. package/dist/src/services/arg-service.d.ts.map +1 -0
  24. package/dist/src/services/arg-service.js +71 -0
  25. package/dist/src/services/arg-service.js.map +1 -0
  26. package/dist/src/services/banner-service.d.ts +8 -0
  27. package/dist/src/services/banner-service.d.ts.map +1 -0
  28. package/dist/src/services/banner-service.js +50 -0
  29. package/dist/src/services/banner-service.js.map +1 -0
  30. package/dist/src/services/file-service.d.ts +12 -0
  31. package/dist/src/services/file-service.d.ts.map +1 -0
  32. package/dist/src/services/file-service.js +82 -0
  33. package/dist/src/services/file-service.js.map +1 -0
  34. package/dist/src/services/log-service.d.ts +14 -0
  35. package/dist/src/services/log-service.d.ts.map +1 -0
  36. package/dist/src/services/log-service.js +61 -0
  37. package/dist/src/services/log-service.js.map +1 -0
  38. package/dist/src/services/project-service.d.ts +31 -0
  39. package/dist/src/services/project-service.d.ts.map +1 -0
  40. package/dist/src/services/project-service.js +86 -0
  41. package/dist/src/services/project-service.js.map +1 -0
  42. package/dist/src/services/spawn-service.d.ts +12 -0
  43. package/dist/src/services/spawn-service.d.ts.map +1 -0
  44. package/dist/src/services/spawn-service.js +111 -0
  45. package/dist/src/services/spawn-service.js.map +1 -0
  46. package/dist/src/services/updater-service.d.ts +7 -0
  47. package/dist/src/services/updater-service.d.ts.map +1 -0
  48. package/dist/src/services/updater-service.js +24 -0
  49. package/dist/src/services/updater-service.js.map +1 -0
  50. package/dist/src/services/util-service.d.ts +23 -0
  51. package/dist/src/services/util-service.d.ts.map +1 -0
  52. package/dist/src/services/util-service.js +89 -0
  53. package/dist/src/services/util-service.js.map +1 -0
  54. package/dist/src/tasks/check-env.d.ts +44 -0
  55. package/dist/src/tasks/check-env.d.ts.map +1 -0
  56. package/dist/src/tasks/check-env.js +126 -0
  57. package/dist/src/tasks/check-env.js.map +1 -0
  58. package/dist/src/tasks/generate-command.d.ts +23 -0
  59. package/dist/src/tasks/generate-command.d.ts.map +1 -0
  60. package/dist/src/tasks/generate-command.js +91 -0
  61. package/dist/src/tasks/generate-command.js.map +1 -0
  62. package/dist/src/tasks/generate-service.d.ts +26 -0
  63. package/dist/src/tasks/generate-service.d.ts.map +1 -0
  64. package/dist/src/tasks/generate-service.js +88 -0
  65. package/dist/src/tasks/generate-service.js.map +1 -0
  66. package/dist/src/tasks/generate-task.d.ts +26 -0
  67. package/dist/src/tasks/generate-task.d.ts.map +1 -0
  68. package/dist/src/tasks/generate-task.js +88 -0
  69. package/dist/src/tasks/generate-task.js.map +1 -0
  70. package/dist/src/tasks/print-about-information.d.ts +8 -0
  71. package/dist/src/tasks/print-about-information.d.ts.map +1 -0
  72. package/dist/src/tasks/print-about-information.js +30 -0
  73. package/dist/src/tasks/print-about-information.js.map +1 -0
  74. package/dist/src/tasks/print-banner.d.ts +12 -0
  75. package/dist/src/tasks/print-banner.d.ts.map +1 -0
  76. package/dist/src/tasks/print-banner.js +47 -0
  77. package/dist/src/tasks/print-banner.js.map +1 -0
  78. package/dist/src/tasks/print-generated-results.d.ts +12 -0
  79. package/dist/src/tasks/print-generated-results.d.ts.map +1 -0
  80. package/dist/src/tasks/print-generated-results.js +48 -0
  81. package/dist/src/tasks/print-generated-results.js.map +1 -0
  82. package/dist/src/tasks/select-gen-target.d.ts +9 -0
  83. package/dist/src/tasks/select-gen-target.d.ts.map +1 -0
  84. package/dist/src/tasks/select-gen-target.js +44 -0
  85. package/dist/src/tasks/select-gen-target.js.map +1 -0
  86. package/dist/src/templates/command.d.ts +2 -0
  87. package/dist/src/templates/command.d.ts.map +1 -0
  88. package/dist/src/templates/command.js +19 -0
  89. package/dist/src/templates/command.js.map +1 -0
  90. package/dist/src/templates/service.d.ts +2 -0
  91. package/dist/src/templates/service.d.ts.map +1 -0
  92. package/dist/src/templates/service.js +16 -0
  93. package/dist/src/templates/service.js.map +1 -0
  94. package/dist/src/templates/task.d.ts +2 -0
  95. package/dist/src/templates/task.d.ts.map +1 -0
  96. package/dist/src/templates/task.js +20 -0
  97. package/dist/src/templates/task.js.map +1 -0
  98. package/dist/src/types/format.d.ts +7 -0
  99. package/dist/src/types/format.d.ts.map +1 -0
  100. package/dist/src/types/format.js +11 -0
  101. package/dist/src/types/format.js.map +1 -0
  102. package/dist/src/types/process.d.ts +16 -0
  103. package/dist/src/types/process.d.ts.map +1 -0
  104. package/dist/src/types/process.js +12 -0
  105. package/dist/src/types/process.js.map +1 -0
  106. package/dist/src/types/state.d.ts +26 -0
  107. package/dist/src/types/state.d.ts.map +1 -0
  108. package/dist/src/types/state.js +17 -0
  109. package/dist/src/types/state.js.map +1 -0
  110. package/dist/src/wrappers/app-task.d.ts +19 -0
  111. package/dist/src/wrappers/app-task.d.ts.map +1 -0
  112. package/dist/src/wrappers/app-task.js +61 -0
  113. package/dist/src/wrappers/app-task.js.map +1 -0
  114. package/dist/src/wrappers/command.d.ts +11 -0
  115. package/dist/src/wrappers/command.d.ts.map +1 -0
  116. package/dist/src/wrappers/command.js +36 -0
  117. package/dist/src/wrappers/command.js.map +1 -0
  118. package/dist/tssc +3 -0
  119. package/eslint.config.ts +11 -0
  120. package/install-link.sh +21 -0
  121. package/package.json +55 -0
  122. package/setup.md +37 -0
  123. package/src/commands/about.ts +9 -0
  124. package/src/commands/gen.ts +20 -0
  125. package/src/index.ts +38 -0
  126. package/src/services/arg-service.ts +52 -0
  127. package/src/services/banner-service.ts +42 -0
  128. package/src/services/file-service.ts +78 -0
  129. package/src/services/log-service.ts +43 -0
  130. package/src/services/project-service.ts +72 -0
  131. package/src/services/spawn-service.ts +73 -0
  132. package/src/services/updater-service.ts +9 -0
  133. package/src/services/util-service.ts +87 -0
  134. package/src/tasks/check-env.ts +125 -0
  135. package/src/tasks/generate-command.ts +87 -0
  136. package/src/tasks/generate-service.ts +82 -0
  137. package/src/tasks/generate-task.ts +83 -0
  138. package/src/tasks/print-about-information.ts +16 -0
  139. package/src/tasks/print-banner.ts +37 -0
  140. package/src/tasks/print-generated-results.ts +36 -0
  141. package/src/tasks/select-gen-target.ts +29 -0
  142. package/src/templates/command.ts +18 -0
  143. package/src/templates/service.ts +12 -0
  144. package/src/templates/task.ts +16 -0
  145. package/src/types/format.ts +6 -0
  146. package/src/types/process.ts +18 -0
  147. package/src/types/state.ts +29 -0
  148. package/src/wrappers/app-task.ts +48 -0
  149. package/src/wrappers/command.ts +21 -0
  150. package/tsconfig.json +27 -0
  151. package/tssc +3 -0
@@ -0,0 +1,78 @@
1
+ import chalk from "chalk";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { singleton } from "tsyringe";
5
+
6
+ const ignoredDirectories = new Set(["node_modules", "dist"]);
7
+
8
+ @singleton()
9
+ export default class FileService {
10
+ getRunnerDir(): string {
11
+ return process.cwd();
12
+ }
13
+
14
+ getFilesInDir(dirPath: string): string[] {
15
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
16
+ throw new Error(`Directory not found: ${dirPath}`);
17
+ }
18
+ return fs.readdirSync(dirPath);
19
+ }
20
+
21
+ getRunnerRootDir(): string {
22
+ let currentDir = this.getRunnerDir();
23
+ while (currentDir !== "/") {
24
+ if (fs.existsSync(path.join(currentDir, "package.json"))) {
25
+ return currentDir;
26
+ }
27
+ currentDir = path.dirname(currentDir);
28
+ }
29
+ throw new Error(
30
+ `Unable to find project root! ${chalk.dim("(missing package.json?)")}`,
31
+ );
32
+ }
33
+
34
+ getTaskDirs(startPath: string): string[] {
35
+ return this.findDirsByName(startPath, "tasks");
36
+ }
37
+
38
+ getServiceDirs(startPath: string): string[] {
39
+ return this.findDirsByName(startPath, "services");
40
+ }
41
+
42
+ getCommandDirs(startPath: string): string[] {
43
+ return this.findDirsByName(startPath, "commands");
44
+ }
45
+
46
+ writeFile(filePath: string, content: string): void {
47
+ const dirPath = path.dirname(filePath);
48
+ if (!fs.existsSync(dirPath)) {
49
+ throw new Error(`Directory not found: ${chalk.magentaBright(dirPath)}`);
50
+ }
51
+ if (fs.existsSync(filePath)) {
52
+ throw new Error(`File already exists: ${chalk.magentaBright(filePath)}`);
53
+ }
54
+ fs.writeFileSync(filePath, content, "utf-8");
55
+ }
56
+
57
+ private findDirsByName(dirPath: string, dirNameSuffix: string): string[] {
58
+ const dirs: string[] = [];
59
+ this.traverseDir(dirPath, (dir) => {
60
+ if (path.basename(dir).includes(dirNameSuffix) && !dirs.includes(dir)) {
61
+ dirs.push(dir);
62
+ }
63
+ });
64
+ return dirs;
65
+ }
66
+
67
+ private traverseDir(dirPath: string, callback: (dir: string) => void) {
68
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
69
+ for (const entry of entries) {
70
+ const fullPath = path.join(dirPath, entry.name);
71
+ if (entry.isDirectory() && !ignoredDirectories.has(entry.name)) {
72
+ this.traverseDir(fullPath, callback);
73
+ } else {
74
+ callback(path.dirname(fullPath));
75
+ }
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,43 @@
1
+ import { inject, injectable } from "tsyringe";
2
+ import { PinoLogger } from "task-script-support";
3
+ import pino from "pino";
4
+
5
+ @injectable()
6
+ export class LogService {
7
+ logger: pino.Logger;
8
+
9
+ static ParentInstance?: pino.Logger;
10
+
11
+ constructor(@inject("PinoLogger") private pinoLogger: PinoLogger) {
12
+ if (!LogService.ParentInstance) {
13
+ this.logger = pinoLogger.getLogger();
14
+ LogService.ParentInstance = this.logger;
15
+ } else {
16
+ this.logger = LogService.ParentInstance.child({});
17
+ }
18
+ }
19
+
20
+ setPrefix(prefix: string) {
21
+ this.logger = this.pinoLogger.getLogger(prefix);
22
+ }
23
+
24
+ public info(msg: string, obj?: unknown): void {
25
+ this.logger.info(obj, msg);
26
+ }
27
+
28
+ public error(err: Error | string, msg?: string, obj?: unknown): void {
29
+ if (err instanceof Error) {
30
+ this.logger.error({ err, ...(obj ? obj : {}) }, msg || "Error occurred");
31
+ } else {
32
+ this.logger.error(obj, `${msg}: ${err}`);
33
+ }
34
+ }
35
+
36
+ public warn(msg: string, obj?: unknown): void {
37
+ this.logger.warn(obj, msg);
38
+ }
39
+
40
+ public debug(msg: string, obj?: unknown): void {
41
+ this.logger.debug(obj, msg);
42
+ }
43
+ }
@@ -0,0 +1,72 @@
1
+ import { autoInjectable } from "tsyringe";
2
+ import FileService from "./file-service";
3
+ import { CaseType } from "../types/format";
4
+ import { UtilService } from "./util-service";
5
+
6
+ @autoInjectable()
7
+ export default class ProjectService {
8
+ static defaults = {
9
+ targetName: "Foo-Bar-Baz",
10
+ sampleFile: "test-file.ts",
11
+ convention: CaseType.PASCAL_CASE,
12
+ extention: ".ts",
13
+ };
14
+
15
+ constructor(
16
+ private fileService: FileService,
17
+ private utilService: UtilService,
18
+ ) {}
19
+
20
+ /**
21
+ * Get all task files found in the project
22
+ *
23
+ * @returns a string[] of task filenames found in the project the script was executed in.
24
+ */
25
+ getTaskFiles() {
26
+ const runnerRoot = this.fileService.getRunnerRootDir();
27
+ const taskFolders = this.fileService.getTaskDirs(runnerRoot);
28
+
29
+ // return available task filenames
30
+ return taskFolders.flatMap((f) => this.fileService.getFilesInDir(f));
31
+ }
32
+
33
+ private getSourceFiles() {
34
+ const runnerRoot = this.fileService.getRunnerRootDir();
35
+
36
+ return [
37
+ ...this.fileService
38
+ .getTaskDirs(runnerRoot)
39
+ .flatMap((dirPath) => this.fileService.getFilesInDir(dirPath)),
40
+ ...this.fileService
41
+ .getCommandDirs(runnerRoot)
42
+ .flatMap((dirPath) => this.fileService.getFilesInDir(dirPath)),
43
+ ...this.fileService
44
+ .getServiceDirs(runnerRoot)
45
+ .flatMap((dirPath) => this.fileService.getFilesInDir(dirPath)),
46
+ ];
47
+ }
48
+
49
+ /**
50
+ * Looks for a naming convention on the given filename array. Returns
51
+ * the first found convention or the default one if it can't be detected.
52
+ *
53
+ * @param existingFiles the files to check for a convention on.
54
+ * @returns
55
+ */
56
+ getConvention(): CaseType {
57
+ const existingFiles: string[] = this.getSourceFiles();
58
+
59
+ if (!existingFiles || !existingFiles.length)
60
+ return ProjectService.defaults.convention;
61
+ let convention: CaseType | null = null;
62
+ for (const file of existingFiles) {
63
+ convention = this.utilService.detectCase(file);
64
+ if (convention !== null) {
65
+ break;
66
+ }
67
+ }
68
+ return convention || ProjectService.defaults.convention;
69
+ }
70
+ }
71
+
72
+ export type ProjectDefaultsKey = keyof typeof ProjectService.defaults;
@@ -0,0 +1,73 @@
1
+ import childProcess, { ChildProcess, spawn } from "node:child_process";
2
+ import type { ProcessStatus } from "../types/process";
3
+ import { singleton } from "tsyringe";
4
+
5
+ @singleton()
6
+ export class SpawnService {
7
+ execSync(command: string) {
8
+ try {
9
+ return childProcess.execSync(command).toString();
10
+ } catch (error) {
11
+ console.error(`Error executing command: ${command}`);
12
+ throw error;
13
+ }
14
+ }
15
+
16
+ exec(command: string) {
17
+ try {
18
+ return childProcess.exec(command).toString();
19
+ } catch (error) {
20
+ console.error(`Error executing command: ${command}`, error);
21
+ throw error;
22
+ }
23
+ }
24
+
25
+ backgroundExec(command: string, args: string[] = []): ChildProcess {
26
+ const child = spawn(command, args, {
27
+ detached: true,
28
+ stdio: "ignore",
29
+ });
30
+
31
+ child.unref();
32
+ return child;
33
+ }
34
+
35
+ isProcessCompleted(pid: number): boolean {
36
+ try {
37
+ // sending signal 0 checks if the process exists
38
+ process.kill(pid, 0);
39
+ return false;
40
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
41
+ } catch (error) {
42
+ return true;
43
+ }
44
+ }
45
+
46
+ kill(pid: number) {
47
+ try {
48
+ process.kill(pid);
49
+ } catch (error) {
50
+ throw new Error(
51
+ `Failed to kill process with PID ${pid}: ${(error as Error).message}`,
52
+ );
53
+ }
54
+ }
55
+
56
+ forceKill(pid: number) {
57
+ try {
58
+ process.kill(pid, "SIGKILL");
59
+ } catch (error) {
60
+ throw new Error(
61
+ `Failed to forcefully kill process with PID ${pid}: ${(error as Error).message}`,
62
+ );
63
+ }
64
+ }
65
+
66
+ async getProcessStatus(pid: number): Promise<ProcessStatus> {
67
+ const completed = this.isProcessCompleted(pid);
68
+ return {
69
+ pid,
70
+ status: completed ? "completed" : "running",
71
+ };
72
+ }
73
+ }
@@ -0,0 +1,9 @@
1
+ import { singleton } from "tsyringe";
2
+
3
+ /**
4
+ * UpdaterService
5
+ */
6
+ @singleton()
7
+ export default class UpdaterService {
8
+ constructor() {}
9
+ }
@@ -0,0 +1,87 @@
1
+ import { singleton, container } from "tsyringe";
2
+ import pkgJson from "../../package.json";
3
+ import { CommandService, PinoLogger } from "task-script-support";
4
+ import { AppStateData } from "../types/state";
5
+ import { CLIArg } from "../types/process";
6
+ import { CaseType } from "../types/format";
7
+
8
+ @singleton()
9
+ export class UtilService {
10
+ static TEXT_BREAKPONT: RegExp = /[. _-]/g;
11
+
12
+ /**
13
+ * Register external dependencies as injectables
14
+ */
15
+ static initializeDependencies() {
16
+ UtilService.registerCommandService();
17
+ UtilService.registerPinoService();
18
+ }
19
+
20
+ static titleize = (s: string) =>
21
+ s ? `${s[0]?.toUpperCase()}${s.slice(1, s.length)}` : s;
22
+ titleize = (s: string) => UtilService.titleize(s);
23
+ titleizeAll = (s: string) => UtilService.titleizeAll(s);
24
+ static titleizeAll = (str: string) =>
25
+ str
26
+ .trim()
27
+ .split(UtilService.TEXT_BREAKPONT)
28
+ .map(UtilService.titleize)
29
+ .join(" ");
30
+
31
+ static getAppName = () => pkgJson.name;
32
+ getAppName = () => pkgJson.name;
33
+
34
+ static getAppVersion = () => pkgJson.version;
35
+ getAppVersion = () => pkgJson.version;
36
+
37
+ static getAppDescription = () => pkgJson.description;
38
+ getAppDescription = () => pkgJson.description;
39
+
40
+ static registerCommandService() {
41
+ container.registerSingleton(CommandService<AppStateData, CLIArg[]>);
42
+
43
+ // configure args provider
44
+ const command = container.resolve(CommandService<AppStateData, CLIArg[]>);
45
+ command.argsProvider = (...cliArgs: unknown[]) => {
46
+ const args = command.argsProvider_Commander(...cliArgs);
47
+ container.registerInstance("Args", args);
48
+ return args;
49
+ };
50
+ }
51
+
52
+ static registerPinoService() {
53
+ container.registerInstance("PinoLogger", new PinoLogger());
54
+ }
55
+
56
+ titleizedToCase(str: string, targetCase: CaseType) {
57
+ switch (targetCase) {
58
+ case CaseType.KEBAB_CASE:
59
+ return str.toLowerCase().replace(UtilService.TEXT_BREAKPONT, "-");
60
+ case CaseType.SNAKE_CASE:
61
+ return str.toLowerCase().replace(UtilService.TEXT_BREAKPONT, "_");
62
+ case CaseType.CAMEL_CASE:
63
+ return `${str[0]?.toLowerCase()}${str.slice(1).replace(UtilService.TEXT_BREAKPONT, "")}`;
64
+ case CaseType.PASCAL_CASE:
65
+ return str.replace(UtilService.TEXT_BREAKPONT, "");
66
+ default:
67
+ throw new Error("Unsupported target case type.");
68
+ }
69
+ }
70
+
71
+ detectCase(filename: string): CaseType | null {
72
+ if (/^[A-Z][a-zA-Z]*[.](ts|js)$/.test(filename)) {
73
+ return CaseType.PASCAL_CASE;
74
+ }
75
+ if (/^[a-z]+(-[a-z]+)+[.](ts|js)$/.test(filename)) {
76
+ return CaseType.KEBAB_CASE;
77
+ }
78
+ if (/^[a-z]+(_[a-z]+)+[.](ts|js)$/.test(filename)) {
79
+ return CaseType.SNAKE_CASE;
80
+ }
81
+ if (/^[a-z]+([A-Z][a-z]*)*[.](ts|js)$/.test(filename)) {
82
+ return CaseType.CAMEL_CASE;
83
+ }
84
+
85
+ return null;
86
+ }
87
+ }
@@ -0,0 +1,125 @@
1
+ import chalk from "chalk";
2
+ import { AppTask } from "../wrappers/app-task";
3
+ import { autoInjectable } from "tsyringe";
4
+ import { EnvironmentConfigKeys } from "../types/state";
5
+ import FileService from "../services/file-service";
6
+ import path from "path";
7
+
8
+ /**
9
+ * Checks the environment configuration
10
+ */
11
+ @autoInjectable()
12
+ export default class CheckEnvironment extends AppTask {
13
+ loggerName = "Check Environment";
14
+
15
+ requiredEnvVars: string[] = [];
16
+
17
+ optionalEnvVars = [
18
+ EnvironmentConfigKeys.NODE_ENV,
19
+ EnvironmentConfigKeys.PINO_LOG_TARGET,
20
+ EnvironmentConfigKeys.PINO_LOG_DIR_PATH,
21
+ EnvironmentConfigKeys.PINO_LOG_FILENAME,
22
+ EnvironmentConfigKeys.PINO_LOG_LEVEL,
23
+ ];
24
+
25
+ constructor(private fileService: FileService) {
26
+ super();
27
+ }
28
+
29
+ async run() {
30
+ this.logger.info(chalk.blueBright("Running Check Environment"));
31
+
32
+ const envVars = [...this.optionalEnvVars, ...this.requiredEnvVars];
33
+ this.getReadMessages(envVars).forEach((m) => this.logger.debug(m));
34
+ const errors: string[] = this.getErrorMessages(this.requiredEnvVars);
35
+ this.checkToExit(errors);
36
+
37
+ try {
38
+ const projectDir = this.fileService.getRunnerRootDir();
39
+ this.checkProjectDependencies(projectDir, errors);
40
+ this.checkProjectDirectories(projectDir, errors);
41
+ } catch (err) {
42
+ errors.push(`Unexpected error checking project: ${err}`);
43
+ }
44
+
45
+ this.checkToExit(errors);
46
+ this.setData({ environmentValidated: true });
47
+ }
48
+
49
+ /**
50
+ * Check for the folders we need to generate files in. Add errors
51
+ * when no target directories can be found.
52
+ *
53
+ * @param projectDir the target project root directory path
54
+ * @param errors string array error messages are added to
55
+ */
56
+ checkProjectDirectories(projectDir: string, errors: string[]) {
57
+ const directories = {
58
+ taskFolders: this.fileService.getTaskDirs(projectDir),
59
+ serviceFolders: this.fileService.getServiceDirs(projectDir),
60
+ commandFolders: this.fileService.getCommandDirs(projectDir),
61
+ };
62
+ Object.keys(directories).forEach((k: string) => {
63
+ const key: keyof typeof directories = k as keyof typeof directories;
64
+ if (!directories[key] || !directories[key].length) {
65
+ errors.push(`Unable to find any ${k} in project`);
66
+ }
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Check that the project has the right dependencies installed. Adds
72
+ * to the errors array when they can't be found.
73
+ *
74
+ * @param projectDir the target project root directory path
75
+ * @param errors string array error messages are added to
76
+ */
77
+ checkProjectDependencies(projectDir: string, errors: string[]) {
78
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
79
+ const packagePath = require(path.join(projectDir, "package.json"));
80
+ const requireDeps = ["task-script-support", "tsyringe"];
81
+ requireDeps.forEach((d) => {
82
+ if (!Object.keys(packagePath.dependencies || {}).includes(d)) {
83
+ errors.push(`Missing required dependencies in project: '${d}'`);
84
+ }
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Log and exit program if errors are present.
90
+ *
91
+ * @param errors the error messages array to check
92
+ */
93
+ private checkToExit(errors: string[]) {
94
+ if (errors.length) {
95
+ this.logEnvErrors(errors);
96
+ process.exit(1);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Logs any error messages in the provided array.
102
+ *
103
+ * @param errors the error messages array to log
104
+ */
105
+ private logEnvErrors(errors: string[]): void {
106
+ if (errors.length) {
107
+ this.logger.error(
108
+ new Error("Misconfigured Environment"),
109
+ chalk.red(errors.join("\n")),
110
+ );
111
+ }
112
+ }
113
+
114
+ private getReadMessages = (allEnvVars: string[]) =>
115
+ allEnvVars
116
+ .filter((e) => !!process.env[e])
117
+ .map(
118
+ (e) => `Read value for ${e}: ${chalk.magentaBright(process.env[e])}`,
119
+ );
120
+
121
+ private getErrorMessages = (reqEnvVars: string[]) =>
122
+ reqEnvVars
123
+ .filter((e) => !process.env[e])
124
+ .map((e) => `Missing required environment variable ${e}`);
125
+ }
@@ -0,0 +1,87 @@
1
+ import chalk from "chalk";
2
+ import { AppTask } from "../wrappers/app-task";
3
+ import { autoInjectable } from "tsyringe";
4
+ import { AppState, GenTargetType } from "../types/state";
5
+ import FileService from "../services/file-service";
6
+ import { UtilService } from "../services/util-service";
7
+ import { CaseType } from "../types/format";
8
+ import path from "path";
9
+ import { getCommandTemplate } from "../templates/command";
10
+ import ProjectService from "../services/project-service";
11
+
12
+ /**
13
+ * Generates a new command class
14
+ */
15
+ @autoInjectable()
16
+ export default class GenerateCommand extends AppTask {
17
+ loggerName = "Generate Command";
18
+
19
+ constructor(
20
+ private fileService: FileService,
21
+ private utilService: UtilService,
22
+ private projectService: ProjectService,
23
+ ) {
24
+ super();
25
+ }
26
+
27
+ async run(): Promise<void | Partial<AppState>> {
28
+ const { utilService, fileService, argService, projectService } = this;
29
+
30
+ if (this.state.data.genTargetType !== GenTargetType.Command) {
31
+ return;
32
+ }
33
+
34
+ this.logger.info(chalk.blueBright("Generating Command"));
35
+
36
+ let targetName = argService.getTargetName();
37
+ if (!targetName) {
38
+ // TODO: Should we prompt for target name here?
39
+ targetName = argService.cleanTargetName(
40
+ ProjectService.defaults.targetName,
41
+ );
42
+ }
43
+
44
+ const targetDirectory = this.getTargetDirectory();
45
+ if (!targetDirectory) {
46
+ throw new Error("Unable to determine target Directory");
47
+ }
48
+
49
+ // detect case between commands and task files
50
+ const taskFiles = projectService.getTaskFiles();
51
+ const convention = projectService.getConvention();
52
+ this.logger.debug(`Using convention: ${chalk.magentaBright(convention)}`);
53
+
54
+ this.logger.debug(`Found ${taskFiles.length} task files`);
55
+ const className = utilService.titleizedToCase(
56
+ targetName,
57
+ CaseType.PASCAL_CASE,
58
+ );
59
+ // TODO: prompt for tasks to use in the new command?
60
+ const taskMappings = new Map();
61
+
62
+ const rendered = getCommandTemplate(className, taskMappings);
63
+ const filename = `${utilService.titleizedToCase(targetName, convention)}${ProjectService.defaults.extention}`;
64
+ const destination = path.join(targetDirectory, filename);
65
+
66
+ // write contents
67
+ fileService.writeFile(destination, rendered);
68
+ return { data: { outputDestination: destination } };
69
+ }
70
+
71
+ /**
72
+ * Get the target directory to save the generated file.
73
+ *
74
+ * @returns the destination path of the parent folder to save the new file in.
75
+ */
76
+ getTargetDirectory() {
77
+ const runnerRoot = this.fileService.getRunnerRootDir();
78
+ const commandFolders = this.fileService.getCommandDirs(runnerRoot);
79
+
80
+ if (commandFolders.length === 1) {
81
+ return commandFolders[0];
82
+ }
83
+
84
+ // TODO: prompt to select destination from available commandFolders
85
+ return commandFolders[0];
86
+ }
87
+ }
@@ -0,0 +1,82 @@
1
+ import chalk from "chalk";
2
+ import { AppTask } from "../wrappers/app-task";
3
+ import { autoInjectable } from "tsyringe";
4
+ import { GenTargetType } from "../types/state";
5
+ import ProjectService from "../services/project-service";
6
+ import { UtilService } from "../services/util-service";
7
+ import FileService from "../services/file-service";
8
+ import { CaseType } from "../types/format";
9
+ import path from "path";
10
+ import { getServiceTemplate } from "../templates/service";
11
+
12
+ /**
13
+ * Generates a new service class
14
+ */
15
+ @autoInjectable()
16
+ export default class GenerateService extends AppTask {
17
+ loggerName = "Generate Service";
18
+
19
+ constructor(
20
+ private projectService: ProjectService,
21
+ private utilService: UtilService,
22
+ private fileService: FileService,
23
+ ) {
24
+ super();
25
+ }
26
+
27
+ async run() {
28
+ if (this.state.data.genTargetType !== GenTargetType.Service) {
29
+ return;
30
+ }
31
+
32
+ this.logger.info(chalk.blueBright("Generating Service"));
33
+ const { argService, utilService, projectService } = this;
34
+
35
+ let targetName = argService.getTargetName();
36
+ if (!targetName) {
37
+ // TODO: Prompt for target name?
38
+ targetName = argService.cleanTargetName(
39
+ ProjectService.defaults.targetName,
40
+ );
41
+ }
42
+
43
+ const targetDirectory = this.getTargetDirectory();
44
+ if (!targetDirectory) {
45
+ throw new Error("Unable to determine target Directory");
46
+ }
47
+
48
+ // detect case
49
+ const convention = projectService.getConvention();
50
+ this.logger.debug(`Using convention: ${chalk.magentaBright(convention)}`);
51
+ const className = utilService.titleizedToCase(
52
+ targetName,
53
+ CaseType.PASCAL_CASE,
54
+ );
55
+
56
+ const rendered = getServiceTemplate(className);
57
+ const filename = `${utilService.titleizedToCase(targetName, convention)}${ProjectService.defaults.extention}`;
58
+ const destination = path.join(targetDirectory, filename);
59
+
60
+ // write contents
61
+ this.fileService.writeFile(destination, rendered);
62
+ return { data: { outputDestination: destination } };
63
+ }
64
+
65
+ /**
66
+ * Get the target directory to save the generated file.
67
+ *
68
+ * @returns the destination path of the parent folder to save the new file in.
69
+ */
70
+ getTargetDirectory() {
71
+ const runnerRoot = this.fileService.getRunnerRootDir();
72
+ const servicesFolders = this.fileService.getServiceDirs(runnerRoot);
73
+
74
+ if (servicesFolders.length === 1) {
75
+ return servicesFolders[0];
76
+ }
77
+
78
+ // TODO: prompt to select destination from available servicesFolders
79
+ this.logger.warn(`Multiple service folders detected. Using first found.`);
80
+ return servicesFolders[0];
81
+ }
82
+ }