@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/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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
-
*
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
83
|
-
"💖 Sponsor
|
|
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(
|
|
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
|
-
*
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
package/bin/containerize/cli.js
CHANGED
|
@@ -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: ["
|
|
9
|
+
aliases: ["ctr"],
|
|
10
10
|
builder: (yargs) => {
|
|
11
11
|
yargs.positional("target", {
|
|
12
12
|
choices: ["docker", "kubernetes", "k8s", "compose"],
|