@expressots/cli 4.0.0-preview.2 → 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.
- package/bin/cicd/cli.d.ts +1 -1
- package/bin/cicd/cli.js +3 -1
- package/bin/cicd/form.js +5 -4
- package/bin/cli.d.ts +1 -5
- package/bin/cli.js +56 -6
- package/bin/commands/project.commands.js +233 -26
- package/bin/containerize/cli.d.ts +1 -1
- package/bin/containerize/cli.js +1 -1
- package/bin/containerize/form.js +49 -51
- package/bin/containerize/generators/ci-generator.js +16 -12
- package/bin/containerize/generators/docker-compose-generator.js +3 -2
- package/bin/containerize/generators/dockerfile-generator.js +50 -28
- package/bin/containerize/generators/kubernetes-generator.js +5 -4
- package/bin/costs/cli.d.ts +1 -1
- package/bin/costs/cli.js +4 -2
- package/bin/dev/cli.d.ts +1 -1
- package/bin/dev/cli.js +3 -1
- package/bin/generate/cli.d.ts +1 -1
- package/bin/generate/templates/nonopinionated/config.tpl +12 -12
- package/bin/generate/templates/nonopinionated/event.tpl +10 -10
- package/bin/generate/templates/nonopinionated/guard.tpl +18 -18
- package/bin/generate/templates/nonopinionated/handler.tpl +12 -12
- package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -27
- package/bin/generate/templates/opinionated/config.tpl +47 -47
- package/bin/generate/templates/opinionated/event.tpl +15 -15
- package/bin/generate/templates/opinionated/guard.tpl +41 -41
- package/bin/generate/templates/opinionated/handler.tpl +23 -23
- package/bin/generate/templates/opinionated/interceptor.tpl +50 -50
- package/bin/generate/utils/command-utils.d.ts +13 -2
- package/bin/generate/utils/command-utils.js +50 -17
- package/bin/generate/utils/opinionated-cmd.js +19 -12
- package/bin/help/cli.d.ts +1 -1
- package/bin/help/command-help-registry.d.ts +23 -0
- package/bin/help/command-help-registry.js +303 -0
- package/bin/help/command-help.d.ts +36 -0
- package/bin/help/command-help.js +56 -0
- package/bin/help/form.js +127 -30
- package/bin/help/main-help.d.ts +8 -0
- package/bin/help/main-help.js +126 -0
- package/bin/help/render.d.ts +32 -0
- package/bin/help/render.js +46 -0
- package/bin/info/cli.d.ts +1 -1
- package/bin/info/form.d.ts +1 -1
- package/bin/info/form.js +11 -11
- package/bin/migrate/cli.d.ts +1 -1
- package/bin/migrate/cli.js +3 -1
- package/bin/migrate/form.js +4 -3
- package/bin/new/cli.d.ts +5 -1
- package/bin/new/cli.js +62 -14
- package/bin/new/form.d.ts +3 -1
- package/bin/new/form.js +338 -23
- package/bin/profile/cli.d.ts +1 -1
- package/bin/profile/cli.js +3 -1
- package/bin/profile/form.js +5 -4
- package/bin/providers/create/form.js +53 -4
- package/bin/studio/cli.js +9 -3
- package/bin/templates/cli.js +7 -5
- package/bin/utils/add-module-to-container.d.ts +14 -3
- package/bin/utils/add-module-to-container.js +330 -111
- package/bin/utils/cli-ui.d.ts +20 -1
- package/bin/utils/cli-ui.js +41 -3
- package/bin/utils/update-tsconfig-paths.js +73 -33
- package/package.json +22 -13
package/bin/templates/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.templatesCommand = void 0;
|
|
|
11
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
12
|
const manager_1 = require("./manager");
|
|
13
13
|
const config_1 = require("../config");
|
|
14
|
+
const cli_ui_1 = require("../utils/cli-ui");
|
|
14
15
|
const templatesCommand = () => {
|
|
15
16
|
return {
|
|
16
17
|
command: "templates <action> [args...]",
|
|
@@ -77,8 +78,9 @@ const templatesCommand = () => {
|
|
|
77
78
|
await showStatus();
|
|
78
79
|
break;
|
|
79
80
|
default:
|
|
80
|
-
|
|
81
|
+
(0, cli_ui_1.printError)(`Unknown action: ${action}`, "templates");
|
|
81
82
|
console.log(chalk_1.default.gray("Run 'expressots templates --help' for usage."));
|
|
83
|
+
process.exit(1);
|
|
82
84
|
}
|
|
83
85
|
},
|
|
84
86
|
};
|
|
@@ -88,7 +90,7 @@ exports.templatesCommand = templatesCommand;
|
|
|
88
90
|
* List available templates
|
|
89
91
|
*/
|
|
90
92
|
async function listTemplates(category, platform) {
|
|
91
|
-
|
|
93
|
+
(0, cli_ui_1.printSection)("📋 Available Templates");
|
|
92
94
|
const manager = (0, manager_1.getTemplateManager)();
|
|
93
95
|
const templates = await manager.listTemplates();
|
|
94
96
|
// Show template source
|
|
@@ -161,7 +163,7 @@ async function listTemplates(category, platform) {
|
|
|
161
163
|
* Update template cache
|
|
162
164
|
*/
|
|
163
165
|
async function updateTemplates() {
|
|
164
|
-
|
|
166
|
+
(0, cli_ui_1.printSection)("🔄 Updating Templates...");
|
|
165
167
|
const manager = (0, manager_1.getTemplateManager)();
|
|
166
168
|
const result = await manager.updateCache();
|
|
167
169
|
if (result.updated > 0) {
|
|
@@ -182,7 +184,7 @@ async function updateTemplates() {
|
|
|
182
184
|
* Clear template cache
|
|
183
185
|
*/
|
|
184
186
|
async function clearCache() {
|
|
185
|
-
|
|
187
|
+
(0, cli_ui_1.printSection)("🗑️ Clearing Template Cache...");
|
|
186
188
|
const manager = (0, manager_1.getTemplateManager)();
|
|
187
189
|
const stats = manager.getCacheStats();
|
|
188
190
|
manager.clearCache();
|
|
@@ -240,7 +242,7 @@ async function manageRepository(args) {
|
|
|
240
242
|
const subAction = args[0];
|
|
241
243
|
if (!subAction) {
|
|
242
244
|
const config = configManager.getTemplateConfig();
|
|
243
|
-
|
|
245
|
+
(0, cli_ui_1.printSection)("📦 Template Repository Configuration");
|
|
244
246
|
console.log(` Repository: ${chalk_1.default.yellow(config.repository)}`);
|
|
245
247
|
console.log(` Branch: ${chalk_1.default.cyan(config.branch)}`);
|
|
246
248
|
console.log(` Cache TTL: ${config.cacheTTL} seconds`);
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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,21 +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.
|
|
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"));
|
|
12
35
|
const update_tsconfig_paths_1 = require("./update-tsconfig-paths");
|
|
13
36
|
const APP_CONTAINER = "app.ts";
|
|
14
|
-
|
|
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() {
|
|
15
43
|
const { sourceRoot } = await compiler_1.default.loadConfig();
|
|
16
|
-
const imports = [];
|
|
17
|
-
const notImports = [];
|
|
18
|
-
// Locate the container file
|
|
19
44
|
const path = (0, glob_1.globSync)(`./${sourceRoot}/${APP_CONTAINER}`, {
|
|
20
45
|
absolute: true,
|
|
21
46
|
ignore: "**/node_modules/**",
|
|
@@ -24,121 +49,315 @@ async function validateAppContainer() {
|
|
|
24
49
|
(0, cli_ui_1.printError)("Module not added to Container. Container file not found!", APP_CONTAINER);
|
|
25
50
|
process.exit(1);
|
|
26
51
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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;
|
|
76
|
+
}
|
|
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;
|
|
33
113
|
}
|
|
34
|
-
|
|
35
|
-
|
|
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++;
|
|
36
134
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const moduleRegex = /this\.configContainer\(\s*\[\s*([\s\S]*?)\s*]\s*\)/;
|
|
40
|
-
const moduleMatch = fileContent.match(moduleRegex);
|
|
41
|
-
if (!moduleMatch) {
|
|
42
|
-
(0, cli_ui_1.printError)("The App class does not contain a valid configContainer([]) declaration!", APP_CONTAINER);
|
|
43
|
-
process.exit(1);
|
|
135
|
+
lastEnd = j;
|
|
136
|
+
i = j;
|
|
44
137
|
}
|
|
45
|
-
|
|
46
|
-
const modules = moduleMatch[1]
|
|
47
|
-
.trim()
|
|
48
|
-
.split(",")
|
|
49
|
-
.filter((m) => m.trim() !== "")
|
|
50
|
-
.map((m) => m.trim());
|
|
51
|
-
return {
|
|
52
|
-
regex: moduleRegex,
|
|
53
|
-
path: path[0],
|
|
54
|
-
content: fileContent,
|
|
55
|
-
modules,
|
|
56
|
-
imports,
|
|
57
|
-
notImports,
|
|
58
|
-
};
|
|
138
|
+
return lastEnd;
|
|
59
139
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
let
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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;
|
|
81
163
|
}
|
|
82
|
-
|
|
83
|
-
|
|
164
|
+
const afterCmt = skipComment(source, i);
|
|
165
|
+
if (afterCmt !== i) {
|
|
166
|
+
i = afterCmt;
|
|
167
|
+
continue;
|
|
84
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++;
|
|
85
182
|
}
|
|
86
|
-
|
|
87
|
-
|
|
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++;
|
|
88
208
|
}
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
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;
|
|
219
|
+
}
|
|
220
|
+
const afterCmt = skipComment(source, j);
|
|
221
|
+
if (afterCmt !== j) {
|
|
222
|
+
j = afterCmt;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (ch === "[")
|
|
226
|
+
depth++;
|
|
227
|
+
else if (ch === "]")
|
|
228
|
+
depth--;
|
|
229
|
+
j++;
|
|
230
|
+
}
|
|
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;
|
|
252
|
+
}
|
|
253
|
+
if (inner.trim().length === 0) {
|
|
254
|
+
return source.slice(0, arrayOpen + 1) + name + source.slice(arrayClose);
|
|
255
|
+
}
|
|
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);
|
|
275
|
+
}
|
|
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);
|
|
311
|
+
}
|
|
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);
|
|
92
316
|
}
|
|
93
|
-
|
|
94
|
-
containerData.modules.push(`${moduleName}Module`);
|
|
95
|
-
const newModule = containerData.modules.join(", ");
|
|
96
|
-
const newModuleDeclaration = `this.configContainer([${newModule}])`;
|
|
97
|
-
const newFileContent = [
|
|
98
|
-
...containerData.imports,
|
|
99
|
-
...containerData.notImports,
|
|
100
|
-
]
|
|
101
|
-
.join("\n")
|
|
102
|
-
.replace(containerData.regex, newModuleDeclaration);
|
|
103
|
-
console.log(" ", chalk_1.default.greenBright(`[container]`.padEnd(14)), chalk_1.default.bold.white(`${moduleName}Module added to ${APP_CONTAINER}! ✔️`));
|
|
104
|
-
await node_fs_1.default.promises.writeFile(containerData.path, newFileContent, "utf8");
|
|
317
|
+
return insertIntoArray(source, outerArray.open, outerArray.close, className);
|
|
105
318
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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$/, "");
|
|
112
330
|
if (opinionated) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const pathAlias = folderName
|
|
116
|
-
? (0, update_tsconfig_paths_1.getPathAliasForFolder)(folderName)
|
|
117
|
-
: "@useCases";
|
|
118
|
-
usecaseDir = `${pathAlias}/`;
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
usecaseDir = `./`;
|
|
122
|
-
}
|
|
123
|
-
if (path.endsWith("/")) {
|
|
124
|
-
path = path.slice(0, -1);
|
|
125
|
-
}
|
|
126
|
-
const newImport = `import { ${moduleName}Module } from "${usecaseDir}${path}.module";`;
|
|
127
|
-
if (containerData.imports.includes(newImport) &&
|
|
128
|
-
containerData.modules.includes(`${moduleName}Module`)) {
|
|
129
|
-
return;
|
|
331
|
+
const rel = normalize(nodePath.relative(folderToScaffold, moduleOutputPath));
|
|
332
|
+
return `${(0, update_tsconfig_paths_1.getPathAliasForFolder)(folderName)}/${rel}`;
|
|
130
333
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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)
|
|
342
|
+
return;
|
|
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);
|
|
143
362
|
}
|
|
144
|
-
exports.
|
|
363
|
+
exports.addModuleToContainerByPath = addModuleToContainerByPath;
|
package/bin/utils/cli-ui.d.ts
CHANGED
|
@@ -26,7 +26,26 @@ export declare function printGenerateError(schematic: string, file: string): Pro
|
|
|
26
26
|
* Print generate success (simplified format for scaffolding)
|
|
27
27
|
*/
|
|
28
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;
|
|
29
48
|
/**
|
|
30
49
|
* Print the ExpressoTS CLI header
|
|
31
50
|
*/
|
|
32
|
-
export declare function printHeader(): void;
|
|
51
|
+
export declare function printHeader(version?: string): void;
|
package/bin/utils/cli-ui.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.printHeader = exports.printGenerateSuccess = exports.printGenerateError = exports.printDebug = exports.printInfo = exports.printWarning = exports.printSuccess = exports.printError = void 0;
|
|
6
|
+
exports.printHeader = exports.printKeyValue = exports.printDivider = exports.printBullet = exports.printSection = exports.printGenerateSuccess = exports.printGenerateError = exports.printDebug = exports.printInfo = exports.printWarning = exports.printSuccess = exports.printError = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const process_1 = require("process");
|
|
9
9
|
/**
|
|
@@ -105,10 +105,48 @@ async function printGenerateSuccess(schematic, file) {
|
|
|
105
105
|
log("INFO", schematic, `${file.split(".")[0]} created!`, "✔️");
|
|
106
106
|
}
|
|
107
107
|
exports.printGenerateSuccess = printGenerateSuccess;
|
|
108
|
+
/**
|
|
109
|
+
* Print a section title. Intended for interactive/listing output (e.g.
|
|
110
|
+
* `templates list`, `costs compare`) where the structured logger format
|
|
111
|
+
* (`[ExpressoTS] timestamp LEVEL`) would be noisy. Leading newline keeps
|
|
112
|
+
* sections visually separated.
|
|
113
|
+
*/
|
|
114
|
+
function printSection(title) {
|
|
115
|
+
process_1.stdout.write(`\n${chalk_1.default.bold.cyan(title)}\n`);
|
|
116
|
+
}
|
|
117
|
+
exports.printSection = printSection;
|
|
118
|
+
/**
|
|
119
|
+
* Print an indented bullet point under a section.
|
|
120
|
+
*/
|
|
121
|
+
function printBullet(text) {
|
|
122
|
+
process_1.stdout.write(` ${chalk_1.default.gray("-")} ${text}\n`);
|
|
123
|
+
}
|
|
124
|
+
exports.printBullet = printBullet;
|
|
125
|
+
/**
|
|
126
|
+
* Print a dim horizontal divider sized to the terminal width (capped).
|
|
127
|
+
*/
|
|
128
|
+
function printDivider() {
|
|
129
|
+
const cols = typeof process.stdout.columns === "number" && process.stdout.columns > 0
|
|
130
|
+
? process.stdout.columns
|
|
131
|
+
: 80;
|
|
132
|
+
const width = Math.min(Math.max(cols, 20), 80);
|
|
133
|
+
process_1.stdout.write(`${chalk_1.default.dim("\u2500".repeat(width))}\n`);
|
|
134
|
+
}
|
|
135
|
+
exports.printDivider = printDivider;
|
|
136
|
+
/**
|
|
137
|
+
* Print an aligned key/value pair (e.g. `Source: remote`).
|
|
138
|
+
*/
|
|
139
|
+
function printKeyValue(key, value, padding = 12) {
|
|
140
|
+
process_1.stdout.write(` ${chalk_1.default.bold(`${key}:`.padEnd(padding))} ${value}\n`);
|
|
141
|
+
}
|
|
142
|
+
exports.printKeyValue = printKeyValue;
|
|
108
143
|
/**
|
|
109
144
|
* Print the ExpressoTS CLI header
|
|
110
145
|
*/
|
|
111
|
-
function printHeader() {
|
|
112
|
-
|
|
146
|
+
function printHeader(version) {
|
|
147
|
+
const title = version
|
|
148
|
+
? `🐎 ExpressoTS CLI v${version}`
|
|
149
|
+
: "🐎 ExpressoTS CLI";
|
|
150
|
+
process_1.stdout.write(`\n${chalk_1.default.bold.green(title)}\n\n`);
|
|
113
151
|
}
|
|
114
152
|
exports.printHeader = printHeader;
|