@expressots/cli 3.0.0 → 4.0.0-preview.3

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 (194) hide show
  1. package/README.md +41 -95
  2. package/bin/cicd/cli.d.ts +6 -0
  3. package/bin/cicd/cli.js +128 -0
  4. package/bin/cicd/form.d.ts +29 -0
  5. package/bin/cicd/form.js +346 -0
  6. package/bin/cicd/generators/azure-devops.d.ts +2 -0
  7. package/bin/cicd/generators/azure-devops.js +370 -0
  8. package/bin/cicd/generators/bitbucket.d.ts +2 -0
  9. package/bin/cicd/generators/bitbucket.js +217 -0
  10. package/bin/cicd/generators/circleci.d.ts +2 -0
  11. package/bin/cicd/generators/circleci.js +274 -0
  12. package/bin/cicd/generators/github-actions.d.ts +14 -0
  13. package/bin/cicd/generators/github-actions.js +426 -0
  14. package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
  15. package/bin/cicd/generators/gitlab-ci.js +237 -0
  16. package/bin/cicd/generators/index.d.ts +6 -0
  17. package/bin/cicd/generators/index.js +15 -0
  18. package/bin/cicd/generators/jenkins.d.ts +2 -0
  19. package/bin/cicd/generators/jenkins.js +248 -0
  20. package/bin/cicd/generators/template-loader.d.ts +17 -0
  21. package/bin/cicd/generators/template-loader.js +128 -0
  22. package/bin/cicd/index.d.ts +1 -0
  23. package/bin/cicd/index.js +5 -0
  24. package/bin/cli.d.ts +1 -5
  25. package/bin/cli.js +72 -7
  26. package/bin/commands/project.commands.d.ts +19 -6
  27. package/bin/commands/project.commands.js +602 -66
  28. package/bin/config/index.d.ts +5 -0
  29. package/bin/config/index.js +10 -0
  30. package/bin/config/manager.d.ts +98 -0
  31. package/bin/config/manager.js +222 -0
  32. package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
  33. package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
  34. package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
  35. package/bin/containerize/analyzers/project-analyzer.js +150 -0
  36. package/bin/containerize/cli.d.ts +4 -0
  37. package/bin/containerize/cli.js +113 -0
  38. package/bin/containerize/form.d.ts +15 -0
  39. package/bin/containerize/form.js +152 -0
  40. package/bin/containerize/generators/ci-generator.d.ts +31 -0
  41. package/bin/containerize/generators/ci-generator.js +940 -0
  42. package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
  43. package/bin/containerize/generators/docker-compose-generator.js +187 -0
  44. package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
  45. package/bin/containerize/generators/dockerfile-generator.js +657 -0
  46. package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
  47. package/bin/containerize/generators/kubernetes-generator.js +134 -0
  48. package/bin/containerize/generators/template-loader.d.ts +36 -0
  49. package/bin/containerize/generators/template-loader.js +129 -0
  50. package/bin/containerize/index.d.ts +4 -0
  51. package/bin/containerize/index.js +13 -0
  52. package/bin/containerize/presets/preset-registry.d.ts +20 -0
  53. package/bin/containerize/presets/preset-registry.js +102 -0
  54. package/bin/costs/cli.d.ts +5 -0
  55. package/bin/costs/cli.js +185 -0
  56. package/bin/costs/form.d.ts +44 -0
  57. package/bin/costs/form.js +412 -0
  58. package/bin/costs/index.d.ts +4 -0
  59. package/bin/costs/index.js +25 -0
  60. package/bin/costs/pricing-manager.d.ts +84 -0
  61. package/bin/costs/pricing-manager.js +342 -0
  62. package/bin/costs/providers/index.d.ts +32 -0
  63. package/bin/costs/providers/index.js +153 -0
  64. package/bin/costs/sources/api-source.d.ts +10 -0
  65. package/bin/costs/sources/api-source.js +32 -0
  66. package/bin/costs/sources/index.d.ts +6 -0
  67. package/bin/costs/sources/index.js +15 -0
  68. package/bin/costs/sources/local-json-source.d.ts +23 -0
  69. package/bin/costs/sources/local-json-source.js +59 -0
  70. package/bin/costs/sources/remote-json-source.d.ts +11 -0
  71. package/bin/costs/sources/remote-json-source.js +53 -0
  72. package/bin/costs/types.d.ts +53 -0
  73. package/bin/costs/types.js +5 -0
  74. package/bin/dev/cli.d.ts +4 -0
  75. package/bin/dev/cli.js +136 -0
  76. package/bin/dev/form.d.ts +36 -0
  77. package/bin/dev/form.js +254 -0
  78. package/bin/dev/index.d.ts +1 -0
  79. package/bin/dev/index.js +5 -0
  80. package/bin/generate/cli.d.ts +1 -1
  81. package/bin/generate/cli.js +29 -2
  82. package/bin/generate/form.d.ts +5 -1
  83. package/bin/generate/form.js +3 -3
  84. package/bin/generate/templates/nonopinionated/config.tpl +12 -0
  85. package/bin/generate/templates/nonopinionated/event.tpl +10 -0
  86. package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
  87. package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
  88. package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
  89. package/bin/generate/templates/opinionated/config.tpl +47 -0
  90. package/bin/generate/templates/opinionated/entity.tpl +1 -8
  91. package/bin/generate/templates/opinionated/event.tpl +15 -0
  92. package/bin/generate/templates/opinionated/guard.tpl +41 -0
  93. package/bin/generate/templates/opinionated/handler.tpl +23 -0
  94. package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
  95. package/bin/generate/utils/command-utils.d.ts +20 -5
  96. package/bin/generate/utils/command-utils.js +145 -48
  97. package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
  98. package/bin/generate/utils/nonopininated-cmd.js +100 -1
  99. package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
  100. package/bin/generate/utils/opinionated-cmd.js +128 -16
  101. package/bin/generate/utils/string-utils.d.ts +6 -0
  102. package/bin/generate/utils/string-utils.js +13 -1
  103. package/bin/help/cli.d.ts +1 -1
  104. package/bin/help/command-help-registry.d.ts +23 -0
  105. package/bin/help/command-help-registry.js +303 -0
  106. package/bin/help/command-help.d.ts +36 -0
  107. package/bin/help/command-help.js +56 -0
  108. package/bin/help/form.js +127 -22
  109. package/bin/help/main-help.d.ts +8 -0
  110. package/bin/help/main-help.js +126 -0
  111. package/bin/help/render.d.ts +32 -0
  112. package/bin/help/render.js +46 -0
  113. package/bin/info/cli.d.ts +1 -1
  114. package/bin/info/form.d.ts +1 -1
  115. package/bin/info/form.js +11 -11
  116. package/bin/migrate/analyzers/platform-detector.d.ts +14 -0
  117. package/bin/migrate/analyzers/platform-detector.js +116 -0
  118. package/bin/migrate/cli.d.ts +6 -0
  119. package/bin/migrate/cli.js +98 -0
  120. package/bin/migrate/form.d.ts +25 -0
  121. package/bin/migrate/form.js +348 -0
  122. package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
  123. package/bin/migrate/generators/compose-to-k8s.js +324 -0
  124. package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
  125. package/bin/migrate/generators/compose-to-railway.js +138 -0
  126. package/bin/migrate/generators/compose-to-render.d.ts +2 -0
  127. package/bin/migrate/generators/compose-to-render.js +148 -0
  128. package/bin/migrate/generators/generic-migration.d.ts +9 -0
  129. package/bin/migrate/generators/generic-migration.js +221 -0
  130. package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
  131. package/bin/migrate/generators/heroku-to-fly.js +291 -0
  132. package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
  133. package/bin/migrate/generators/heroku-to-railway.js +283 -0
  134. package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
  135. package/bin/migrate/generators/heroku-to-render.js +148 -0
  136. package/bin/migrate/generators/index.d.ts +7 -0
  137. package/bin/migrate/generators/index.js +17 -0
  138. package/bin/migrate/generators/template-loader.d.ts +21 -0
  139. package/bin/migrate/generators/template-loader.js +59 -0
  140. package/bin/migrate/index.d.ts +1 -0
  141. package/bin/migrate/index.js +5 -0
  142. package/bin/new/cli.d.ts +5 -1
  143. package/bin/new/cli.js +77 -14
  144. package/bin/new/form.d.ts +27 -4
  145. package/bin/new/form.js +605 -75
  146. package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
  147. package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
  148. package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
  149. package/bin/profile/analyzers/image-analyzer.js +85 -0
  150. package/bin/profile/cli.d.ts +4 -0
  151. package/bin/profile/cli.js +94 -0
  152. package/bin/profile/form.d.ts +56 -0
  153. package/bin/profile/form.js +401 -0
  154. package/bin/profile/index.d.ts +1 -0
  155. package/bin/profile/index.js +5 -0
  156. package/bin/profile/optimizers/index.d.ts +19 -0
  157. package/bin/profile/optimizers/index.js +137 -0
  158. package/bin/providers/add/form.d.ts +1 -1
  159. package/bin/providers/add/form.js +27 -6
  160. package/bin/providers/create/form.js +53 -3
  161. package/bin/scripts/form.js +27 -5
  162. package/bin/studio/cli.d.ts +15 -0
  163. package/bin/studio/cli.js +172 -0
  164. package/bin/studio/index.d.ts +5 -0
  165. package/bin/studio/index.js +9 -0
  166. package/bin/templates/cache.d.ts +54 -0
  167. package/bin/templates/cache.js +180 -0
  168. package/bin/templates/cli.d.ts +8 -0
  169. package/bin/templates/cli.js +294 -0
  170. package/bin/templates/fetcher.d.ts +49 -0
  171. package/bin/templates/fetcher.js +208 -0
  172. package/bin/templates/index.d.ts +11 -0
  173. package/bin/templates/index.js +37 -0
  174. package/bin/templates/manager.d.ts +116 -0
  175. package/bin/templates/manager.js +323 -0
  176. package/bin/templates/renderer.d.ts +49 -0
  177. package/bin/templates/renderer.js +204 -0
  178. package/bin/templates/types.d.ts +51 -0
  179. package/bin/templates/types.js +5 -0
  180. package/bin/utils/add-module-to-container.d.ts +14 -3
  181. package/bin/utils/add-module-to-container.js +327 -98
  182. package/bin/utils/cli-ui.d.ts +49 -3
  183. package/bin/utils/cli-ui.js +133 -13
  184. package/bin/utils/index.d.ts +4 -0
  185. package/bin/utils/index.js +4 -0
  186. package/bin/utils/input-validation.d.ts +50 -0
  187. package/bin/utils/input-validation.js +143 -0
  188. package/bin/utils/package-manager-commands.d.ts +24 -0
  189. package/bin/utils/package-manager-commands.js +50 -0
  190. package/bin/utils/safe-spawn.d.ts +35 -0
  191. package/bin/utils/safe-spawn.js +51 -0
  192. package/bin/utils/update-tsconfig-paths.d.ts +35 -0
  193. package/bin/utils/update-tsconfig-paths.js +326 -0
  194. package/package.json +165 -156
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Template types and interfaces
3
+ */
4
+ export type TemplateCategory = "cicd" | "docker" | "kubernetes" | "migrations";
5
+ export type CICDPlatform = "github" | "gitlab" | "circleci" | "jenkins" | "bitbucket" | "azure";
6
+ export type CIStrategy = "basic" | "comprehensive" | "security-focused";
7
+ export type DockerTemplate = "production" | "development" | "compose" | "compose-development";
8
+ export type KubernetesTemplate = "deployment" | "service" | "configmap" | "ingress" | "secrets" | "kustomization";
9
+ export interface TemplateInfo {
10
+ path: string;
11
+ version: string;
12
+ description?: string;
13
+ variables?: string[];
14
+ }
15
+ export interface TemplateManifest {
16
+ version: string;
17
+ updated: string;
18
+ templates: {
19
+ cicd?: Record<CICDPlatform, Record<CIStrategy, TemplateInfo>>;
20
+ docker?: Record<DockerTemplate, TemplateInfo>;
21
+ kubernetes?: Record<KubernetesTemplate, TemplateInfo>;
22
+ migrations?: Record<string, Record<string, TemplateInfo>>;
23
+ };
24
+ }
25
+ export interface TemplateVariable {
26
+ name: string;
27
+ value: string | number | boolean;
28
+ }
29
+ export interface RenderOptions {
30
+ variables: Record<string, string | number | boolean | undefined>;
31
+ conditionals?: Record<string, boolean>;
32
+ }
33
+ export interface CacheEntry<T> {
34
+ data: T;
35
+ timestamp: number;
36
+ ttl: number;
37
+ }
38
+ export interface CacheConfig {
39
+ directory: string;
40
+ ttl: number;
41
+ }
42
+ export interface TemplateConfig {
43
+ repository: string;
44
+ branch: string;
45
+ cacheTTL: number;
46
+ }
47
+ export interface FetchResult<T> {
48
+ data: T | null;
49
+ source: "cache" | "remote" | "fallback";
50
+ error?: string;
51
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Template types and interfaces
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1,14 @@
1
- declare function addModuleToContainer(name: string, modulePath?: string, path?: string): Promise<void>;
2
- declare function addModuleToContainerNestedPath(name: string, path?: string): Promise<void>;
3
- export { addModuleToContainer, addModuleToContainerNestedPath };
1
+ /**
2
+ * Register a scaffolded module in `app.ts`.
3
+ *
4
+ * The import specifier is derived from the module file's real location
5
+ * (`moduleOutputPath`) so it always resolves to the file on disk, regardless of
6
+ * the path style used to scaffold it.
7
+ *
8
+ * @param moduleClassName - Exported module symbol, e.g. `UserModule`.
9
+ * @param moduleOutputPath - Path to the generated `*.module.ts` file.
10
+ * @param folderToScaffold - Destination root, e.g. `src/useCases`.
11
+ * @param folderName - Folder basename used to resolve the path alias, e.g. `useCases`.
12
+ */
13
+ declare function addModuleToContainerByPath(moduleClassName: string, moduleOutputPath: string, folderToScaffold: string, folderName: string): Promise<void>;
14
+ export { addModuleToContainerByPath };
@@ -1,20 +1,46 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.addModuleToContainerNestedPath = exports.addModuleToContainer = void 0;
7
- const chalk_1 = __importDefault(require("chalk"));
29
+ exports.addModuleToContainerByPath = void 0;
8
30
  const glob_1 = require("glob");
9
31
  const node_fs_1 = __importDefault(require("node:fs"));
32
+ const nodePath = __importStar(require("node:path"));
10
33
  const cli_ui_1 = require("./cli-ui");
11
34
  const compiler_1 = __importDefault(require("./compiler"));
35
+ const update_tsconfig_paths_1 = require("./update-tsconfig-paths");
12
36
  const APP_CONTAINER = "app.ts";
13
- async function validateAppContainer() {
37
+ /**
38
+ * Locate `app.ts` and read its raw content. We deliberately avoid any
39
+ * line-based parsing here so multi-line imports and nested array literals
40
+ * survive untouched.
41
+ */
42
+ async function readAppContainer() {
14
43
  const { sourceRoot } = await compiler_1.default.loadConfig();
15
- const imports = [];
16
- const notImports = [];
17
- // Locate the container file
18
44
  const path = (0, glob_1.globSync)(`./${sourceRoot}/${APP_CONTAINER}`, {
19
45
  absolute: true,
20
46
  ignore: "**/node_modules/**",
@@ -23,112 +49,315 @@ async function validateAppContainer() {
23
49
  (0, cli_ui_1.printError)("Module not added to Container. Container file not found!", APP_CONTAINER);
24
50
  process.exit(1);
25
51
  }
26
- // Read the container file
27
- const fileContent = await node_fs_1.default.promises.readFile(path[0], "utf8");
28
- // Collect imports and other lines
29
- fileContent.split("\n").forEach((line) => {
30
- if (line.startsWith("import")) {
31
- imports.push(line);
52
+ const content = await node_fs_1.default.promises.readFile(path[0], "utf8");
53
+ return { path: path[0], content };
54
+ }
55
+ /**
56
+ * Detect the indentation (leading whitespace) of the line containing `index`.
57
+ */
58
+ function getLineIndent(source, index) {
59
+ const lineStart = source.lastIndexOf("\n", index - 1) + 1;
60
+ const match = /^[\t ]*/.exec(source.slice(lineStart));
61
+ return match ? match[0] : "";
62
+ }
63
+ /**
64
+ * Skip over a string literal starting at `i` (the index of the opening quote).
65
+ * Handles `"..."`, `'...'`, and template strings ``` `...` ``` including escapes.
66
+ * Returns the index just past the closing quote.
67
+ */
68
+ function skipString(source, i) {
69
+ const quote = source[i];
70
+ i++;
71
+ while (i < source.length) {
72
+ const ch = source[i];
73
+ if (ch === "\\") {
74
+ i += 2;
75
+ continue;
32
76
  }
33
- else {
34
- notImports.push(line);
77
+ if (ch === quote)
78
+ return i + 1;
79
+ i++;
80
+ }
81
+ return source.length;
82
+ }
83
+ /**
84
+ * Skip over a `// line` or `/* block *\/` comment starting at `i`.
85
+ * Returns the index just past the comment, or `i` unchanged if not a comment.
86
+ */
87
+ function skipComment(source, i) {
88
+ if (source[i] === "/" && source[i + 1] === "/") {
89
+ const nl = source.indexOf("\n", i);
90
+ return nl === -1 ? source.length : nl;
91
+ }
92
+ if (source[i] === "/" && source[i + 1] === "*") {
93
+ const close = source.indexOf("*/", i + 2);
94
+ return close === -1 ? source.length : close + 2;
95
+ }
96
+ return i;
97
+ }
98
+ /**
99
+ * Find the index immediately after the last top-level `import ... ;`
100
+ * statement. Returns 0 if no imports are present. The scan is string- and
101
+ * comment-aware so multi-line imports and quoted semicolons don't confuse it.
102
+ */
103
+ function findLastImportEnd(source) {
104
+ let i = 0;
105
+ let lastEnd = 0;
106
+ while (i < source.length) {
107
+ while (i < source.length && /\s/.test(source[i]))
108
+ i++;
109
+ const afterComment = skipComment(source, i);
110
+ if (afterComment !== i) {
111
+ i = afterComment;
112
+ continue;
35
113
  }
36
- });
37
- // Regex to detect and extract modules from configContainer
38
- const moduleRegex = /this\.configContainer\(\s*\[\s*([\s\S]*?)\s*]\s*\)/;
39
- const moduleMatch = fileContent.match(moduleRegex);
40
- if (!moduleMatch) {
41
- (0, cli_ui_1.printError)("The App class does not contain a valid configContainer([]) declaration!", APP_CONTAINER);
42
- process.exit(1);
114
+ if (!source.startsWith("import", i))
115
+ break;
116
+ // Walk forward until the terminating semicolon, ignoring strings/comments.
117
+ let j = i + "import".length;
118
+ while (j < source.length) {
119
+ const ch = source[j];
120
+ if (ch === '"' || ch === "'" || ch === "`") {
121
+ j = skipString(source, j);
122
+ continue;
123
+ }
124
+ const afterCmt = skipComment(source, j);
125
+ if (afterCmt !== j) {
126
+ j = afterCmt;
127
+ continue;
128
+ }
129
+ if (ch === ";") {
130
+ j++;
131
+ break;
132
+ }
133
+ j++;
134
+ }
135
+ lastEnd = j;
136
+ i = j;
43
137
  }
44
- // Extract modules if present
45
- const modules = moduleMatch[1]
46
- .trim()
47
- .split(",")
48
- .filter((m) => m.trim() !== "")
49
- .map((m) => m.trim());
50
- return {
51
- regex: moduleRegex,
52
- path: path[0],
53
- content: fileContent,
54
- modules,
55
- imports,
56
- notImports,
57
- };
138
+ return lastEnd;
58
139
  }
59
- async function addModuleToContainer(name, modulePath, path) {
60
- console.log("To chamando esse cara");
61
- const containerData = await validateAppContainer();
62
- const moduleName = (name[0].toUpperCase() + name.slice(1)).trimStart();
63
- const { opinionated } = await compiler_1.default.loadConfig();
64
- let usecaseDir;
65
- if (opinionated) {
66
- usecaseDir = `@useCases/`;
140
+ /**
141
+ * Locate the call expression `callName(` starting at or after `from`, then
142
+ * return the `[start, end]` indices (inclusive of the opening `(` and matching
143
+ * `)`) of its argument list. Returns null if not found.
144
+ *
145
+ * The matcher respects strings, comments, and balanced brackets so the inner
146
+ * `CreateModule([...])` does not prematurely close the outer call.
147
+ */
148
+ function findCallRange(source, callName, from = 0) {
149
+ const needle = `${callName}(`;
150
+ const start = source.indexOf(needle, from);
151
+ if (start === -1)
152
+ return null;
153
+ const open = start + needle.length - 1; // index of '('
154
+ let depthParen = 1;
155
+ let depthBracket = 0;
156
+ let depthBrace = 0;
157
+ let i = open + 1;
158
+ while (i < source.length && depthParen > 0) {
159
+ const ch = source[i];
160
+ if (ch === '"' || ch === "'" || ch === "`") {
161
+ i = skipString(source, i);
162
+ continue;
163
+ }
164
+ const afterCmt = skipComment(source, i);
165
+ if (afterCmt !== i) {
166
+ i = afterCmt;
167
+ continue;
168
+ }
169
+ if (ch === "(")
170
+ depthParen++;
171
+ else if (ch === ")")
172
+ depthParen--;
173
+ else if (ch === "[")
174
+ depthBracket++;
175
+ else if (ch === "]")
176
+ depthBracket--;
177
+ else if (ch === "{")
178
+ depthBrace++;
179
+ else if (ch === "}")
180
+ depthBrace--;
181
+ i++;
67
182
  }
68
- else {
69
- usecaseDir = `./`;
183
+ if (depthParen !== 0 || depthBracket !== 0 || depthBrace !== 0)
184
+ return null;
185
+ return { open, close: i - 1 }; // close = index of ')'
186
+ }
187
+ /**
188
+ * Locate the first array literal `[...]` at or after `from`, returning the
189
+ * indices of the opening `[` and matching `]`. Bracket-balanced and
190
+ * string/comment safe.
191
+ */
192
+ function findArrayRange(source, from) {
193
+ let i = from;
194
+ while (i < source.length) {
195
+ const ch = source[i];
196
+ if (ch === "[")
197
+ break;
198
+ if (ch === '"' || ch === "'" || ch === "`") {
199
+ i = skipString(source, i);
200
+ continue;
201
+ }
202
+ const afterCmt = skipComment(source, i);
203
+ if (afterCmt !== i) {
204
+ i = afterCmt;
205
+ continue;
206
+ }
207
+ i++;
70
208
  }
71
- let newImport = "";
72
- const modulePathRegex = /^[^/]=$/;
73
- if (!modulePathRegex.test(modulePath)) {
74
- if (path.split("/").length > 1) {
75
- newImport = `import { ${moduleName}Module } from "${usecaseDir}${name.toLowerCase()}/${name.toLowerCase()}.module";`;
209
+ if (i >= source.length || source[i] !== "[")
210
+ return null;
211
+ const open = i;
212
+ let depth = 1;
213
+ let j = open + 1;
214
+ while (j < source.length && depth > 0) {
215
+ const ch = source[j];
216
+ if (ch === '"' || ch === "'" || ch === "`") {
217
+ j = skipString(source, j);
218
+ continue;
76
219
  }
77
- else {
78
- newImport = `import { ${moduleName}Module } from "${usecaseDir}${name.toLowerCase()}.module";`;
220
+ const afterCmt = skipComment(source, j);
221
+ if (afterCmt !== j) {
222
+ j = afterCmt;
223
+ continue;
79
224
  }
225
+ if (ch === "[")
226
+ depth++;
227
+ else if (ch === "]")
228
+ depth--;
229
+ j++;
80
230
  }
81
- else {
82
- newImport = `import { ${moduleName}Module } from "${usecaseDir}${name}/${name.toLowerCase()}.module";`;
231
+ if (depth !== 0)
232
+ return null;
233
+ return { open, close: j - 1 };
234
+ }
235
+ /**
236
+ * Insert `name` as a new entry in the array literal that starts at `arrayOpen`
237
+ * (the index of `[`) and ends at `arrayClose` (the index of the matching `]`).
238
+ *
239
+ * Preserves the original formatting: trailing-comma style is honoured, inline
240
+ * arrays stay inline, and multi-line arrays receive the new entry on its own
241
+ * line with matching indentation. If `name` is already present in the array
242
+ * the source is returned unchanged.
243
+ */
244
+ function insertIntoArray(source, arrayOpen, arrayClose, name) {
245
+ const inner = source.slice(arrayOpen + 1, arrayClose);
246
+ // Skip insertion if the identifier is already a top-level token in the array.
247
+ const tokenRegex = /\b[A-Za-z_$][\w$]*\b/g;
248
+ let match;
249
+ while ((match = tokenRegex.exec(inner)) !== null) {
250
+ if (match[0] === name)
251
+ return source;
83
252
  }
84
- if (containerData.imports.includes(newImport) &&
85
- containerData.modules.includes(`${moduleName}Module`)) {
86
- return;
253
+ if (inner.trim().length === 0) {
254
+ return source.slice(0, arrayOpen + 1) + name + source.slice(arrayClose);
87
255
  }
88
- containerData.imports.push(newImport);
89
- containerData.modules.push(`${moduleName}Module`);
90
- const newModule = containerData.modules.join(", ");
91
- const newModuleDeclaration = `this.configContainer([${newModule}])`;
92
- const newFileContent = [
93
- ...containerData.imports,
94
- ...containerData.notImports,
95
- ]
96
- .join("\n")
97
- .replace(containerData.regex, newModuleDeclaration);
98
- console.log(" ", chalk_1.default.greenBright(`[container]`.padEnd(14)), chalk_1.default.bold.white(`${moduleName}Module added to ${APP_CONTAINER}! ✔️`));
99
- await node_fs_1.default.promises.writeFile(containerData.path, newFileContent, "utf8");
256
+ // Find the last meaningful character inside the array (skip trailing
257
+ // whitespace before `]`).
258
+ let lastChar = arrayClose - 1;
259
+ while (lastChar > arrayOpen && /\s/.test(source[lastChar]))
260
+ lastChar--;
261
+ const hasTrailingComma = source[lastChar] === ",";
262
+ const isMultiLine = inner.includes("\n");
263
+ if (!isMultiLine) {
264
+ const insertion = hasTrailingComma ? ` ${name}` : `, ${name}`;
265
+ return (source.slice(0, lastChar + 1) +
266
+ insertion +
267
+ source.slice(lastChar + 1));
268
+ }
269
+ // Multi-line: match the indentation of the last item and place the new
270
+ // entry on its own line just before the closing bracket.
271
+ const itemIndent = getLineIndent(source, lastChar);
272
+ const insertAt = lastChar + 1;
273
+ const insertion = (hasTrailingComma ? "" : ",") + `\n${itemIndent}${name},`;
274
+ return source.slice(0, insertAt) + insertion + source.slice(insertAt);
100
275
  }
101
- exports.addModuleToContainer = addModuleToContainer;
102
- async function addModuleToContainerNestedPath(name, path) {
103
- const containerData = await validateAppContainer();
104
- const moduleName = (name[0].toUpperCase() + name.slice(1)).trimStart();
105
- const { opinionated } = await compiler_1.default.loadConfig();
106
- let usecaseDir;
107
- if (opinionated) {
108
- usecaseDir = `@useCases/`;
276
+ /**
277
+ * Insert a new import statement after the last existing one, preserving any
278
+ * blank line that already separates imports from code. If the import is
279
+ * already present (textually) the file is returned unchanged.
280
+ */
281
+ function insertImport(source, importLine) {
282
+ if (source.includes(importLine))
283
+ return source;
284
+ const insertAt = findLastImportEnd(source);
285
+ if (insertAt === 0) {
286
+ // No existing imports: prepend at top.
287
+ const sep = source.startsWith("\n") ? "" : "\n";
288
+ return importLine + "\n" + sep + source;
289
+ }
290
+ const before = source.slice(0, insertAt);
291
+ const after = source.slice(insertAt);
292
+ return before + "\n" + importLine + after;
293
+ }
294
+ /**
295
+ * Add `${moduleName}Module` to the container declaration in `app.ts`.
296
+ *
297
+ * Handles both layouts:
298
+ * - v4 wrapper: this.configContainer([CreateModule([...]), ModuleA, ModuleB])
299
+ * - legacy flat: this.configContainer([ModuleA, ModuleB])
300
+ *
301
+ * Scaffolded modules are always inserted into the outer `configContainer([...])`
302
+ * array as peer entries alongside `CreateModule([...])`. The inner
303
+ * `CreateModule([...])` is reserved for orphan controllers that don't have
304
+ * their own module file.
305
+ */
306
+ function addModuleToContainerSource(source, className) {
307
+ const configCall = findCallRange(source, "this.configContainer");
308
+ if (!configCall) {
309
+ (0, cli_ui_1.printError)("The App class does not contain a valid configContainer([]) declaration!", APP_CONTAINER);
310
+ process.exit(1);
109
311
  }
110
- else {
111
- usecaseDir = `./`;
312
+ const outerArray = findArrayRange(source, configCall.open + 1);
313
+ if (!outerArray || outerArray.close > configCall.close) {
314
+ (0, cli_ui_1.printError)("configContainer must be called with an array literal argument.", APP_CONTAINER);
315
+ process.exit(1);
112
316
  }
113
- if (path.endsWith("/")) {
114
- path = path.slice(0, -1);
317
+ return insertIntoArray(source, outerArray.open, outerArray.close, className);
318
+ }
319
+ /**
320
+ * Build the import specifier for a scaffolded module from its real on-disk
321
+ * location. In opinionated mode the path alias for the destination folder is
322
+ * used (e.g. `@useCases/user-create/user.module`); otherwise a relative import
323
+ * from the source root is produced. Deriving the specifier from the actual file
324
+ * path guarantees the generated import always matches where the module was
325
+ * written, independent of the path style (sugar/single/nested).
326
+ */
327
+ async function buildModuleImportSpec(moduleOutputPath, folderToScaffold, folderName) {
328
+ const { opinionated, sourceRoot } = await compiler_1.default.loadConfig();
329
+ const normalize = (p) => p.replace(/\\/g, "/").replace(/\.ts$/, "");
330
+ if (opinionated) {
331
+ const rel = normalize(nodePath.relative(folderToScaffold, moduleOutputPath));
332
+ return `${(0, update_tsconfig_paths_1.getPathAliasForFolder)(folderName)}/${rel}`;
115
333
  }
116
- const newImport = `import { ${moduleName}Module } from "${usecaseDir}${path}.module";`;
117
- if (containerData.imports.includes(newImport) &&
118
- containerData.modules.includes(`${moduleName}Module`)) {
334
+ const rel = normalize(nodePath.relative(sourceRoot, moduleOutputPath));
335
+ return `./${rel}`;
336
+ }
337
+ async function applyContainerEdit(className, importLine) {
338
+ const info = await readAppContainer();
339
+ let next = insertImport(info.content, importLine);
340
+ next = addModuleToContainerSource(next, className);
341
+ if (next === info.content)
119
342
  return;
120
- }
121
- containerData.imports.push(newImport);
122
- containerData.modules.push(`${moduleName}Module`);
123
- const newModule = containerData.modules.join(", ");
124
- const newModuleDeclaration = `this.configContainer([${newModule}])`;
125
- const newFileContent = [
126
- ...containerData.imports,
127
- ...containerData.notImports,
128
- ]
129
- .join("\n")
130
- .replace(containerData.regex, newModuleDeclaration);
131
- console.log(" ", chalk_1.default.greenBright(`[container]`.padEnd(14)), chalk_1.default.bold.white(`${moduleName}Module added to ${APP_CONTAINER}! ✔️`));
132
- await node_fs_1.default.promises.writeFile(containerData.path, newFileContent, "utf8");
343
+ await node_fs_1.default.promises.writeFile(info.path, next, "utf8");
344
+ (0, cli_ui_1.printSuccess)(`${className} registered in ${APP_CONTAINER}`, "container");
345
+ }
346
+ /**
347
+ * Register a scaffolded module in `app.ts`.
348
+ *
349
+ * The import specifier is derived from the module file's real location
350
+ * (`moduleOutputPath`) so it always resolves to the file on disk, regardless of
351
+ * the path style used to scaffold it.
352
+ *
353
+ * @param moduleClassName - Exported module symbol, e.g. `UserModule`.
354
+ * @param moduleOutputPath - Path to the generated `*.module.ts` file.
355
+ * @param folderToScaffold - Destination root, e.g. `src/useCases`.
356
+ * @param folderName - Folder basename used to resolve the path alias, e.g. `useCases`.
357
+ */
358
+ async function addModuleToContainerByPath(moduleClassName, moduleOutputPath, folderToScaffold, folderName) {
359
+ const importSpec = await buildModuleImportSpec(moduleOutputPath, folderToScaffold, folderName);
360
+ const importLine = `import { ${moduleClassName} } from "${importSpec}";`;
361
+ await applyContainerEdit(moduleClassName, importLine);
133
362
  }
134
- exports.addModuleToContainerNestedPath = addModuleToContainerNestedPath;
363
+ exports.addModuleToContainerByPath = addModuleToContainerByPath;
@@ -1,5 +1,51 @@
1
- export declare function printError(message: string, component: string): void;
2
- export declare function printSuccess(message: string, component: string): void;
3
- export declare function printWarning(message: string, component?: string): void;
1
+ /**
2
+ * Print error message (matches core logger ERROR format)
3
+ */
4
+ export declare function printError(message: string, context: string): void;
5
+ /**
6
+ * Print success message (matches core logger INFO format)
7
+ */
8
+ export declare function printSuccess(message: string, context: string): void;
9
+ /**
10
+ * Print warning message (matches core logger WARN format)
11
+ */
12
+ export declare function printWarning(message: string, context?: string): void;
13
+ /**
14
+ * Print info message (matches core logger INFO format)
15
+ */
16
+ export declare function printInfo(message: string, context: string): void;
17
+ /**
18
+ * Print debug message (matches core logger DEBUG format)
19
+ */
20
+ export declare function printDebug(message: string, context: string): void;
21
+ /**
22
+ * Print generate error (simplified format for scaffolding)
23
+ */
4
24
  export declare function printGenerateError(schematic: string, file: string): Promise<void>;
25
+ /**
26
+ * Print generate success (simplified format for scaffolding)
27
+ */
5
28
  export declare function printGenerateSuccess(schematic: string, file: string): Promise<void>;
29
+ /**
30
+ * Print a section title. Intended for interactive/listing output (e.g.
31
+ * `templates list`, `costs compare`) where the structured logger format
32
+ * (`[ExpressoTS] timestamp LEVEL`) would be noisy. Leading newline keeps
33
+ * sections visually separated.
34
+ */
35
+ export declare function printSection(title: string): void;
36
+ /**
37
+ * Print an indented bullet point under a section.
38
+ */
39
+ export declare function printBullet(text: string): void;
40
+ /**
41
+ * Print a dim horizontal divider sized to the terminal width (capped).
42
+ */
43
+ export declare function printDivider(): void;
44
+ /**
45
+ * Print an aligned key/value pair (e.g. `Source: remote`).
46
+ */
47
+ export declare function printKeyValue(key: string, value: string, padding?: number): void;
48
+ /**
49
+ * Print the ExpressoTS CLI header
50
+ */
51
+ export declare function printHeader(version?: string): void;