@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.
Files changed (63) hide show
  1. package/bin/cicd/cli.d.ts +1 -1
  2. package/bin/cicd/cli.js +3 -1
  3. package/bin/cicd/form.js +5 -4
  4. package/bin/cli.d.ts +1 -5
  5. package/bin/cli.js +56 -6
  6. package/bin/commands/project.commands.js +233 -26
  7. package/bin/containerize/cli.d.ts +1 -1
  8. package/bin/containerize/cli.js +1 -1
  9. package/bin/containerize/form.js +49 -51
  10. package/bin/containerize/generators/ci-generator.js +16 -12
  11. package/bin/containerize/generators/docker-compose-generator.js +3 -2
  12. package/bin/containerize/generators/dockerfile-generator.js +50 -28
  13. package/bin/containerize/generators/kubernetes-generator.js +5 -4
  14. package/bin/costs/cli.d.ts +1 -1
  15. package/bin/costs/cli.js +4 -2
  16. package/bin/dev/cli.d.ts +1 -1
  17. package/bin/dev/cli.js +3 -1
  18. package/bin/generate/cli.d.ts +1 -1
  19. package/bin/generate/templates/nonopinionated/config.tpl +12 -12
  20. package/bin/generate/templates/nonopinionated/event.tpl +10 -10
  21. package/bin/generate/templates/nonopinionated/guard.tpl +18 -18
  22. package/bin/generate/templates/nonopinionated/handler.tpl +12 -12
  23. package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -27
  24. package/bin/generate/templates/opinionated/config.tpl +47 -47
  25. package/bin/generate/templates/opinionated/event.tpl +15 -15
  26. package/bin/generate/templates/opinionated/guard.tpl +41 -41
  27. package/bin/generate/templates/opinionated/handler.tpl +23 -23
  28. package/bin/generate/templates/opinionated/interceptor.tpl +50 -50
  29. package/bin/generate/utils/command-utils.d.ts +13 -2
  30. package/bin/generate/utils/command-utils.js +50 -17
  31. package/bin/generate/utils/opinionated-cmd.js +19 -12
  32. package/bin/help/cli.d.ts +1 -1
  33. package/bin/help/command-help-registry.d.ts +23 -0
  34. package/bin/help/command-help-registry.js +303 -0
  35. package/bin/help/command-help.d.ts +36 -0
  36. package/bin/help/command-help.js +56 -0
  37. package/bin/help/form.js +127 -30
  38. package/bin/help/main-help.d.ts +8 -0
  39. package/bin/help/main-help.js +126 -0
  40. package/bin/help/render.d.ts +32 -0
  41. package/bin/help/render.js +46 -0
  42. package/bin/info/cli.d.ts +1 -1
  43. package/bin/info/form.d.ts +1 -1
  44. package/bin/info/form.js +11 -11
  45. package/bin/migrate/cli.d.ts +1 -1
  46. package/bin/migrate/cli.js +3 -1
  47. package/bin/migrate/form.js +4 -3
  48. package/bin/new/cli.d.ts +5 -1
  49. package/bin/new/cli.js +62 -14
  50. package/bin/new/form.d.ts +3 -1
  51. package/bin/new/form.js +338 -23
  52. package/bin/profile/cli.d.ts +1 -1
  53. package/bin/profile/cli.js +3 -1
  54. package/bin/profile/form.js +5 -4
  55. package/bin/providers/create/form.js +53 -4
  56. package/bin/studio/cli.js +9 -3
  57. package/bin/templates/cli.js +7 -5
  58. package/bin/utils/add-module-to-container.d.ts +14 -3
  59. package/bin/utils/add-module-to-container.js +330 -111
  60. package/bin/utils/cli-ui.d.ts +20 -1
  61. package/bin/utils/cli-ui.js +41 -3
  62. package/bin/utils/update-tsconfig-paths.js +73 -33
  63. package/package.json +22 -13
package/bin/cicd/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CommandModule } from "yargs";
2
- type CommandModuleArgs = {};
2
+ type CommandModuleArgs = Record<string, never>;
3
3
  export type CIPlatform = "github" | "gitlab" | "circleci" | "jenkins" | "bitbucket" | "azure";
4
4
  export type CIStrategy = "basic" | "comprehensive" | "security-focused";
5
5
  declare const cicdCommand: () => CommandModule<CommandModuleArgs, any>;
package/bin/cicd/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cicdCommand = void 0;
4
4
  const form_1 = require("./form");
5
+ const cli_ui_1 = require("../utils/cli-ui");
5
6
  const cicdCommand = () => {
6
7
  return {
7
8
  command: "cicd <action> [platform]",
@@ -118,7 +119,8 @@ const cicdCommand = () => {
118
119
  await (0, form_1.validatePipelines)();
119
120
  break;
120
121
  default:
121
- console.log(`Unknown action: ${action}`);
122
+ (0, cli_ui_1.printError)(`Unknown action: ${action}`, "cicd");
123
+ process.exit(1);
122
124
  }
123
125
  },
124
126
  };
package/bin/cicd/form.js CHANGED
@@ -10,6 +10,7 @@ const chalk_1 = __importDefault(require("chalk"));
10
10
  const inquirer_1 = __importDefault(require("inquirer"));
11
11
  const project_analyzer_1 = require("../containerize/analyzers/project-analyzer");
12
12
  const generators_1 = require("./generators");
13
+ const cli_ui_1 = require("../utils/cli-ui");
13
14
  const PLATFORMS = [
14
15
  {
15
16
  id: "github",
@@ -46,7 +47,7 @@ const PLATFORMS = [
46
47
  * Interactive CI/CD setup wizard
47
48
  */
48
49
  async function initCICD(options) {
49
- console.log(chalk_1.default.cyan("\n🔧 ExpressoTS CI/CD Setup Wizard\n"));
50
+ (0, cli_ui_1.printSection)("🔧 ExpressoTS CI/CD Setup Wizard");
50
51
  // Analyze project first
51
52
  const analysis = await (0, project_analyzer_1.analyzeProject)();
52
53
  // Interactive prompts
@@ -148,7 +149,7 @@ exports.initCICD = initCICD;
148
149
  * Generate CI/CD configuration for specific platform(s)
149
150
  */
150
151
  async function generateCICD(options) {
151
- console.log(chalk_1.default.cyan("\n🔧 ExpressoTS CI/CD Generator\n"));
152
+ (0, cli_ui_1.printSection)("🔧 ExpressoTS CI/CD Generator");
152
153
  if (!options.platform) {
153
154
  console.log(chalk_1.default.red("Error: Please specify a platform. Use 'expressots cicd list' to see available platforms."));
154
155
  return;
@@ -173,7 +174,7 @@ exports.generateCICD = generateCICD;
173
174
  * List available CI/CD platforms
174
175
  */
175
176
  async function listPlatforms() {
176
- console.log(chalk_1.default.cyan("\n📋 Available CI/CD Platforms\n"));
177
+ (0, cli_ui_1.printSection)("📋 Available CI/CD Platforms");
177
178
  console.log(chalk_1.default.bold("Platform".padEnd(20) + "Description".padEnd(45) + "Status"));
178
179
  console.log("-".repeat(80));
179
180
  for (const platform of PLATFORMS) {
@@ -191,7 +192,7 @@ exports.listPlatforms = listPlatforms;
191
192
  * Validate existing CI/CD configurations
192
193
  */
193
194
  async function validatePipelines() {
194
- console.log(chalk_1.default.cyan("\n🔍 Validating CI/CD Configurations\n"));
195
+ (0, cli_ui_1.printSection)("🔍 Validating CI/CD Configurations");
195
196
  const cwd = process.cwd();
196
197
  const validations = [];
197
198
  // Check for each platform's config file
package/bin/cli.d.ts CHANGED
@@ -1,6 +1,2 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * The current version of the ExpressoTS Bundle.
4
- * core, adapters, and cli.
5
- */
6
- export declare const BUNDLE_VERSION = "4.0.0-preview.2";
2
+ export declare const BUNDLE_VERSION: string;
package/bin/cli.js CHANGED
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.BUNDLE_VERSION = void 0;
8
+ const fs_1 = require("fs");
9
+ const path_1 = require("path");
8
10
  const chalk_1 = __importDefault(require("chalk"));
9
11
  const yargs_1 = __importDefault(require("yargs"));
10
12
  const helpers_1 = require("yargs/helpers");
@@ -23,14 +25,60 @@ const new_1 = require("./new");
23
25
  const providers_1 = require("./providers");
24
26
  const cli_2 = require("./providers/create/cli");
25
27
  const cli_ui_1 = require("./utils/cli-ui");
28
+ const main_help_1 = require("./help/main-help");
29
+ const command_help_registry_1 = require("./help/command-help-registry");
26
30
  const scripts_1 = require("./scripts");
27
31
  const studio_1 = require("./studio");
28
32
  /**
29
33
  * The current version of the ExpressoTS Bundle.
30
- * core, adapters, and cli.
34
+ * Derived from this CLI package's own package.json — single source of truth.
35
+ * The compiled binary lives at `bin/cli.js`, so the package.json is one
36
+ * directory above `__dirname`. When running from source via tsx the layout
37
+ * is `src/cli.ts` -> `../package.json`, which still resolves correctly.
31
38
  */
32
- exports.BUNDLE_VERSION = "4.0.0-preview.2";
33
- (0, cli_ui_1.printHeader)();
39
+ function readBundleVersion() {
40
+ try {
41
+ const pkgPath = (0, path_1.resolve)(__dirname, "..", "package.json");
42
+ const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, "utf8"));
43
+ if (typeof pkg.version === "string" && pkg.version.length > 0) {
44
+ return pkg.version;
45
+ }
46
+ }
47
+ catch {
48
+ // fall through to the safe default below
49
+ }
50
+ return "0.0.0";
51
+ }
52
+ exports.BUNDLE_VERSION = readBundleVersion();
53
+ // Respect the NO_COLOR convention (https://no-color.org). chalk v4 does
54
+ // not auto-detect this, so we disable color output explicitly when the
55
+ // variable is present (regardless of its value).
56
+ if (process.env.NO_COLOR !== undefined) {
57
+ chalk_1.default.level = 0;
58
+ }
59
+ // Intercept the *top-level* help (and the bare no-command invocation)
60
+ // to render our refined, grouped help screen instead of the sprawling
61
+ // default yargs output. Per-command help (e.g. `new --help`) is left to
62
+ // yargs so its rich, command-specific epilogs still work.
63
+ const cliArgs = (0, helpers_1.hideBin)(process.argv);
64
+ const TOP_LEVEL_HELP_TOKENS = new Set(["help", "--help", "-h"]);
65
+ const isTopLevelHelp = cliArgs.length === 0 ||
66
+ (cliArgs.length === 1 && TOP_LEVEL_HELP_TOKENS.has(cliArgs[0]));
67
+ if (isTopLevelHelp) {
68
+ (0, main_help_1.printMainHelp)(exports.BUNDLE_VERSION);
69
+ process.exit(0);
70
+ }
71
+ // Intercept per-command help (e.g. `costs --help`) for commands that have a
72
+ // registered spec, rendering the same refined, grouped screen as the rest of
73
+ // the CLI. Commands without a spec fall through to yargs' default help.
74
+ if ((0, command_help_registry_1.tryPrintCommandHelp)(cliArgs, exports.BUNDLE_VERSION)) {
75
+ process.exit(0);
76
+ }
77
+ // Only show the banner in interactive terminals. When output is piped
78
+ // (CI, file redirection, shell completion), the header would be noise.
79
+ if (process.stdout.isTTY) {
80
+ (0, cli_ui_1.printHeader)(exports.BUNDLE_VERSION);
81
+ }
34
82
  (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
35
83
  .scriptName("expressots")
36
84
  .command((0, new_1.createProject)())
@@ -52,6 +100,7 @@ exports.BUNDLE_VERSION = "4.0.0-preview.2";
52
100
  .command((0, studio_1.studioCommand)())
53
101
  .command((0, info_1.infoProject)())
54
102
  .command((0, cli_1.helpCommand)())
103
+ .completion("completion", "Generate a shell completion script (bash/zsh)")
55
104
  .demandCommand(1, "You need at least one command before moving on")
56
105
  .strict()
57
106
  .fail((msg, err, yargs) => {
@@ -79,10 +128,11 @@ exports.BUNDLE_VERSION = "4.0.0-preview.2";
79
128
  process.exit(1);
80
129
  })
81
130
  .epilog(`${chalk_1.default.bold.green("For more information:")} \n\n` +
82
- "🌐 visit:\t https://expresso-ts.com\n" +
83
- "💖 Sponsor:\t https://github.com/sponsors/expressots")
131
+ `${"🌐 visit:".padEnd(12)} https://expresso-ts.com\n` +
132
+ `${"💖 Sponsor:".padEnd(12)} https://github.com/sponsors/expressots`)
84
133
  .help("help", "Show command help")
85
134
  .alias("h", "help")
86
- .version(false)
135
+ .version(exports.BUNDLE_VERSION)
136
+ .alias("V", "version")
87
137
  .wrap(140)
88
138
  .parse();
@@ -36,7 +36,95 @@ const cli_ui_1 = require("../utils/cli-ui");
36
36
  const compiler_1 = __importDefault(require("../utils/compiler"));
37
37
  const safe_spawn_1 = require("../utils/safe-spawn");
38
38
  /**
39
- * Helper function to load and extract outDir from tsconfig.build.json
39
+ * Resolve a tsconfig.json file with full `extends` chain support.
40
+ * Recursively loads the base config(s) and merges `compilerOptions`
41
+ * (child wins), producing the same flattened result that `tsc` sees.
42
+ */
43
+ function resolveTsConfig(configPath) {
44
+ if (!(0, fs_1.existsSync)(configPath)) {
45
+ return {};
46
+ }
47
+ let raw;
48
+ try {
49
+ raw = (0, fs_1.readFileSync)(configPath, "utf-8");
50
+ }
51
+ catch {
52
+ return {};
53
+ }
54
+ // tsconfig.json allows JS-style comments but JSON.parse does not.
55
+ // We strip them character-by-character so we never touch `//` or
56
+ // `/*` sequences that appear inside quoted strings (e.g. paths).
57
+ const stripped = stripJsonComments(raw);
58
+ let config;
59
+ try {
60
+ config = JSON.parse(stripped);
61
+ }
62
+ catch {
63
+ return {};
64
+ }
65
+ if (typeof config.extends === "string") {
66
+ const baseRelative = config.extends;
67
+ const basePath = path_1.default.resolve(path_1.default.dirname(configPath), baseRelative);
68
+ const baseConfig = resolveTsConfig(basePath);
69
+ const baseOpts = baseConfig.compilerOptions ?? {};
70
+ const childOpts = config.compilerOptions ?? {};
71
+ config.compilerOptions = { ...baseOpts, ...childOpts };
72
+ delete config.extends;
73
+ }
74
+ return config;
75
+ }
76
+ // Strip JS-style comments (single-line and block) from a JSON string
77
+ // without corrupting quoted content. Walks the input character by
78
+ // character, tracking whether we are inside a string literal.
79
+ function stripJsonComments(text) {
80
+ let result = "";
81
+ let i = 0;
82
+ const len = text.length;
83
+ while (i < len) {
84
+ const ch = text[i];
85
+ // String literal — copy verbatim until the closing quote.
86
+ if (ch === '"') {
87
+ let j = i + 1;
88
+ while (j < len) {
89
+ if (text[j] === "\\") {
90
+ j += 2; // skip escaped character
91
+ }
92
+ else if (text[j] === '"') {
93
+ j++;
94
+ break;
95
+ }
96
+ else {
97
+ j++;
98
+ }
99
+ }
100
+ result += text.slice(i, j);
101
+ i = j;
102
+ continue;
103
+ }
104
+ // Single-line comment
105
+ if (ch === "/" && text[i + 1] === "/") {
106
+ // Skip until end of line.
107
+ i += 2;
108
+ while (i < len && text[i] !== "\n")
109
+ i++;
110
+ continue;
111
+ }
112
+ // Multi-line comment
113
+ if (ch === "/" && text[i + 1] === "*") {
114
+ i += 2;
115
+ while (i < len && !(text[i] === "*" && text[i + 1] === "/"))
116
+ i++;
117
+ i += 2; // skip closing */
118
+ continue;
119
+ }
120
+ result += ch;
121
+ i++;
122
+ }
123
+ return result;
124
+ }
125
+ /**
126
+ * Helper function to load and extract outDir from tsconfig.build.json,
127
+ * resolving the `extends` chain so the value can live in the base config.
40
128
  */
41
129
  function getOutDir() {
42
130
  const tsconfigBuildPath = (0, path_1.join)(process.cwd(), "tsconfig.build.json");
@@ -44,17 +132,11 @@ function getOutDir() {
44
132
  (0, cli_ui_1.printError)("Cannot find tsconfig.build.json. Please create one in the root directory", "tsconfig-build-path");
45
133
  process.exit(1);
46
134
  }
47
- let tsconfig;
48
- try {
49
- tsconfig = JSON.parse((0, fs_1.readFileSync)(tsconfigBuildPath, "utf-8"));
50
- }
51
- catch (err) {
52
- (0, cli_ui_1.printError)(`Failed to parse tsconfig.build.json: ${err.message}`, "tsconfig-build-path");
53
- process.exit(1);
54
- }
55
- const outDir = tsconfig.compilerOptions?.outDir;
135
+ const tsconfig = resolveTsConfig(tsconfigBuildPath);
136
+ const opts = tsconfig.compilerOptions;
137
+ const outDir = opts?.outDir;
56
138
  if (!outDir) {
57
- (0, cli_ui_1.printError)("Cannot find outDir in tsconfig.build.json. Please provide an outDir.", "tsconfig-build-path");
139
+ (0, cli_ui_1.printError)("Cannot find outDir in tsconfig.build.json (or its extended config). Please provide an outDir.", "tsconfig-build-path");
58
140
  process.exit(1);
59
141
  }
60
142
  if (!(0, fs_1.existsSync)(outDir)) {
@@ -147,6 +229,86 @@ exports.prodCommand = {
147
229
  await (0, exports.runCommand)({ command: "prod" });
148
230
  },
149
231
  };
232
+ /**
233
+ * Recursively collect the PIDs of every descendant of `rootPid`.
234
+ *
235
+ * `tsx --watch` exits immediately on SIGINT/SIGTERM and *abandons* the
236
+ * server process it spawned, so the server is still running its graceful
237
+ * shutdown after `tsx` is gone. To wait for it we snapshot the process
238
+ * tree (while it's still attached to `tsx`) and later poll those PIDs.
239
+ *
240
+ * Returns an empty list on Windows (no `ps`) or if the lookup fails, in
241
+ * which case the caller simply skips the wait.
242
+ */
243
+ function getDescendantPids(rootPid) {
244
+ if (process.platform === "win32" || !rootPid || rootPid < 0) {
245
+ return [];
246
+ }
247
+ try {
248
+ const res = (0, safe_spawn_1.safeSpawnSync)("ps", ["-A", "-o", "pid=,ppid="], {
249
+ encoding: "utf-8",
250
+ });
251
+ const out = res.stdout ? String(res.stdout) : "";
252
+ const childrenByParent = new Map();
253
+ for (const line of out.split("\n")) {
254
+ const match = line.trim().match(/^(\d+)\s+(\d+)$/);
255
+ if (!match)
256
+ continue;
257
+ const pid = Number(match[1]);
258
+ const ppid = Number(match[2]);
259
+ const siblings = childrenByParent.get(ppid) ?? [];
260
+ siblings.push(pid);
261
+ childrenByParent.set(ppid, siblings);
262
+ }
263
+ const descendants = [];
264
+ const stack = [rootPid];
265
+ while (stack.length > 0) {
266
+ const current = stack.pop();
267
+ for (const child of childrenByParent.get(current) ?? []) {
268
+ descendants.push(child);
269
+ stack.push(child);
270
+ }
271
+ }
272
+ return descendants;
273
+ }
274
+ catch {
275
+ return [];
276
+ }
277
+ }
278
+ /**
279
+ * Check whether a process is still alive. `process.kill(pid, 0)` sends no
280
+ * signal but performs the permission/existence check: ESRCH means gone,
281
+ * EPERM means alive but owned by another user.
282
+ */
283
+ function isPidAlive(pid) {
284
+ try {
285
+ process.kill(pid, 0);
286
+ return true;
287
+ }
288
+ catch (err) {
289
+ return err.code === "EPERM";
290
+ }
291
+ }
292
+ /**
293
+ * Resolve once every PID in `pids` has exited, or once `timeoutMs` elapses.
294
+ */
295
+ function waitForPidsToExit(pids, timeoutMs) {
296
+ return new Promise((resolve) => {
297
+ if (pids.length === 0) {
298
+ resolve();
299
+ return;
300
+ }
301
+ const deadline = Date.now() + timeoutMs;
302
+ const poll = () => {
303
+ if (Date.now() >= deadline || !pids.some(isPidAlive)) {
304
+ resolve();
305
+ return;
306
+ }
307
+ setTimeout(poll, 50);
308
+ };
309
+ poll();
310
+ });
311
+ }
150
312
  /**
151
313
  * Helper function to execute a command
152
314
  * @param command The command to execute
@@ -164,12 +326,60 @@ function execCmd(command, args, cwd = process.cwd()) {
164
326
  stdio: "inherit",
165
327
  cwd,
166
328
  });
167
- proc.on("error", (err) => reject(err));
168
- proc.on("close", (code) => {
169
- if (code === 0) {
170
- resolve();
329
+ // On Ctrl+C the SIGINT hits the whole foreground process group, so
330
+ // the spawned process (and the server it runs) already receive it.
331
+ // We keep *this* (parent) process alive — instead of dying from the
332
+ // default signal behavior — so the shell doesn't redraw its prompt
333
+ // until the server has finished shutting down. We also snapshot the
334
+ // child process tree on the first signal: `tsx --watch` exits right
335
+ // away and abandons the server, so we remember the server PID(s) to
336
+ // wait on them after `tsx` is gone.
337
+ let descendantPids = [];
338
+ let signalled = false;
339
+ const onSignal = () => {
340
+ if (signalled)
341
+ return;
342
+ signalled = true;
343
+ descendantPids = getDescendantPids(proc.pid ?? -1);
344
+ };
345
+ process.on("SIGINT", onSignal);
346
+ process.on("SIGTERM", onSignal);
347
+ const cleanup = () => {
348
+ process.removeListener("SIGINT", onSignal);
349
+ process.removeListener("SIGTERM", onSignal);
350
+ };
351
+ proc.on("error", (err) => {
352
+ cleanup();
353
+ reject(err);
354
+ });
355
+ proc.on("close", (code, signal) => {
356
+ // Exit codes 130 (SIGINT) and 143 (SIGTERM) mean the user or
357
+ // orchestrator intentionally stopped the process — not a failure.
358
+ const isSignalExit = signalled ||
359
+ code === 130 ||
360
+ code === 143 ||
361
+ signal === "SIGINT" ||
362
+ signal === "SIGTERM";
363
+ if (code === 0 || isSignalExit) {
364
+ if (isSignalExit) {
365
+ // The server (a `tsx --watch` grandchild) may still be
366
+ // finishing its graceful shutdown after `tsx` itself has
367
+ // exited. Wait for it to fully terminate so its final
368
+ // "Graceful shutdown completed" log lands before the shell
369
+ // prompt returns. The 9s cap matches the framework's own
370
+ // shutdown watchdog (default timeout + buffer).
371
+ void waitForPidsToExit(descendantPids, 9000).then(() => {
372
+ cleanup();
373
+ resolve();
374
+ });
375
+ }
376
+ else {
377
+ cleanup();
378
+ resolve();
379
+ }
171
380
  }
172
381
  else {
382
+ cleanup();
173
383
  reject(new Error(`Command failed with code ${code}`));
174
384
  }
175
385
  });
@@ -201,17 +411,14 @@ const transformPathAliases = async (outDir) => {
201
411
  if (!(0, fs_1.existsSync)(tsconfigPath)) {
202
412
  return; // No tsconfig.build.json, skip transformation
203
413
  }
204
- let tsconfig;
205
- try {
206
- tsconfig = JSON.parse((0, fs_1.readFileSync)(tsconfigPath, "utf-8"));
207
- }
208
- catch (err) {
209
- (0, cli_ui_1.printError)(`Failed to parse tsconfig.build.json for path-alias transform: ${err.message}`, "transform-paths");
210
- return;
211
- }
212
- const paths = tsconfig.compilerOptions?.paths;
213
- const baseUrl = tsconfig.compilerOptions?.baseUrl;
214
- if (!paths || !baseUrl) {
414
+ const tsconfig = resolveTsConfig(tsconfigPath);
415
+ const opts = tsconfig.compilerOptions;
416
+ const paths = opts?.paths;
417
+ // `baseUrl` is deprecated in TypeScript 7. When it's omitted the path
418
+ // targets are resolved relative to the tsconfig file itself, which is
419
+ // the project root in our generated templates so default to ".".
420
+ const baseUrl = opts?.baseUrl ?? ".";
421
+ if (!paths) {
215
422
  return; // No path aliases defined, skip
216
423
  }
217
424
  // Build regex patterns for each alias
@@ -1,4 +1,4 @@
1
1
  import { CommandModule } from "yargs";
2
- type CommandModuleArgs = {};
2
+ type CommandModuleArgs = Record<string, never>;
3
3
  declare const containerize: () => CommandModule<CommandModuleArgs, any>;
4
4
  export { containerize };
@@ -6,7 +6,7 @@ const containerize = () => {
6
6
  return {
7
7
  command: "containerize [target] [environment]",
8
8
  describe: "Generate container configurations for your ExpressoTS application.",
9
- aliases: ["c"],
9
+ aliases: ["ctr"],
10
10
  builder: (yargs) => {
11
11
  yargs.positional("target", {
12
12
  choices: ["docker", "kubernetes", "k8s", "compose"],